badline 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +76 -0
- data/CLAUDE.md +1 -1
- data/README.md +130 -17
- data/doc/media.md +14 -2
- data/doc/pinned-behaviour.md +61 -0
- data/doc/snapshot-format.md +379 -70
- data/doc/snapshots.md +185 -10
- data/lib/badline/cbm2/acia.rb +10 -0
- data/lib/badline/cbm2/bus.rb +263 -0
- data/lib/badline/cbm2/cpu.rb +58 -0
- data/lib/badline/cbm2/crtc_video.rb +133 -0
- data/lib/badline/cbm2/ieee_drive.rb +18 -0
- data/lib/badline/cbm2/ieee_layout.rb +69 -0
- data/lib/badline/cbm2/ieee_ports.rb +104 -0
- data/lib/badline/cbm2/ieee_traps.rb +81 -0
- data/lib/badline/cbm2/keyboard_ports.rb +60 -0
- data/lib/badline/cbm2/keyed_modules.rb +67 -0
- data/lib/badline/cbm2/keyed_state.rb +91 -0
- data/lib/badline/cbm2/model.rb +44 -0
- data/lib/badline/cbm2/p500/control_ports.rb +80 -0
- data/lib/badline/cbm2/p500/ieee_layout.rb +41 -0
- data/lib/badline/cbm2/p500/keyed_state.rb +85 -0
- data/lib/badline/cbm2/p500/model.rb +39 -0
- data/lib/badline/cbm2/p500/pages.rb +57 -0
- data/lib/badline/cbm2/p500.rb +262 -0
- data/lib/badline/cbm2/sid_page.rb +19 -0
- data/lib/badline/cbm2.rb +267 -0
- data/lib/badline/{plus4 → chips}/acia/frames.rb +1 -1
- data/lib/badline/chips/acia/keyed_state.rb +47 -0
- data/lib/badline/chips/acia.rb +307 -0
- data/lib/badline/chips/crtc.rb +3 -2
- data/lib/badline/chips/riot/fast_forward.rb +72 -0
- data/lib/badline/chips/riot/keyed_parts.rb +69 -0
- data/lib/badline/chips/riot/timer.rb +80 -0
- data/lib/badline/chips/riot.rb +162 -0
- data/lib/badline/chips/rriot/keyed_state.rb +30 -0
- data/lib/badline/chips/rriot.rb +132 -0
- data/lib/badline/chips/tpi/keyed_state.rb +38 -0
- data/lib/badline/chips/tpi.rb +233 -0
- data/lib/badline/chips/vic/bank.rb +60 -5
- data/lib/badline/chips/vic/sprite.rb +4 -3
- data/lib/badline/drives/datasette/keyed_state.rb +9 -1
- data/lib/badline/drives/datasette.rb +27 -3
- data/lib/badline/drives/ieee_bus.rb +50 -4
- data/lib/badline/drives/model2031/ieee_port.rb +133 -0
- data/lib/badline/drives/model2031/keyed_state.rb +66 -0
- data/lib/badline/drives/model2031/parts.rb +25 -0
- data/lib/badline/drives/model2031.rb +92 -0
- data/lib/badline/drives/model8050/disk.rb +163 -0
- data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
- data/lib/badline/drives/model8050/idle.rb +177 -0
- data/lib/badline/drives/model8050/ieee_port.rb +147 -0
- data/lib/badline/drives/model8050/ip_bus.rb +93 -0
- data/lib/badline/drives/model8050/keyed_state.rb +160 -0
- data/lib/badline/drives/model8050/mechanism.rb +230 -0
- data/lib/badline/drives/model8050/parts.rb +37 -0
- data/lib/badline/drives/model8050.rb +243 -0
- data/lib/badline/frontend/app.rb +10 -3
- data/lib/badline/frontend/boot.rb +1 -1
- data/lib/badline/frontend/controls.rb +51 -15
- data/lib/badline/frontend/gamepads.rb +3 -3
- data/lib/badline/frontend/keys.rb +61 -2
- data/lib/badline/frontend/media_slots.rb +1 -1
- data/lib/badline/frontend/menu/menu_media.rb +14 -11
- data/lib/badline/frontend/menu/menu_pages.rb +29 -6
- data/lib/badline/frontend/menu/menu_ports.rb +40 -4
- data/lib/badline/frontend/menu/pause_menu.rb +1 -1
- data/lib/badline/frontend/pet_keyboard.rb +23 -7
- data/lib/badline/frontend/timeline.rb +19 -10
- data/lib/badline/kernal_trap/dos/disk_commands.rb +80 -0
- data/lib/badline/kernal_trap/dos/status.rb +3 -0
- data/lib/badline/kernal_trap/dos.rb +18 -5
- data/lib/badline/kernal_trap/ieee.rb +7 -2
- data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
- data/lib/badline/machine/ieee_drives.rb +194 -0
- data/lib/badline/machine/settling.rb +4 -0
- data/lib/badline/machine.rb +26 -8
- data/lib/badline/media/cbm2_media.rb +29 -0
- data/lib/badline/media/pet_media.rb +5 -5
- data/lib/badline/media/program.rb +6 -3
- data/lib/badline/media/true_drive.rb +50 -8
- data/lib/badline/media.rb +21 -8
- data/lib/badline/options/family.rb +15 -3
- data/lib/badline/options/help.rb +24 -6
- data/lib/badline/options/table.rb +25 -5
- data/lib/badline/options/validation.rb +47 -7
- data/lib/badline/options.rb +48 -15
- data/lib/badline/pet/bus/expansion.rb +99 -5
- data/lib/badline/pet/bus.rb +30 -12
- data/lib/badline/pet/cassettes.rb +22 -1
- data/lib/badline/pet/characters.rb +8 -2
- data/lib/badline/pet/crtc_video.rb +20 -7
- data/lib/badline/pet/ieee_drive.rb +17 -0
- data/lib/badline/pet/ieee_ports.rb +19 -1
- data/lib/badline/pet/ieee_traps.rb +10 -5
- data/lib/badline/pet/keyed_modules.rb +4 -1
- data/lib/badline/pet/keyed_parts.rb +10 -6
- data/lib/badline/pet/model.rb +56 -6
- data/lib/badline/pet/rom_sets.rb +30 -6
- data/lib/badline/pet.rb +45 -16
- data/lib/badline/plus4/acia.rb +4 -298
- data/lib/badline/plus4/keyed_parts.rb +0 -44
- data/lib/badline/plus4.rb +1 -0
- data/lib/badline/roms/8050/dos-2.7.rom +0 -0
- data/lib/badline/roms/8050/fdc-901869-01.rom +0 -0
- data/lib/badline/roms/8050/fdc-901885-04.rom +0 -0
- data/lib/badline/roms/README +144 -1
- data/lib/badline/roms/cbm2/basic128-02b.rom +0 -0
- data/lib/badline/roms/cbm2/basic128.rom +0 -0
- data/lib/badline/roms/cbm2/basic256.rom +0 -0
- data/lib/badline/roms/cbm2/characters-600.rom +0 -0
- data/lib/badline/roms/cbm2/characters-700.rom +0 -0
- data/lib/badline/roms/cbm2/kernal-03b.rom +0 -0
- data/lib/badline/roms/cbm2/kernal-04a.rom +0 -0
- data/lib/badline/roms/cbm2/p500-basic.rom +0 -0
- data/lib/badline/roms/cbm2/p500-kernal.rom +0 -0
- data/lib/badline/roms/dos2031.rom +0 -0
- data/lib/badline/roms/pet/characters-din.rom +0 -0
- data/lib/badline/roms/pet/editor4-80-b-50hz-8296.rom +0 -0
- data/lib/badline/snapshot/cbm2_setup.rb +53 -0
- data/lib/badline/snapshot/container.rb +9 -1
- data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
- data/lib/badline/snapshot/field_doc.rb +39 -3
- data/lib/badline/snapshot/image.rb +79 -15
- data/lib/badline/snapshot/pet_setup.rb +30 -13
- data/lib/badline/snapshot/vice/cbm2.rb +93 -0
- data/lib/badline/snapshot/vice/cbm2_acia.rb +75 -0
- data/lib/badline/snapshot/vice/cbm2_crtc.rb +127 -0
- data/lib/badline/snapshot/vice/cbm2_mains.rb +49 -0
- data/lib/badline/snapshot/vice/cbm2_mem.rb +75 -0
- data/lib/badline/snapshot/vice/cbm2_restore.rb +204 -0
- data/lib/badline/snapshot/vice/cbm2_tpi.rb +58 -0
- data/lib/badline/snapshot/vice/cias.rb +13 -9
- data/lib/badline/snapshot/vice/p500.rb +96 -0
- data/lib/badline/snapshot/vice/p500_restore.rb +130 -0
- data/lib/badline/snapshot/vice/p500_vicii.rb +166 -0
- data/lib/badline/snapshot/vice/pet.rb +121 -0
- data/lib/badline/snapshot/vice/pet_cpu.rb +70 -0
- data/lib/badline/snapshot/vice/pet_crtc.rb +241 -0
- data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
- data/lib/badline/snapshot/vice/pet_drive8050.rb +216 -0
- data/lib/badline/snapshot/vice/pet_mem.rb +93 -0
- data/lib/badline/snapshot/vice/pet_pias.rb +56 -0
- data/lib/badline/snapshot/vice/pet_restore.rb +214 -0
- data/lib/badline/snapshot/vice/pet_via.rb +165 -0
- data/lib/badline/snapshot/vice/sid_registers.rb +2 -1
- data/lib/badline/snapshot/vice/vicii_import.rb +10 -6
- data/lib/badline/snapshot/vice.rb +4 -0
- data/lib/badline/snapshot.rb +11 -3
- data/lib/badline/storage/crt_file.rb +7 -1
- data/lib/badline/storage/d64_image.rb +7 -0
- data/lib/badline/storage/d80_image.rb +17 -1
- data/lib/badline/storage/d81_image.rb +14 -0
- data/lib/badline/storage/disk_image/bam.rb +18 -3
- data/lib/badline/storage/disk_image/formatting.rb +69 -0
- data/lib/badline/storage/disk_image/writing.rb +35 -0
- data/lib/badline/storage/disk_image.rb +2 -0
- data/lib/badline/storage.rb +1 -0
- data/lib/badline/support/cbm2_checkpoint.rb +48 -0
- data/lib/badline/support/p500_checkpoint.rb +35 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline.rb +1 -0
- metadata +87 -2
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
module PET
|
|
7
|
+
# Restores a PET from a snapshot xpet wrote. The modules badline
|
|
8
|
+
# reads become the PET's own keyed modules (Keyed), which it loads
|
|
9
|
+
# as it loads its own snapshot. Whatever they don't set stays as the
|
|
10
|
+
# machine has it at power-on, and the datasettes' tapes and the
|
|
11
|
+
# disk device 8 serves stay as they are. A snapshot with a true 2031,
|
|
12
|
+
# 8050 or 8250 puts one on the bus, with the disks badline's drive
|
|
13
|
+
# of that model had in it, and one without takes badline's off.
|
|
14
|
+
module Restore
|
|
15
|
+
# MAINCPU and PETMEM hold the machine itself, so a version badline
|
|
16
|
+
# doesn't read fails the restore. The others are left out and
|
|
17
|
+
# reported.
|
|
18
|
+
READ = [MainCPU::NAME, PETMem::NAME, CRTC::NAME, "PIA1", "PIA2", VIA::NAME].freeze
|
|
19
|
+
# The host's media, which a restore keeps.
|
|
20
|
+
KEPT = %w[BL-TAPE1 BL-TAPE2 BL-TRAPDOS].freeze
|
|
21
|
+
# The disks in a true drive's drives, which a restore keeps.
|
|
22
|
+
DISKS = %w[BL-D8-DISK BL-D8-DISK1].freeze
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
# Applies the modules badline reads to `computer`, and returns a
|
|
27
|
+
# Report. The snapshot goes into a new machine first, so one that
|
|
28
|
+
# fails leaves `computer` as it was, and one of a machine built
|
|
29
|
+
# another way fails too.
|
|
30
|
+
def import(container, computer)
|
|
31
|
+
unless computer.family == :pet
|
|
32
|
+
raise FormatError, "the snapshot is of a PET, not a #{Image::FAMILIES.fetch(computer.family.to_s)}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
setup = PET.setup(container)
|
|
36
|
+
ours = PETSetup.of(computer)
|
|
37
|
+
raise FormatError, "the snapshot is of a #{setup}, not a #{ours}" unless setup == ours
|
|
38
|
+
|
|
39
|
+
apply(container, setup.build)
|
|
40
|
+
had_drive = computer.true_drive
|
|
41
|
+
computer.power_cycle!
|
|
42
|
+
report = apply(container, computer)
|
|
43
|
+
kept = computer.mounted? ? ["the disk mounted in device 8 stays mounted"] : []
|
|
44
|
+
if had_drive && !computer.true_drive.equal?(had_drive)
|
|
45
|
+
kept << "the #{had_drive.model_name} on device #{had_drive.device} is taken off the bus"
|
|
46
|
+
end
|
|
47
|
+
Report.new(applied: report.applied, ignored: report.ignored + kept)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Applies the modules badline reads to `computer`, a machine built
|
|
51
|
+
# as the snapshot's was, and returns the Report.
|
|
52
|
+
def apply(container, computer)
|
|
53
|
+
applied = []
|
|
54
|
+
ignored = []
|
|
55
|
+
sections = []
|
|
56
|
+
READ.each do |name|
|
|
57
|
+
section = container[name]
|
|
58
|
+
next if section.nil?
|
|
59
|
+
|
|
60
|
+
if reads?(section)
|
|
61
|
+
applied << name
|
|
62
|
+
else
|
|
63
|
+
raise FormatError, "#{section}: badline doesn't read this version" if required?(name)
|
|
64
|
+
|
|
65
|
+
ignored << "#{section}: badline doesn't read this version, left out"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
disks = []
|
|
69
|
+
computer.save_modules.each do |section|
|
|
70
|
+
sections << section if KEPT.include?(section.name)
|
|
71
|
+
disks << section if DISKS.include?(section.name)
|
|
72
|
+
end
|
|
73
|
+
sections += drive(container, applied, ignored, kept_disks(container, computer, disks))
|
|
74
|
+
computer.load_modules(Keyed::Modules.new(modules(container, computer, applied) + sections))
|
|
75
|
+
Report.new(applied:, ignored: ignored + left_out(container, applied))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# The disks badline's true drive has, which stay in the drive the
|
|
79
|
+
# snapshot puts on the bus when it is of the same model.
|
|
80
|
+
def kept_disks(container, computer, disks)
|
|
81
|
+
drive = computer.true_drive
|
|
82
|
+
return [] if drive.nil?
|
|
83
|
+
|
|
84
|
+
if Drive8050.present?(container)
|
|
85
|
+
Drive8050.model(container) == drive.model_name ? disks : []
|
|
86
|
+
else
|
|
87
|
+
drive.model_name == "2031" ? disks.first(1) : []
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# The true drive's modules where the snapshot has one badline
|
|
92
|
+
# reads, or none.
|
|
93
|
+
def drive(container, applied, ignored, disks)
|
|
94
|
+
return drive8050(container, applied, ignored, disks) if Drive8050.present?(container)
|
|
95
|
+
return [] unless Drive2031.present?(container)
|
|
96
|
+
|
|
97
|
+
names = [Drive2031::UNITS.first, Drive2031::CPU, Drive2031::VIA1, Drive2031::VIA2]
|
|
98
|
+
return [] unless readable?(container, names, "2031", ignored) { |section| Drive2031.reads?(section) }
|
|
99
|
+
|
|
100
|
+
applied.concat(names)
|
|
101
|
+
Drive2031.import(container, disks.first)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def drive8050(container, applied, ignored, disks)
|
|
105
|
+
model = Drive8050.model(container)
|
|
106
|
+
return [] unless readable?(container, Drive8050::NAMES, model || "8050", ignored) do |section|
|
|
107
|
+
Drive8050.reads?(section)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
if model.nil?
|
|
111
|
+
ignored << "the true drive: badline doesn't read its #{Drive8050::UNIT}'s type, left out"
|
|
112
|
+
return []
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
applied.concat(Drive8050::NAMES)
|
|
116
|
+
Drive8050.import(container, Drive8050.reference(model), disks)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Whether the snapshot has every module `names` lists at a version
|
|
120
|
+
# badline reads, saying which it doesn't in `ignored`.
|
|
121
|
+
def readable?(container, names, model, ignored, &reads)
|
|
122
|
+
unread = names.map { |name| container[name] }.reject { |section| section && reads.call(section) }
|
|
123
|
+
return true if unread.empty?
|
|
124
|
+
|
|
125
|
+
ignored << "the true #{model}: badline doesn't read #{unread.compact.join(', ')}, left out"
|
|
126
|
+
false
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def required?(name) = [MainCPU::NAME, PETMem::NAME].include?(name)
|
|
130
|
+
|
|
131
|
+
def reads?(section)
|
|
132
|
+
case section.name
|
|
133
|
+
when MainCPU::NAME then MainCPU.reads?(section)
|
|
134
|
+
when PETMem::NAME then PETMem.reads?(section)
|
|
135
|
+
when CRTC::NAME then CRTC.reads?(section)
|
|
136
|
+
when VIA::NAME then VIA.reads?(section)
|
|
137
|
+
else PIAs.reads?(section)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# The PET's keyed modules from the ones `applied` names.
|
|
142
|
+
def modules(container, computer, applied)
|
|
143
|
+
model = computer.model
|
|
144
|
+
crtc = applied.include?(CRTC::NAME) ? container[CRTC::NAME] : nil
|
|
145
|
+
video = video(crtc, computer)
|
|
146
|
+
drive = driving?(crtc, video, model)
|
|
147
|
+
sections = [Keyed.section("BL-MACHINE") { |out| machine(out, container, computer, drive) },
|
|
148
|
+
Keyed.section("BL-RAM") do |out|
|
|
149
|
+
PETMem.import(container[PETMem::NAME], out, model.screen_bytes)
|
|
150
|
+
end]
|
|
151
|
+
sections << Keyed.section("BL-CPU") { |out| MainCPU.import(container[MainCPU::NAME], out) }
|
|
152
|
+
%w[PIA1 PIA2].each do |name|
|
|
153
|
+
next unless applied.include?(name)
|
|
154
|
+
|
|
155
|
+
sections << Keyed.section("BL-#{name}") do |out|
|
|
156
|
+
PIAs.import(container[name], out)
|
|
157
|
+
out.bool("cb.c1_high", drive) if name == "PIA1"
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
if applied.include?(VIA::NAME)
|
|
161
|
+
sections << Keyed.section("BL-VIA") { |out| VIA.import(container[VIA::NAME], out) }
|
|
162
|
+
end
|
|
163
|
+
sections + video
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def machine(out, container, computer, drive)
|
|
167
|
+
out.text("family", "pet")
|
|
168
|
+
PETSetup.of(computer).write_fields(out)
|
|
169
|
+
out.int("cycles", FieldReader.new(container[MainCPU::NAME]).qword).bool("vertical_drive", drive)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# The vertical drive line, low in the CRTC's vertical sync or past
|
|
173
|
+
# the discrete video's rows, as the video's module, `video`, has it.
|
|
174
|
+
def driving?(crtc, video, model)
|
|
175
|
+
return true if crtc.nil?
|
|
176
|
+
|
|
177
|
+
fields = Keyed::Reader.parse(video.first)
|
|
178
|
+
if model.video == :discrete
|
|
179
|
+
fields.int("line", 0) < Badline::PET::DiscreteVideo::DISPLAYED_LINES
|
|
180
|
+
else
|
|
181
|
+
fields.int("vsync_lines", 0).zero?
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# The discrete video's module, or the CRTC's, run on from xpet's
|
|
186
|
+
# counters to where badline's drops the vertical drive line on the
|
|
187
|
+
# cycle xpet's does (CRTC::CRTC_LEAD). The CRTC runs on from the
|
|
188
|
+
# start of the beam's line, or for a beam in the first cycles of
|
|
189
|
+
# its line, a frame less those cycles on, which lands on the same
|
|
190
|
+
# counters while the registers stood through the frame as they
|
|
191
|
+
# stand.
|
|
192
|
+
def video(crtc, computer)
|
|
193
|
+
return [] if crtc.nil?
|
|
194
|
+
|
|
195
|
+
model = computer.model
|
|
196
|
+
return [Keyed.section("BL-VIDEO") { |out| CRTC.import_discrete(crtc, out) }] if model.video == :discrete
|
|
197
|
+
|
|
198
|
+
counters = Keyed.section("BL-CRTC") do |out|
|
|
199
|
+
CRTC.import(crtc, out, CRTC.columns(model), CRTC.attributes(model))
|
|
200
|
+
end
|
|
201
|
+
cycles = (CRTC::CRTC_LEAD + CRTC.column_of(crtc)) % CRTC.frame_cycles(CRTC.registers(crtc))
|
|
202
|
+
fields = CRTC.run_on(CRTC.fresh_crtc(computer), Keyed::Reader.parse(counters), cycles)
|
|
203
|
+
[Keyed.section("BL-CRTC") { |out| CRTC.copy(fields, out) }]
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def left_out(container, applied)
|
|
207
|
+
container.sections.reject { |section| READ.include?(section.name) || applied.include?(section.name) }
|
|
208
|
+
.map { |section| "#{section}: badline doesn't read this module, left out" }
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
module PET
|
|
7
|
+
# VIA 2.2, the 6522 at $E840: port A's and port B's output and
|
|
8
|
+
# direction registers, timer 1's latch and counter, timer 2's low
|
|
9
|
+
# and high latch, the value it was last loaded with and its counter,
|
|
10
|
+
# which timers will set their interrupt flag (timer 1 in bit 7,
|
|
11
|
+
# timer 2 in bit 6 while it counts cycles), the shift register, the
|
|
12
|
+
# ACR, the PCR, the interrupt flags and enable mask, timer 1's level
|
|
13
|
+
# on PB7 in bit 7, the shift register's clock ticks into its byte,
|
|
14
|
+
# CA2's level in bit 7, and whether timer 2's next underflow sets
|
|
15
|
+
# its flag. xpet saves each counter as a read on the next cycle
|
|
16
|
+
# finds it, where badline's holds it before that cycle counts.
|
|
17
|
+
#
|
|
18
|
+
# Written: timer 2's high latch and the value it was loaded with,
|
|
19
|
+
# which badline doesn't keep, as xpet has them before a program
|
|
20
|
+
# loads it, or while the shift register clocks off it, as its low
|
|
21
|
+
# byte reloads. Free-running, the shift register's ticks are only
|
|
22
|
+
# whether CB1 is low.
|
|
23
|
+
#
|
|
24
|
+
# Read: the registers and both timers with their latches, as they
|
|
25
|
+
# count from the next cycle, and the shift register where its clock
|
|
26
|
+
# ticks are. A load or reload under way, a shift clock tick due off
|
|
27
|
+
# timer 2 and the latched ports start afresh, and CB2's level
|
|
28
|
+
# follows from the PCR.
|
|
29
|
+
module VIA
|
|
30
|
+
NAME = "VIA"
|
|
31
|
+
MAJOR = 2
|
|
32
|
+
MINOR = 2
|
|
33
|
+
T1_ARMED = 0x80
|
|
34
|
+
T2_TIMED = 0x40
|
|
35
|
+
LEVELS = 0x60
|
|
36
|
+
# The shift register's clock ticks a byte takes, which an idle
|
|
37
|
+
# register's count stands at.
|
|
38
|
+
TICKS = 16
|
|
39
|
+
SIZE = 28
|
|
40
|
+
# The shift register's modes that clock off timer 2.
|
|
41
|
+
T2_SHIFTS = [1, 4, 5].freeze
|
|
42
|
+
|
|
43
|
+
module_function
|
|
44
|
+
|
|
45
|
+
def reads?(section) = section.major == MAJOR && section.minor == MINOR
|
|
46
|
+
|
|
47
|
+
# The VIA's module, under `name`, from its BL-VIA fields in `input`.
|
|
48
|
+
def export(input, name = NAME)
|
|
49
|
+
fields = FieldWriter.new
|
|
50
|
+
%w[ora ddra orb ddrb].each { |key| fields.byte(input.int(key, 0)) }
|
|
51
|
+
fields.word(input.int("t1.latch", 0xffff)).word(timer1(input))
|
|
52
|
+
t2 = timer2(input)
|
|
53
|
+
latch_low = input.int("t2.latch_low", 0xff)
|
|
54
|
+
fields.byte(latch_low).byte(0xff).word(shifting?(input) ? (t2 & 0xff00) | latch_low : 0xffff).word(t2)
|
|
55
|
+
fields.byte(armed(input)).byte(input.int("sr.data", 0))
|
|
56
|
+
fields.byte(input.int("acr", 0)).byte(input.int("pcr", 0))
|
|
57
|
+
fields.byte(input.int("ifr.flags", 0)).byte(input.int("ifr.enable", 0))
|
|
58
|
+
fields.byte(input.bool("t1.pb7", true) ? 0x80 : 0)
|
|
59
|
+
fields.byte(ticks(input))
|
|
60
|
+
fields.byte(LEVELS | (input.bool("ca.c2_output", true) ? 0x80 : 0)).zeros(2)
|
|
61
|
+
fields.byte(input.bool("t2.armed", false) ? 1 : 0).zeros(2).section(name, MAJOR, MINOR)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Timer 1's counter as a read on the next cycle finds it: as it
|
|
65
|
+
# stands while a load holds it, the latch while it reloads, and one
|
|
66
|
+
# less otherwise.
|
|
67
|
+
def timer1(input)
|
|
68
|
+
counter = input.int("t1.counter", 0xffff)
|
|
69
|
+
return counter if input.bool("t1.hold", false)
|
|
70
|
+
return input.int("t1.latch", 0xffff) if input.bool("t1.reload", false)
|
|
71
|
+
|
|
72
|
+
(counter - 1) & 0xffff
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Timer 2's counter as a read on the next cycle finds it: as it
|
|
76
|
+
# stands while it counts pulses or a load holds it, and one less
|
|
77
|
+
# otherwise.
|
|
78
|
+
def timer2(input)
|
|
79
|
+
counter = input.int("t2.counter", 0xffff)
|
|
80
|
+
return counter if input.bool("t2.hold", false) || input.bool("t2.counting_pulses", false)
|
|
81
|
+
|
|
82
|
+
(counter - 1) & 0xffff
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# The shift register's clock ticks into its byte: two a bit, the
|
|
86
|
+
# first one dropping CB1, TICKS when idle. Free-running, its bits
|
|
87
|
+
# never run out, and only whether CB1 is low counts.
|
|
88
|
+
def ticks(input)
|
|
89
|
+
bits = input.int("sr.bits", 0)
|
|
90
|
+
return TICKS if bits.zero?
|
|
91
|
+
|
|
92
|
+
low = input.bool("sr.clock", true) ? 0 : 1
|
|
93
|
+
return low if input.int("sr.mode", 0) == 4
|
|
94
|
+
|
|
95
|
+
((8 - bits) * 2) + low
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The shift register from its mode, data and the ticks into its
|
|
99
|
+
# byte (#ticks), with the last bit put out on CB2, or CB2 high
|
|
100
|
+
# as it is at power-on while the register is off.
|
|
101
|
+
def shift_register(out, mode, data, ticks)
|
|
102
|
+
ticks = [ticks, TICKS].min
|
|
103
|
+
bits = ticks == TICKS ? 0 : 8 - (ticks / 2)
|
|
104
|
+
bits = 8 if mode == 4 && ticks < TICKS
|
|
105
|
+
out.int("sr.mode", mode).int("sr.data", data).int("sr.bits", bits).int("sr.delay", 0)
|
|
106
|
+
out.bool("sr.clock", ticks.even?).bool("sr.cb2", mode.zero? || ticks.zero? || data.anybits?(0x01))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Whether the shift register clocks off timer 2.
|
|
110
|
+
def shifting?(input) = T2_SHIFTS.include?((input.int("acr", 0) >> 2) & 0x07)
|
|
111
|
+
|
|
112
|
+
# Timer 2 sets its flag, or clocks the shift register, while it
|
|
113
|
+
# counts cycles.
|
|
114
|
+
def armed(input)
|
|
115
|
+
pulses = input.bool("t2.count_pulses", false)
|
|
116
|
+
timed = !pulses && (input.bool("t2.armed", false) || shifting?(input))
|
|
117
|
+
(input.bool("t1.armed", false) ? T1_ARMED : 0) | (timed ? T2_TIMED : 0)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# The registers, the timers and the control lines by their BL-VIA
|
|
121
|
+
# keys, in a keyed module's writer.
|
|
122
|
+
def import(section, out)
|
|
123
|
+
bytes = FieldReader.new(section).bytes(SIZE)
|
|
124
|
+
acr = bytes[0x10]
|
|
125
|
+
pcr = bytes[0x11]
|
|
126
|
+
out.int("ora", bytes[0]).int("ddra", bytes[1]).int("orb", bytes[2]).int("ddrb", bytes[3])
|
|
127
|
+
out.int("acr", acr).int("pcr", pcr)
|
|
128
|
+
timers(out, bytes, acr)
|
|
129
|
+
shift_register(out, (acr >> 2) & 0x07, bytes[0x0f], bytes[0x15])
|
|
130
|
+
out.int("ifr.flags", bytes[0x12] & 0x7f).int("ifr.enable", bytes[0x13] & 0x7f)
|
|
131
|
+
control_lines(out, "ca.", pcr & 0x0f, bytes[0x16].anybits?(0x80))
|
|
132
|
+
control_lines(out, "cb.", pcr >> 4, true)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def timers(out, bytes, acr)
|
|
136
|
+
out.int("t1.latch", bytes[4] | (bytes[5] << 8)).int("t1.counter",
|
|
137
|
+
((bytes[6] | (bytes[7] << 8)) + 1) & 0xffff)
|
|
138
|
+
out.bool("t1.hold", false)
|
|
139
|
+
out.bool("t1.reload", false).bool("t1.armed", bytes[0x0e].anybits?(T1_ARMED))
|
|
140
|
+
out.bool("t1.pb7", bytes[0x14].anybits?(0x80))
|
|
141
|
+
pulses = acr.anybits?(0x20)
|
|
142
|
+
t2 = bytes[0x0c] | (bytes[0x0d] << 8)
|
|
143
|
+
out.int("t2.latch_low", bytes[8]).int("t2.counter", pulses ? t2 : (t2 + 1) & 0xffff).bool("t2.hold", false)
|
|
144
|
+
out.bool("t2.low_reload", false).bool("t2.low_underflowed", false)
|
|
145
|
+
out.bool("t2.armed", bytes[0x19].anybits?(0x01))
|
|
146
|
+
out.bool("t2.count_pulses", pulses).bool("t2.counting_pulses", pulses)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# A handshake under way holds C2 low.
|
|
150
|
+
def control_lines(out, prefix, control, c2_high)
|
|
151
|
+
mode = control >> 1
|
|
152
|
+
handshake_low = mode == 4 && !c2_high
|
|
153
|
+
output = case mode
|
|
154
|
+
when 4 then !handshake_low
|
|
155
|
+
when 6 then false
|
|
156
|
+
else true
|
|
157
|
+
end
|
|
158
|
+
out.int("#{prefix}control", control).bool("#{prefix}handshake_low", handshake_low)
|
|
159
|
+
out.int("#{prefix}pulse", 0).bool("#{prefix}c2_output", output)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -38,7 +38,8 @@ module Badline
|
|
|
38
38
|
|
|
39
39
|
def import(section, computer)
|
|
40
40
|
registers = FieldReader.new(section).skip(4).bytes(0x20)
|
|
41
|
-
|
|
41
|
+
sid = computer.sid
|
|
42
|
+
registers.first(0x19).each_with_index { |value, reg| sid.poke(sid.start + reg, value) }
|
|
42
43
|
end
|
|
43
44
|
end
|
|
44
45
|
end
|
|
@@ -4,20 +4,24 @@ module Badline
|
|
|
4
4
|
module Snapshot
|
|
5
5
|
module Vice
|
|
6
6
|
module VICII
|
|
7
|
-
# Reads a VIC-II module and puts it into badline's VIC.
|
|
7
|
+
# Reads a VIC-II module and puts it into badline's VIC. A reader of
|
|
8
|
+
# another layout passes no fields and sets the same state itself.
|
|
8
9
|
class Import
|
|
9
10
|
def initialize(fields)
|
|
10
11
|
@fields = fields
|
|
12
|
+
return if fields.nil?
|
|
13
|
+
|
|
11
14
|
read_beam
|
|
12
15
|
read_counters
|
|
13
16
|
read_sprites
|
|
14
17
|
end
|
|
15
18
|
|
|
16
|
-
def apply(computer)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
def apply(computer) = apply_to(computer.vic, computer.address_bus.color_ram, 0xd800)
|
|
20
|
+
|
|
21
|
+
# Puts it into `vic`, with its colour RAM into `color_ram` from
|
|
22
|
+
# `base`.
|
|
23
|
+
def apply_to(vic, color_ram, base)
|
|
24
|
+
@color_ram.each_with_index { |value, offset| color_ram.poke(base + offset, value) }
|
|
21
25
|
load_registers(vic)
|
|
22
26
|
position(vic)
|
|
23
27
|
load_counters(vic)
|
|
@@ -13,6 +13,10 @@ require "badline/snapshot/vice/peripherals"
|
|
|
13
13
|
require "badline/snapshot/vice/vicii"
|
|
14
14
|
require "badline/snapshot/vice/vicii_import"
|
|
15
15
|
require "badline/snapshot/vice/restore"
|
|
16
|
+
require "badline/snapshot/vice/pet"
|
|
17
|
+
require "badline/snapshot/vice/cbm2"
|
|
18
|
+
require "badline/snapshot/vice/p500"
|
|
19
|
+
require "badline/snapshot/vice/p500_restore"
|
|
16
20
|
|
|
17
21
|
module Badline
|
|
18
22
|
module Snapshot
|
data/lib/badline/snapshot.rb
CHANGED
|
@@ -36,8 +36,8 @@ module Badline
|
|
|
36
36
|
# end of the instruction its CPU is in, and of an REU transfer under
|
|
37
37
|
# way (Machine::Settling), so a save advances it by a few cycles, and a
|
|
38
38
|
# second save straight after runs none. A VIC-20's, a C128's, a
|
|
39
|
-
# Plus/4's or a
|
|
40
|
-
# xplus4 or
|
|
39
|
+
# Plus/4's, a PET's or a CBM-II's snapshot is in a container named as
|
|
40
|
+
# xvic, x128, xplus4, xpet or xcbm2 names its machine.
|
|
41
41
|
def dump(computer) = Image.of(computer).to_s
|
|
42
42
|
|
|
43
43
|
def read(path) = Image.new(Container.parse(File.binread(path)), name: File.basename(path))
|
|
@@ -52,7 +52,7 @@ module Badline
|
|
|
52
52
|
def restore(computer, path, &) = read(path).restore(computer, &)
|
|
53
53
|
|
|
54
54
|
# A machine's snapshots, as .vsf files and in memory. C64, Vic20, C128,
|
|
55
|
-
# Plus4 and
|
|
55
|
+
# Plus4, PET, CBM2 and CBM2::P500 each include it.
|
|
56
56
|
module Files
|
|
57
57
|
def save_snapshot(path) = Snapshot.save(self, path)
|
|
58
58
|
|
|
@@ -99,4 +99,12 @@ module Badline
|
|
|
99
99
|
class PET
|
|
100
100
|
include Snapshot::Files
|
|
101
101
|
end
|
|
102
|
+
|
|
103
|
+
class CBM2
|
|
104
|
+
include Snapshot::Files
|
|
105
|
+
|
|
106
|
+
class P500
|
|
107
|
+
include Snapshot::Files
|
|
108
|
+
end
|
|
109
|
+
end
|
|
102
110
|
end
|
|
@@ -8,6 +8,7 @@ module Badline
|
|
|
8
8
|
SIGNATURE = "C64 CARTRIDGE ".b
|
|
9
9
|
VIC20_SIGNATURE = "VIC20 CARTRIDGE ".b
|
|
10
10
|
PLUS4_SIGNATURE = "PLUS4 CARTRIDGE ".b
|
|
11
|
+
CBM2_SIGNATURE = "CBM2 CARTRIDGE ".b
|
|
11
12
|
CHIP_SIGNATURE = "CHIP".b
|
|
12
13
|
CHIP_HEADER_SIZE = 0x10
|
|
13
14
|
RAM_CHIP = 1
|
|
@@ -19,6 +20,9 @@ module Badline
|
|
|
19
20
|
# What .write needs of an image: the fields a parsed CRTFile reads.
|
|
20
21
|
Image = Data.define(:hardware_type, :subtype, :exrom, :game, :name, :chips)
|
|
21
22
|
|
|
23
|
+
# Whether the file at +path+ is signed as a CBM-II's cartridge.
|
|
24
|
+
def self.cbm2?(path) = File.binread(path, CBM2_SIGNATURE.length) == CBM2_SIGNATURE
|
|
25
|
+
|
|
22
26
|
def self.write(path, image)
|
|
23
27
|
File.binwrite(path, encode(image))
|
|
24
28
|
end
|
|
@@ -36,7 +40,8 @@ module Badline
|
|
|
36
40
|
end
|
|
37
41
|
|
|
38
42
|
# `bytes` stands in for the file's contents. `machine` is the one the
|
|
39
|
-
# image has to be for, :c64, :vic20 or :
|
|
43
|
+
# image has to be for, :c64, :vic20, :plus4 or :cbm2, or nil for
|
|
44
|
+
# any.
|
|
40
45
|
def initialize(path = nil, bytes: nil, machine: :c64)
|
|
41
46
|
parse(bytes || File.binread(path))
|
|
42
47
|
return if machine.nil? || @machine == machine
|
|
@@ -60,6 +65,7 @@ module Badline
|
|
|
60
65
|
return :c64 if bytes.start_with?(SIGNATURE)
|
|
61
66
|
return :vic20 if bytes.start_with?(VIC20_SIGNATURE)
|
|
62
67
|
return :plus4 if bytes.start_with?(PLUS4_SIGNATURE)
|
|
68
|
+
return :cbm2 if bytes.start_with?(CBM2_SIGNATURE)
|
|
63
69
|
|
|
64
70
|
raise FormatError, "Missing CRT signature"
|
|
65
71
|
end
|
|
@@ -52,6 +52,13 @@ module Badline
|
|
|
52
52
|
def bam_tracks = 1..35
|
|
53
53
|
def bam_count(track) = [18, 0, 4 * track]
|
|
54
54
|
def bam_bitmap(track) = [18, 0, (4 * track) + 1]
|
|
55
|
+
def bitmap_size = 3
|
|
56
|
+
|
|
57
|
+
# The header links the directory and holds the BAM itself.
|
|
58
|
+
def header_link = [18, 1]
|
|
59
|
+
def dos_type = "2A"
|
|
60
|
+
def header_tail = 4
|
|
61
|
+
def bam_headers(_id) = []
|
|
55
62
|
end
|
|
56
63
|
end
|
|
57
64
|
end
|
|
@@ -31,7 +31,7 @@ module Badline
|
|
|
31
31
|
def directory_track = 39
|
|
32
32
|
def directory_sector = 1
|
|
33
33
|
def directory_interleave = 3
|
|
34
|
-
def interleave =
|
|
34
|
+
def interleave = 5
|
|
35
35
|
|
|
36
36
|
# Only the directory track is kept from files. The BAM's blocks on
|
|
37
37
|
# track 38 are marked in use, and the rest of that track holds files.
|
|
@@ -43,6 +43,22 @@ module Badline
|
|
|
43
43
|
def bam_tracks = 1..SIDE_TRACKS
|
|
44
44
|
def bam_count(track) = [38, 3 * ((track - 1) / 50), 6 + (5 * ((track - 1) % 50))]
|
|
45
45
|
def bam_bitmap(track) = [38, 3 * ((track - 1) / 50), 7 + (5 * ((track - 1) % 50))]
|
|
46
|
+
def bitmap_size = 4
|
|
47
|
+
|
|
48
|
+
# The header links the BAM's first block, and each BAM block the next,
|
|
49
|
+
# the last one the directory. A BAM block names the format and the
|
|
50
|
+
# first track it covers and the one past its last.
|
|
51
|
+
def header_link = [38, 0]
|
|
52
|
+
def dos_type = "2C"
|
|
53
|
+
def header_tail = 4
|
|
54
|
+
|
|
55
|
+
def bam_headers(_id)
|
|
56
|
+
groups = bam_tracks.each_slice(50).to_a
|
|
57
|
+
groups.each_with_index.map do |tracks, block|
|
|
58
|
+
link = block == groups.length - 1 ? [directory_track, directory_sector] : [38, 3 * (block + 1)]
|
|
59
|
+
[38, 3 * block, [*link, dos_type.getbyte(1), 0, tracks.first, tracks.last + 1]]
|
|
60
|
+
end
|
|
61
|
+
end
|
|
46
62
|
end
|
|
47
63
|
end
|
|
48
64
|
end
|
|
@@ -21,6 +21,20 @@ module Badline
|
|
|
21
21
|
def bam_tracks = 1..80
|
|
22
22
|
def bam_count(track) = [40, track > 40 ? 2 : 1, 0x10 + (((track - 1) % 40) * 6)]
|
|
23
23
|
def bam_bitmap(track) = [40, track > 40 ? 2 : 1, 0x11 + (((track - 1) % 40) * 6)]
|
|
24
|
+
def bitmap_size = 5
|
|
25
|
+
|
|
26
|
+
# The header links the directory's first block, at 40/3. Each BAM
|
|
27
|
+
# block links the next, names the format and its complement, and
|
|
28
|
+
# holds the ID and the I/O byte.
|
|
29
|
+
def header_link = [40, 3]
|
|
30
|
+
def dos_type = "3D"
|
|
31
|
+
def header_tail = 2
|
|
32
|
+
|
|
33
|
+
def bam_headers(id)
|
|
34
|
+
[[40, 1, [40, 2]], [40, 2, [0, 0xff]]].map do |track, sector, link|
|
|
35
|
+
[track, sector, [*link, 0x44, 0xbb, *id, 0xc0, 0]]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
24
38
|
|
|
25
39
|
def sectors_in(_track) = 40
|
|
26
40
|
end
|
|
@@ -71,17 +71,32 @@ module Badline
|
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
# A chain's first block goes on the free track nearest the
|
|
74
|
-
# directory. Each block after it
|
|
75
|
-
#
|
|
74
|
+
# directory, from its sector 0. Each block after it looks from the
|
|
75
|
+
# interleave further round the track (#next_sector), on the same
|
|
76
|
+
# track or, once that one is full, from the same sector on the next
|
|
77
|
+
# track out.
|
|
76
78
|
def next_block(track = nil, sector = nil)
|
|
77
79
|
candidates = track ? [track, *tracks_after(track)] : allocation_order
|
|
80
|
+
start = track ? next_sector(track, sector) : 0
|
|
78
81
|
candidates.each do |candidate|
|
|
79
|
-
free = free_sector(candidate,
|
|
82
|
+
free = free_sector(candidate, start)
|
|
80
83
|
return [candidate, free] if free
|
|
81
84
|
end
|
|
82
85
|
nil
|
|
83
86
|
end
|
|
84
87
|
|
|
88
|
+
# The interleave on from the sector. Past the track's last sector
|
|
89
|
+
# it comes round again one short, but never below sector 0, as
|
|
90
|
+
# the 2031, 8050, 8250 and 1581 DOS do.
|
|
91
|
+
def next_sector(track, sector)
|
|
92
|
+
start = sector + interleave
|
|
93
|
+
count = sectors_in(track)
|
|
94
|
+
return start if start < count
|
|
95
|
+
|
|
96
|
+
start -= count
|
|
97
|
+
start.positive? ? start - 1 : start
|
|
98
|
+
end
|
|
99
|
+
|
|
85
100
|
def free_sector(track, first)
|
|
86
101
|
count = sectors_in(track)
|
|
87
102
|
(0...count).map { |i| (first + i) % count }.find { |s| block_free?(track, s) }
|