badline 0.16.1 → 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 +60 -0
- data/CLAUDE.md +1 -1
- data/README.md +102 -12
- data/doc/media.md +5 -2
- data/doc/pinned-behaviour.md +61 -0
- data/doc/snapshot-format.md +376 -69
- data/doc/snapshots.md +148 -14
- 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/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 +18 -4
- 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 +4 -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 +23 -6
- data/lib/badline/media/cbm2_media.rb +29 -0
- data/lib/badline/media/program.rb +6 -3
- data/lib/badline/media/true_drive.rb +38 -9
- data/lib/badline/media.rb +15 -6
- data/lib/badline/options/family.rb +15 -3
- data/lib/badline/options/help.rb +16 -2
- data/lib/badline/options/table.rb +18 -4
- data/lib/badline/options/validation.rb +27 -3
- data/lib/badline/options.rb +21 -7
- data/lib/badline/pet/cassettes.rb +22 -1
- data/lib/badline/pet/ieee_drive.rb +5 -64
- data/lib/badline/pet/ieee_ports.rb +7 -0
- data/lib/badline/pet.rb +15 -4
- 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 +111 -0
- 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/snapshot/cbm2_setup.rb +53 -0
- data/lib/badline/snapshot/container.rb +7 -2
- data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
- data/lib/badline/snapshot/field_doc.rb +38 -3
- data/lib/badline/snapshot/image.rb +62 -9
- 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 +7 -3
- data/lib/badline/snapshot/vice/pet_cpu.rb +5 -2
- data/lib/badline/snapshot/vice/pet_crtc.rb +10 -15
- 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_restore.rb +70 -6
- data/lib/badline/snapshot/vice/pet_via.rb +6 -5
- 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 +3 -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 +72 -2
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
module CBM2
|
|
7
|
+
# Restores a CBM-II from a snapshot xcbm2 wrote. The modules badline
|
|
8
|
+
# reads become the machine's own keyed modules (Keyed), which it
|
|
9
|
+
# loads as it loads its own snapshot: the CIA and the SID are read
|
|
10
|
+
# into a machine of their own first, as the C64's are, and saved
|
|
11
|
+
# from there. Whatever they don't set stays as the machine has it
|
|
12
|
+
# at power-on, and the disk device 8 serves through the traps stays
|
|
13
|
+
# as it is.
|
|
14
|
+
module Restore
|
|
15
|
+
# MAINCPU and CBM2MEM 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, CBM2Mem::NAME, CRTC::NAME, "CIA1", *TPI::NAMES, ACIA::NAME, SIDRegisters::NAME,
|
|
19
|
+
SIDExtended::NAME].freeze
|
|
20
|
+
REQUIRED = [MainCPU::NAME, CBM2Mem::NAME].freeze
|
|
21
|
+
# TPI1's inputs: the mains on I0, the CIA on I2 and the ACIA on I4.
|
|
22
|
+
MAINS = 0x01
|
|
23
|
+
CIA = 0x04
|
|
24
|
+
ACIA_LINE = 0x10
|
|
25
|
+
# I1, the IEEE-488 bus's SRQ, and I3, the coprocessor's, which
|
|
26
|
+
# nothing pulls.
|
|
27
|
+
IDLE_LINES = 0x0a
|
|
28
|
+
# The modules read into a machine of their own first.
|
|
29
|
+
CHIPS = %w[BL-CIA BL-SID].freeze
|
|
30
|
+
|
|
31
|
+
module_function
|
|
32
|
+
|
|
33
|
+
# Applies the modules badline reads to `computer`, and returns a
|
|
34
|
+
# Report. The snapshot goes into a new machine first, so one that
|
|
35
|
+
# fails leaves `computer` as it was, and one of a machine built
|
|
36
|
+
# another way fails too.
|
|
37
|
+
def import(container, computer)
|
|
38
|
+
unless computer.family == :cbm2
|
|
39
|
+
raise FormatError, "the snapshot is of a CBM-II, not a #{Image::FAMILIES.fetch(computer.family.to_s)}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
setup = CBM2.setup(container)
|
|
43
|
+
ours = CBM2Setup.of(computer)
|
|
44
|
+
raise FormatError, "the snapshot is of a #{setup}, not a #{ours}" unless setup == ours
|
|
45
|
+
|
|
46
|
+
apply(container, setup.build)
|
|
47
|
+
reapply(computer) { apply(container, computer) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Switches `computer` off and on and applies the snapshot with the
|
|
51
|
+
# block, then reports the disk the traps keep serving and a true
|
|
52
|
+
# drive taken off the bus.
|
|
53
|
+
def reapply(computer)
|
|
54
|
+
had_drive = computer.true_drive
|
|
55
|
+
computer.power_cycle!
|
|
56
|
+
report = yield
|
|
57
|
+
kept = computer.mounted? ? ["the disk mounted in device 8 stays mounted"] : []
|
|
58
|
+
if had_drive && !computer.true_drive.equal?(had_drive)
|
|
59
|
+
kept << "the #{had_drive.model_name} on device #{had_drive.device} is taken off the bus"
|
|
60
|
+
end
|
|
61
|
+
Report.new(applied: report.applied, ignored: report.ignored + kept)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Applies the modules badline reads to `computer`, a machine built
|
|
65
|
+
# as the snapshot's was, and returns the Report.
|
|
66
|
+
def apply(container, computer)
|
|
67
|
+
raise FormatError, "the snapshot has no #{MainCPU::NAME} module" if container[MainCPU::NAME].nil?
|
|
68
|
+
|
|
69
|
+
applied = []
|
|
70
|
+
ignored = []
|
|
71
|
+
READ.each do |name|
|
|
72
|
+
section = container[name]
|
|
73
|
+
next if section.nil?
|
|
74
|
+
|
|
75
|
+
reason = refusal(section, container)
|
|
76
|
+
if reason.nil?
|
|
77
|
+
applied << name
|
|
78
|
+
else
|
|
79
|
+
raise FormatError, "#{section}: #{reason}" if REQUIRED.include?(name)
|
|
80
|
+
|
|
81
|
+
ignored << "#{section}: #{reason}, left out"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
computer.load_modules(Keyed::Modules.new(modules(container, computer, applied, ignored)))
|
|
85
|
+
Report.new(applied:, ignored: ignored + left_out(container, applied))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Why badline leaves the module out, or nil when it reads it.
|
|
89
|
+
def refusal(section, container)
|
|
90
|
+
return "badline doesn't read this version" unless reads?(section)
|
|
91
|
+
|
|
92
|
+
"another SID engine wrote it" if section.name == SIDExtended::NAME && !Vice::Restore.resid?(container)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def reads?(section)
|
|
96
|
+
case section.name
|
|
97
|
+
when MainCPU::NAME then PET::MainCPU.reads?(section)
|
|
98
|
+
when CBM2Mem::NAME then CBM2Mem.reads?(section)
|
|
99
|
+
when CRTC::NAME then CRTC.reads?(section)
|
|
100
|
+
when "CIA1" then CIAs.reads?(section)
|
|
101
|
+
when ACIA::NAME then ACIA.reads?(section)
|
|
102
|
+
when SIDRegisters::NAME then SIDRegisters.reads?(section)
|
|
103
|
+
when SIDExtended::NAME then SIDExtended.reads?(section)
|
|
104
|
+
else TPI.reads?(section)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# The machine's keyed modules: its own at power-on, with those the
|
|
109
|
+
# modules `applied` names stand for in their place.
|
|
110
|
+
def modules(container, computer, applied, ignored)
|
|
111
|
+
chips = chips(container, computer, applied)
|
|
112
|
+
own = {}
|
|
113
|
+
computer.save_modules.each { |section| own[section.name] = section }
|
|
114
|
+
drive(container, computer, applied, ignored, own)
|
|
115
|
+
chips.save_modules.each { |section| own[section.name] = section if CHIPS.include?(section.name) }
|
|
116
|
+
cycles = FieldReader.new(container[MainCPU::NAME]).qword
|
|
117
|
+
crtc = crtc(container, computer, applied, own)
|
|
118
|
+
high = machine(own, computer, cycles, crtc)
|
|
119
|
+
own["BL-RAM"] = Keyed.section("BL-RAM") do |out|
|
|
120
|
+
CBM2Mem.import(container[CBM2Mem::NAME], out, computer.model.ram_segments)
|
|
121
|
+
end
|
|
122
|
+
own["BL-CPU"] = Keyed.section("BL-CPU") { |out| PET::MainCPU.import(container[MainCPU::NAME], out) }
|
|
123
|
+
if applied.include?(ACIA::NAME)
|
|
124
|
+
own["BL-ACIA"] = Keyed.section("BL-ACIA") { |out| ACIA.import(container[ACIA::NAME], out, cycles) }
|
|
125
|
+
end
|
|
126
|
+
lines = IDLE_LINES | (high ? MAINS : 0) | (chips.cia.interrupted? ? 0 : CIA)
|
|
127
|
+
tpis(container, applied, own, acia_irq?(container, applied) ? lines : lines | ACIA_LINE)
|
|
128
|
+
own.values
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Puts the true drive's modules in `own` in place of badline's: the
|
|
132
|
+
# 2031, 8050 or 8250 the snapshot has, as xpet's are read
|
|
133
|
+
# (PET::Restore), with the disks badline's drive of that model has,
|
|
134
|
+
# or none.
|
|
135
|
+
def drive(container, computer, applied, ignored, own)
|
|
136
|
+
disks = PET::Restore::DISKS.filter_map { |name| own[name] }
|
|
137
|
+
own.delete_if { |name, _| name.match?(/\ABL-D\d+(-|\z)/) }
|
|
138
|
+
kept = PET::Restore.kept_disks(container, computer, disks)
|
|
139
|
+
PET::Restore.drive(container, applied, ignored, kept).each { |section| own[section.name] = section }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# A machine of its own with xcbm2's CIA and SID read into it.
|
|
143
|
+
def chips(container, computer, applied)
|
|
144
|
+
machine = CBM2Setup.of(computer).build
|
|
145
|
+
CIAs.import_into(container["CIA1"], machine.cia) if applied.include?("CIA1")
|
|
146
|
+
SIDRegisters.import(container[SIDRegisters::NAME], machine) if applied.include?(SIDRegisters::NAME)
|
|
147
|
+
SIDExtended.import(container[SIDExtended::NAME], machine) if applied.include?(SIDExtended::NAME)
|
|
148
|
+
machine
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Puts the CRTC's and the picture's modules in `own`, and returns
|
|
152
|
+
# the CRTC's fields.
|
|
153
|
+
def crtc(container, computer, applied, own)
|
|
154
|
+
return Keyed::Reader.parse(own.fetch("BL-CRTC")) unless applied.include?(CRTC::NAME)
|
|
155
|
+
|
|
156
|
+
section = CRTC.import(container[CRTC::NAME], computer)
|
|
157
|
+
fields = Keyed::Reader.parse(section)
|
|
158
|
+
own["BL-CRTC"] = section
|
|
159
|
+
own["BL-VIDEO"] = CRTC.video(container[CRTC::NAME], fields)
|
|
160
|
+
fields
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Puts BL-MACHINE in `own`, with the mains' next falling edge where
|
|
164
|
+
# xcbm2 ticks it for the CRTC's fields `crtc`, and returns whether
|
|
165
|
+
# the mains is high.
|
|
166
|
+
def machine(own, computer, cycles, crtc)
|
|
167
|
+
high = true
|
|
168
|
+
own["BL-MACHINE"] = Keyed.section("BL-MACHINE") do |out|
|
|
169
|
+
out.text("family", "cbm2")
|
|
170
|
+
CBM2Setup.of(computer).write_fields(out)
|
|
171
|
+
out.int("cycles", cycles)
|
|
172
|
+
high = Mains.write(out, Mains.next_tick(crtc), Badline::CBM2::CLOCK_HZ / (2 * computer.mains_hz))
|
|
173
|
+
end
|
|
174
|
+
high
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Puts both TPIs' modules in `own`, TPI1's with its interrupt
|
|
178
|
+
# inputs at `lines`, TPI2's all high.
|
|
179
|
+
def tpis(container, applied, own, lines)
|
|
180
|
+
TPI::NAMES.zip([lines, 0x1f]).each do |name, levels|
|
|
181
|
+
next unless applied.include?(name)
|
|
182
|
+
|
|
183
|
+
own["BL-#{name}"] = Keyed.section("BL-#{name}") { |out| TPI.import(container[name], out, levels) }
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Whether the ACIA pulls its IRQ line: its status has the
|
|
188
|
+
# interrupt, and DTR enables it.
|
|
189
|
+
def acia_irq?(container, applied)
|
|
190
|
+
return false unless applied.include?(ACIA::NAME)
|
|
191
|
+
|
|
192
|
+
fields = FieldReader.new(container[ACIA::NAME]).skip(2)
|
|
193
|
+
fields.byte.anybits?(ACIA::IRQ) && fields.byte.anybits?(0x01)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def left_out(container, applied)
|
|
197
|
+
container.sections.reject { |section| applied.include?(section.name) }
|
|
198
|
+
.map { |section| "#{section}: badline doesn't read this module, left out" }
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
module CBM2
|
|
7
|
+
# TPI1 and TPI2 1.0, the 6525s: PRA, PRB, PRC, DDRA, DDRB, DDRC, CR
|
|
8
|
+
# and the AIR, then the interrupts latched and not yet taken through
|
|
9
|
+
# the AIR, and CA in bit 7 and CB in bit 6 of the last byte. In mode
|
|
10
|
+
# 1 xcbm2 keeps the latches in PRC's low five bits and the mask in
|
|
11
|
+
# DDRC's.
|
|
12
|
+
#
|
|
13
|
+
# xcbm2 saves no priority stack: a TPI read from its snapshot has
|
|
14
|
+
# none in service, and one written for it loses the one it has. The
|
|
15
|
+
# levels on the interrupt inputs aren't saved either, so they are
|
|
16
|
+
# read as the machine drives them (CBM2::Restore).
|
|
17
|
+
#
|
|
18
|
+
# Written and read: the registers, the latches, the mask, the AIR,
|
|
19
|
+
# CA and CB.
|
|
20
|
+
module TPI
|
|
21
|
+
NAMES = %w[TPI1 TPI2].freeze
|
|
22
|
+
MAJOR = 1
|
|
23
|
+
MINOR = 0
|
|
24
|
+
LATCHES = 0x1f
|
|
25
|
+
CA = 0x80
|
|
26
|
+
CB = 0x40
|
|
27
|
+
|
|
28
|
+
module_function
|
|
29
|
+
|
|
30
|
+
def reads?(section) = section.major == MAJOR && section.minor == MINOR
|
|
31
|
+
|
|
32
|
+
# The module `name` from the TPI's BL-TPI fields in `input`.
|
|
33
|
+
def export(name, input)
|
|
34
|
+
pra, prb, prc = input.ints("ports", [0, 0, 0])
|
|
35
|
+
ddra, ddrb, ddrc = input.ints("directions", [0, 0, 0])
|
|
36
|
+
control = input.int("control", 0)
|
|
37
|
+
latches = input.int("latches", 0)
|
|
38
|
+
prc = (prc & ~LATCHES) | latches if control.anybits?(0x01)
|
|
39
|
+
lines = (input.bool("ca", false) ? CA : 0) | (input.bool("cb", false) ? CB : 0)
|
|
40
|
+
FieldWriter.new.bytes([pra, prb, prc, ddra, ddrb, ddrc, control, input.int("air", 0), latches, lines])
|
|
41
|
+
.section(name, MAJOR, MINOR)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The registers by their BL-TPI keys, in a keyed module's writer,
|
|
45
|
+
# with `lines` the levels on the interrupt inputs.
|
|
46
|
+
def import(section, out, lines)
|
|
47
|
+
pra, prb, prc, ddra, ddrb, ddrc, control, air, _taken, edges = FieldReader.new(section).bytes(10)
|
|
48
|
+
out.ints("ports", [pra, prb, prc]).ints("directions", [ddra, ddrb, ddrc]).int("control", control)
|
|
49
|
+
mode1 = control.anybits?(0x01)
|
|
50
|
+
out.int("latches", mode1 ? prc & LATCHES : 0)
|
|
51
|
+
out.int("air", mode1 ? air & LATCHES : 0).ints("stack", [])
|
|
52
|
+
out.bool("ca", edges.anybits?(CA)).bool("cb", edges.anybits?(CB)).int("lines", lines)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -28,13 +28,16 @@ module Badline
|
|
|
28
28
|
module_function
|
|
29
29
|
|
|
30
30
|
def export(computer)
|
|
31
|
-
[computer.cia1, computer.cia2].each_with_index.map
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
[computer.cia1, computer.cia2].each_with_index.map { |cia, i| section(cia, NAMES[i]) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# One CIA's module, named `name`.
|
|
35
|
+
def section(cia, name)
|
|
36
|
+
fields = FieldWriter.new
|
|
37
|
+
registers(cia, fields)
|
|
38
|
+
time_of_day(cia.time_of_day, fields)
|
|
39
|
+
internals(cia, fields)
|
|
40
|
+
fields.section(name, MAJOR, MINOR)
|
|
38
41
|
end
|
|
39
42
|
|
|
40
43
|
def registers(cia, fields)
|
|
@@ -80,9 +83,10 @@ module Badline
|
|
|
80
83
|
def reads?(section) = section.major == MAJOR && section.minor >= MINOR
|
|
81
84
|
|
|
82
85
|
def import(section, computer)
|
|
83
|
-
|
|
84
|
-
Import.new(FieldReader.new(section)).apply(cia)
|
|
86
|
+
import_into(section, section.name == NAMES.first ? computer.cia1 : computer.cia2)
|
|
85
87
|
end
|
|
88
|
+
|
|
89
|
+
def import_into(section, cia) = Import.new(FieldReader.new(section)).apply(cia)
|
|
86
90
|
end
|
|
87
91
|
end
|
|
88
92
|
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/snapshot/vice/p500_vicii"
|
|
4
|
+
|
|
5
|
+
module Badline
|
|
6
|
+
module Snapshot
|
|
7
|
+
module Vice
|
|
8
|
+
# xcbm5x0's modules for the P500, the CBM 510: the B series' CPU,
|
|
9
|
+
# CIA, TPIs, ACIA and SID modules (CBM2), CBM2MEM with segment 15's
|
|
10
|
+
# video matrix and colour RAM, xcbm5x0's own VIC-II (VICII) and
|
|
11
|
+
# C500DATA, the cycles to the mains' next tick as a dword, then 4
|
|
12
|
+
# bytes xcbm5x0 leaves 0. They go
|
|
13
|
+
# through the machine's keyed modules as the B series' do, and the
|
|
14
|
+
# VIC through badline's VIC as the C64's does.
|
|
15
|
+
module P500
|
|
16
|
+
# The modules xcbm5x0 resets the machine without, each as xcbm5x0
|
|
17
|
+
# leaves them with nothing attached.
|
|
18
|
+
BEFORE_VIC = [["FSDRIVE", 0, 0, Array.new(258, 0)]].freeze
|
|
19
|
+
AFTER_VIC = [["TAPEPORT", 1, 0, [0x01, 0x01]], ["JOYPORT0", 0, 0, [0x01]], ["JOYSTICK0", 1, 2, [0x00, 0x00]],
|
|
20
|
+
["JOYPORT1", 0, 0, [0x01]], ["JOYSTICK1", 1, 2, [0x00, 0x00]]].freeze
|
|
21
|
+
MAINS = "C500DATA"
|
|
22
|
+
# CBM2MEM's second byte, which names the P500.
|
|
23
|
+
P500_RAM = 0x40
|
|
24
|
+
MATRIX = 0x400
|
|
25
|
+
# How many cycles after C500DATA's count badline's mains falls, so
|
|
26
|
+
# that TPI1 latches it on xcbm5x0's cycle.
|
|
27
|
+
TICK_LEAD = 4
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
def export(machine, sections)
|
|
32
|
+
readers = {}
|
|
33
|
+
sections.each { |section| readers[section.name] = Keyed::Reader.parse(section) }
|
|
34
|
+
machine.sid.catch_up!
|
|
35
|
+
irqs = [machine.tpi1.irq?, machine.vic.interrupted?].count(true)
|
|
36
|
+
[PET::MainCPU.write(machine, irqs), memory(machine), fixed("CBM2CART", 0, 1, [0x00]),
|
|
37
|
+
CIAs.section(machine.cia, "CIA1"),
|
|
38
|
+
*CBM2::TPI::NAMES.map { |name| CBM2::TPI.export(name, CBM2.reader(readers, "BL-#{name}")) },
|
|
39
|
+
CBM2::ACIA.export(CBM2.reader(readers, "BL-ACIA"), machine.cycles),
|
|
40
|
+
SIDRegisters.export(machine), SIDExtended.export(machine), *CBM2.drives(machine, readers),
|
|
41
|
+
*BEFORE_VIC.map { |entry| fixed(*entry) },
|
|
42
|
+
VICII.export(machine), mains(CBM2.reader(readers, "BL-MACHINE")), *AFTER_VIC.map { |entry| fixed(*entry) }]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def fixed(name, major, minor, bytes) = FieldWriter.new.bytes(bytes).section(name, major, minor)
|
|
46
|
+
|
|
47
|
+
# CBM2MEM as xcbm5x0 writes it: 128K, the P500's byte, the model
|
|
48
|
+
# line, both segment registers, segment 15's RAM with the registers
|
|
49
|
+
# in its first two bytes, the video matrix, the colour RAM a nibble
|
|
50
|
+
# a byte, then segments 0 and 1.
|
|
51
|
+
def memory(machine)
|
|
52
|
+
bus = machine.bus
|
|
53
|
+
system = bus.ram.snapshot
|
|
54
|
+
system[0] = bus.execution_segment
|
|
55
|
+
system[1] = bus.indirect_segment
|
|
56
|
+
fields = FieldWriter.new.byte(1).byte(P500_RAM).byte(CBM2::CBM2Mem::M600_50HZ)
|
|
57
|
+
fields.byte(bus.execution_segment).byte(bus.indirect_segment).bytes(system).bytes(bus.screen.snapshot)
|
|
58
|
+
fields.bytes(Array.new(MATRIX) { |offset| machine.color_ram.nibble(0xd400 + offset) })
|
|
59
|
+
Badline::CBM2::P500::Model::RAM_SEGMENTS.each { |segment| fields.bytes(bus.segment_ram[segment].snapshot) }
|
|
60
|
+
fields.section(CBM2::CBM2Mem::NAME, CBM2::CBM2Mem::MAJOR, CBM2::CBM2Mem::MINOR)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# C500DATA: the cycles to the mains' next tick, TICK_LEAD less than
|
|
64
|
+
# to the falling edge BL-MACHINE's countdown and level put it at.
|
|
65
|
+
def mains(machine)
|
|
66
|
+
countdown = machine.int("mains_countdown", 1)
|
|
67
|
+
cycles = machine.bool("mains_high", true) ? countdown : countdown + half_period(machine)
|
|
68
|
+
FieldWriter.new.dword([cycles - TICK_LEAD, 0].max).zeros(4).section(MAINS, 0, 0)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def half_period(machine)
|
|
72
|
+
setup = CBM2Setup.read_fields(machine)
|
|
73
|
+
Badline::CBM2::P500::Model.named(setup.model).region.clock_hz / (2 * setup.mains_hz)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# The cycles to the mains' falling edge where xcbm5x0 ticks it.
|
|
77
|
+
def cycles_to_tick(section) = FieldReader.new(section).dword + TICK_LEAD
|
|
78
|
+
|
|
79
|
+
# How the machine xcbm5x0 saved was built: a 510, NTSC where
|
|
80
|
+
# xcbm5x0's picture has NTSC's lines, at its region's mains.
|
|
81
|
+
def setup(container)
|
|
82
|
+
memory = CBM2.memory(container)
|
|
83
|
+
unless CBM2::CBM2Mem.mapped(memory) == P500_RAM && CBM2::CBM2Mem.ram_kb(memory) == 128
|
|
84
|
+
raise FormatError, "#{memory}: a P500 built otherwise than badline's 510, with 128K of RAM"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
vic = container[VICII::NAME]
|
|
88
|
+
ntsc = !vic.nil? && VICII.reads?(vic) && VICII.picture_lines(vic) == VICII::SIZES.fetch(263).last
|
|
89
|
+
model = ntsc ? "510ntsc" : "510"
|
|
90
|
+
CBM2Setup.new(model:, kernal: Badline::CBM2::P500::Model::KERNALS.keys.first,
|
|
91
|
+
mains_hz: Badline::CBM2::P500::Model.named(model).region.mains_hz, refresh: false)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
module P500
|
|
7
|
+
# Restores a P500 from a snapshot xcbm5x0 wrote, as CBM2::Restore
|
|
8
|
+
# restores a B series: the modules badline reads become the
|
|
9
|
+
# machine's keyed modules, the CIA, the SID and the VIC read into a
|
|
10
|
+
# machine of their own first. Whatever they don't set stays as the
|
|
11
|
+
# machine has it at power-on, and the disk device 8 serves through
|
|
12
|
+
# the traps stays as it is.
|
|
13
|
+
module Restore
|
|
14
|
+
READ = [MainCPU::NAME, CBM2::CBM2Mem::NAME, "CIA1", *CBM2::TPI::NAMES, CBM2::ACIA::NAME, SIDRegisters::NAME,
|
|
15
|
+
SIDExtended::NAME, VICII::NAME, MAINS].freeze
|
|
16
|
+
REQUIRED = [MainCPU::NAME, CBM2::CBM2Mem::NAME].freeze
|
|
17
|
+
# The modules read into a machine of their own first.
|
|
18
|
+
CHIPS = %w[BL-CIA BL-SID BL-VIC BL-COLORRAM].freeze
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
def import(container, computer)
|
|
23
|
+
unless computer.is_a?(Badline::CBM2::P500)
|
|
24
|
+
raise FormatError, "the snapshot is of a P500, not a #{computer_name(computer)}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
setup = P500.setup(container)
|
|
28
|
+
ours = CBM2Setup.of(computer)
|
|
29
|
+
raise FormatError, "the snapshot is of a #{setup}, not a #{ours}" unless setup == ours
|
|
30
|
+
|
|
31
|
+
apply(container, setup.build)
|
|
32
|
+
CBM2::Restore.reapply(computer) { apply(container, computer) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def computer_name(computer)
|
|
36
|
+
computer.family == :cbm2 ? "CBM-II's B series" : Image::FAMILIES.fetch(computer.family.to_s)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def apply(container, computer)
|
|
40
|
+
raise FormatError, "the snapshot has no #{MainCPU::NAME} module" if container[MainCPU::NAME].nil?
|
|
41
|
+
|
|
42
|
+
applied = []
|
|
43
|
+
ignored = []
|
|
44
|
+
READ.each do |name|
|
|
45
|
+
section = container[name]
|
|
46
|
+
next if section.nil?
|
|
47
|
+
|
|
48
|
+
reason = refusal(section, container)
|
|
49
|
+
if reason.nil?
|
|
50
|
+
applied << name
|
|
51
|
+
else
|
|
52
|
+
raise FormatError, "#{section}: #{reason}" if REQUIRED.include?(name)
|
|
53
|
+
|
|
54
|
+
ignored << "#{section}: #{reason}, left out"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
computer.load_modules(Keyed::Modules.new(modules(container, computer, applied, ignored)))
|
|
58
|
+
Report.new(applied:, ignored: ignored + CBM2::Restore.left_out(container, applied))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def refusal(section, container)
|
|
62
|
+
case section.name
|
|
63
|
+
when VICII::NAME then VICII.reads?(section) ? nil : "badline doesn't read this version"
|
|
64
|
+
when MAINS then section.major.zero? && section.minor.zero? ? nil : "badline doesn't read this version"
|
|
65
|
+
else CBM2::Restore.refusal(section, container)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def modules(container, computer, applied, ignored)
|
|
70
|
+
chips = chips(container, computer, applied)
|
|
71
|
+
own = {}
|
|
72
|
+
computer.save_modules.each { |section| own[section.name] = section }
|
|
73
|
+
CBM2::Restore.drive(container, computer, applied, ignored, own)
|
|
74
|
+
chips.save_modules.each { |section| own[section.name] = section if CHIPS.include?(section.name) }
|
|
75
|
+
cycles = FieldReader.new(container[MainCPU::NAME]).qword
|
|
76
|
+
high = machine(own, container, computer, cycles, applied)
|
|
77
|
+
own["BL-RAM"] = Keyed.section("BL-RAM") { |out| memory(container[CBM2::CBM2Mem::NAME], out) }
|
|
78
|
+
own["BL-CPU"] = Keyed.section("BL-CPU") { |out| PET::MainCPU.import(container[MainCPU::NAME], out) }
|
|
79
|
+
if applied.include?(CBM2::ACIA::NAME)
|
|
80
|
+
own["BL-ACIA"] = Keyed.section("BL-ACIA") { |out| CBM2::ACIA.import(container[CBM2::ACIA::NAME], out, cycles) }
|
|
81
|
+
end
|
|
82
|
+
lines = CBM2::Restore::IDLE_LINES | (high ? CBM2::Restore::MAINS : 0)
|
|
83
|
+
lines |= CBM2::Restore::CIA unless chips.cia.interrupted?
|
|
84
|
+
lines |= CBM2::Restore::ACIA_LINE unless CBM2::Restore.acia_irq?(container, applied)
|
|
85
|
+
CBM2::Restore.tpis(container, applied, own, lines)
|
|
86
|
+
own.values
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# A machine of its own with xcbm5x0's CIA, SID and VIC read into
|
|
90
|
+
# it.
|
|
91
|
+
def chips(container, computer, applied)
|
|
92
|
+
machine = CBM2::Restore.chips(container, computer, applied)
|
|
93
|
+
VICII.import(container[VICII::NAME], machine.vic, machine.color_ram) if applied.include?(VICII::NAME)
|
|
94
|
+
machine
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Puts BL-MACHINE in `own`, with the mains' next falling edge where
|
|
98
|
+
# C500DATA has xcbm5x0's tick, and returns whether the mains is
|
|
99
|
+
# high.
|
|
100
|
+
def machine(own, container, computer, cycles, applied)
|
|
101
|
+
half = computer.model.region.clock_hz / (2 * computer.mains_hz)
|
|
102
|
+
tick = applied.include?(MAINS) ? P500.cycles_to_tick(container[MAINS]) : half
|
|
103
|
+
high = true
|
|
104
|
+
own["BL-MACHINE"] = Keyed.section("BL-MACHINE") do |out|
|
|
105
|
+
out.text("family", "cbm2")
|
|
106
|
+
CBM2Setup.of(computer).write_fields(out)
|
|
107
|
+
out.int("cycles", cycles)
|
|
108
|
+
high = CBM2::Mains.write(out, [tick, 1].max, half)
|
|
109
|
+
end
|
|
110
|
+
high
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# The RAM, segment 15's video matrix and the segment registers by
|
|
114
|
+
# their BL-RAM keys.
|
|
115
|
+
def memory(section, out)
|
|
116
|
+
fields = FieldReader.new(section).skip(3)
|
|
117
|
+
out.int("execution_segment", fields.byte & 0x0f).int("indirect_segment", fields.byte & 0x0f)
|
|
118
|
+
system = fields.string(CBM2::CBM2Mem::SYSTEM_RAM)
|
|
119
|
+
out.bytes("system", "\0\0".b + system.byteslice(2, CBM2::CBM2Mem::SYSTEM_RAM - 2))
|
|
120
|
+
out.bytes("screen", fields.string(MATRIX))
|
|
121
|
+
fields.skip(MATRIX)
|
|
122
|
+
Badline::CBM2::P500::Model::RAM_SEGMENTS.each do |segment|
|
|
123
|
+
out.bytes("segment#{segment}", fields.string(CBM2::CBM2Mem::SEGMENT))
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|