badline 0.18.0 → 0.19.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 +40 -0
- data/CLAUDE.md +8 -0
- data/CONTRIBUTING.md +4 -0
- data/README.md +10 -7
- data/doc/library.md +5 -1
- data/doc/media.md +3 -2
- data/doc/pinned-behaviour.md +41 -1
- data/doc/snapshot-format.md +56 -24
- data/doc/snapshots.md +45 -8
- data/doc/vice.md +344 -0
- data/lib/badline/audio/renderer.rb +4 -8
- data/lib/badline/c128.rb +4 -0
- data/lib/badline/c64/bus/fittings.rb +6 -0
- data/lib/badline/c64.rb +6 -0
- data/lib/badline/cbm2/p500.rb +4 -0
- data/lib/badline/cbm2.rb +4 -0
- data/lib/badline/chips/cpu/core.rb +29 -4
- data/lib/badline/chips/cpu/interrupts.rb +9 -0
- data/lib/badline/chips/cpu/keyed_state.rb +15 -9
- data/lib/badline/chips/cpu/microcode.rb +10 -0
- data/lib/badline/chips/rriot.rb +4 -1
- data/lib/badline/chips/sid/decimator.rb +1 -1
- data/lib/badline/chips/sid/keyed_parts.rb +51 -18
- data/lib/badline/chips/sid.rb +6 -2
- data/lib/badline/chips/vic_i/keyed_parts.rb +6 -1
- data/lib/badline/chips/vic_i/light_pen.rb +89 -0
- data/lib/badline/chips/vic_i.rb +12 -7
- data/lib/badline/drives/model1541/sector_reader.rb +20 -3
- data/lib/badline/drives/model1541.rb +1 -0
- data/lib/badline/drives/model1551.rb +1 -0
- data/lib/badline/drives/model1571.rb +1 -0
- data/lib/badline/drives/model1581.rb +1 -0
- data/lib/badline/drives/model2031.rb +1 -0
- data/lib/badline/drives/model4040/mechanism.rb +6 -3
- data/lib/badline/drives/model4040.rb +17 -1
- data/lib/badline/drives/model8050/board.rb +2 -0
- data/lib/badline/drives/model8050/dos_board.rb +49 -6
- data/lib/badline/drives/model8050/fdc_bus.rb +7 -0
- data/lib/badline/drives/model8050/idle.rb +137 -24
- data/lib/badline/drives/model8050.rb +14 -0
- data/lib/badline/drives/model9060/idle.rb +30 -10
- data/lib/badline/drives/model9060.rb +2 -0
- data/lib/badline/input/joystick.rb +15 -2
- data/lib/badline/kim1/tape.rb +3 -1
- data/lib/badline/kim1.rb +4 -0
- data/lib/badline/machine.rb +3 -0
- data/lib/badline/media/true_drive.rb +10 -0
- data/lib/badline/media/vic20_media.rb +4 -2
- data/lib/badline/media.rb +5 -1
- data/lib/badline/memory.rb +2 -4
- data/lib/badline/options/table.rb +1 -1
- data/lib/badline/pet/superpet/dongle.rb +13 -2
- data/lib/badline/pet/superpet.rb +5 -0
- data/lib/badline/pet.rb +4 -0
- data/lib/badline/plus4.rb +4 -0
- data/lib/badline/roms/README +11 -1
- data/lib/badline/snapshot/container.rb +6 -4
- data/lib/badline/snapshot/field_doc.rb +8 -4
- data/lib/badline/snapshot/image.rb +15 -6
- data/lib/badline/snapshot/vice/pet.rb +1 -0
- data/lib/badline/snapshot/vice/pet_dongle.rb +72 -0
- data/lib/badline/snapshot/vice/pet_mem.rb +11 -5
- data/lib/badline/snapshot/vice/vic20.rb +70 -0
- data/lib/badline/snapshot/vice/vic20_cpu.rb +61 -0
- data/lib/badline/snapshot/vice/vic20_mem.rb +85 -0
- data/lib/badline/snapshot/vice/vic20_restore.rb +124 -0
- data/lib/badline/snapshot/vice/vic20_vici.rb +200 -0
- data/lib/badline/snapshot/vice.rb +1 -0
- data/lib/badline/snapshot.rb +4 -4
- data/lib/badline/storage/disk_image/writing.rb +2 -2
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic20/port_wiring.rb +11 -0
- data/lib/badline/vic20.rb +4 -0
- metadata +9 -1
|
@@ -35,9 +35,10 @@ module Badline
|
|
|
35
35
|
CARTRIDGES = [32, 5, 21, 18, 1, 53].freeze
|
|
36
36
|
|
|
37
37
|
# The machines the doc is made from: each family at power-on, the C64
|
|
38
|
-
# with each kind of expansion, cartridge and media,
|
|
39
|
-
# the 264 series' models, its TCBM interface,
|
|
40
|
-
# true drives, the 1541 and the 1551, a CRTC PET,
|
|
38
|
+
# with each kind of expansion, cartridge and media, a second SID and
|
|
39
|
+
# its sound recording, the 264 series' models, its TCBM interface,
|
|
40
|
+
# cartridge, tape and true drives, the 1541 and the 1551, a CRTC PET,
|
|
41
|
+
# a 2001, an 8096,
|
|
41
42
|
# an 8296 with its user port jumpered, a PET with a disk and tapes and
|
|
42
43
|
# one with a true 2031, one with an 8050, one with an 8250, one with
|
|
43
44
|
# a 4040 and one with a D9060, a SuperPET in 6809 mode with a disk in
|
|
@@ -48,7 +49,7 @@ module Badline
|
|
|
48
49
|
{
|
|
49
50
|
"c64" => [C64.new, C64.new(ram_expansion: :plus256k), C64.new(reu: 512),
|
|
50
51
|
*CARTRIDGES.map { |type| with_cartridge(type) }, with_trap_dos, with_tape, with_drive(:d64),
|
|
51
|
-
with_drive(:d81), with_extra_sids],
|
|
52
|
+
with_drive(:d81), with_extra_sids, with_recording],
|
|
52
53
|
"vic20" => [Vic20.new, with_rom],
|
|
53
54
|
"c128" => [C128.new(mode: :c128), with_drive(:d71)],
|
|
54
55
|
"plus4" => [Plus4.new, Plus4.new(model: "c16"), Plus4.new(tcbm: true), Plus4.new(sid_card: :mos6581),
|
|
@@ -139,6 +140,9 @@ module Badline
|
|
|
139
140
|
C64.new.tap { |machine| machine.address_bus.add_sid(Chips::SID.new(at: 0xd420)) }
|
|
140
141
|
end
|
|
141
142
|
|
|
143
|
+
# A C64 recording its SID's output, as the window does with sound on.
|
|
144
|
+
def self.with_recording = C64.new.tap { |machine| machine.sound_source.record(rate: 44_100) }
|
|
145
|
+
|
|
142
146
|
def self.with_trap_dos
|
|
143
147
|
C64.new.tap { |machine| machine.mount(Storage::D64Image.new("", read_only: true, bytes: blank(174_848))) }
|
|
144
148
|
end
|
|
@@ -40,8 +40,7 @@ module Badline
|
|
|
40
40
|
# The snapshots VICE's emulators write that badline doesn't read, by
|
|
41
41
|
# the machine their container names.
|
|
42
42
|
UNREAD = {
|
|
43
|
-
Container::
|
|
44
|
-
Container::PLUS4 => "Plus/4 snapshots, not xplus4's"
|
|
43
|
+
Container::C128 => "C128 snapshots, not x128's", Container::PLUS4 => "Plus/4 snapshots, not xplus4's"
|
|
45
44
|
}.freeze
|
|
46
45
|
|
|
47
46
|
attr_reader :container
|
|
@@ -62,8 +61,8 @@ module Badline
|
|
|
62
61
|
end
|
|
63
62
|
|
|
64
63
|
# The snapshot with VICE's modules ahead of the BL-* ones for a C64,
|
|
65
|
-
# a PET, a CBM-II's B series or a P500, so x64sc,
|
|
66
|
-
# xcbm5x0 opens it too. Raises FormatError for a Plus/4, whose
|
|
64
|
+
# a VIC-20, a PET, a CBM-II's B series or a P500, so x64sc, xvic,
|
|
65
|
+
# xpet, xcbm2 or xcbm5x0 opens it too. Raises FormatError for a Plus/4, whose
|
|
67
66
|
# snapshots badline doesn't write for xplus4.
|
|
68
67
|
def self.exported(computer)
|
|
69
68
|
raise FormatError, "badline doesn't export a Plus/4's snapshot for VICE" if computer.family == :plus4
|
|
@@ -73,6 +72,7 @@ module Badline
|
|
|
73
72
|
sections = computer.save_modules
|
|
74
73
|
case computer.family
|
|
75
74
|
when :c64 then sections = Vice.export(computer) + sections
|
|
75
|
+
when :vic20 then sections = Vice::Vic20.export(computer, sections) + sections
|
|
76
76
|
when :pet then sections = Vice::PET.export(computer, sections) + sections
|
|
77
77
|
when :cbm2 then sections = cbm2_export(computer, sections) + sections
|
|
78
78
|
end
|
|
@@ -87,6 +87,7 @@ module Badline
|
|
|
87
87
|
# The VICE emulator a machine's VICE modules are for.
|
|
88
88
|
def self.vice_emulator(computer)
|
|
89
89
|
case machine_name(computer)
|
|
90
|
+
when Container::VIC20 then "xvic"
|
|
90
91
|
when Container::PET then "xpet"
|
|
91
92
|
when Container::CBM2 then "xcbm2"
|
|
92
93
|
when Container::CBM2_5X0 then "xcbm5x0"
|
|
@@ -132,13 +133,14 @@ module Badline
|
|
|
132
133
|
|
|
133
134
|
# A new machine, built as the snapshot's was and restored from it. A
|
|
134
135
|
# badline snapshot restores from its BL-* modules alone, and a VICE
|
|
135
|
-
# one through the modules badline reads, x64sc's,
|
|
136
|
-
# x128's and xplus4's own modules aren't read. Yields a line for
|
|
136
|
+
# one through the modules badline reads, x64sc's, xvic's, xpet's,
|
|
137
|
+
# xcbm2's or xcbm5x0's. x128's and xplus4's own modules aren't read. Yields a line for
|
|
137
138
|
# each thing left out, or warns it without a block. `detached`
|
|
138
139
|
# restores it without the host's files (Keyed::Modules).
|
|
139
140
|
def load(detached: false, &)
|
|
140
141
|
refuse_old_layout
|
|
141
142
|
refuse_unread
|
|
143
|
+
return load_xvic(&) if vic20? && !badline?
|
|
142
144
|
return load_xpet(&) if pet? && !badline?
|
|
143
145
|
return load_xcbm2(&) if cbm2? && !badline?
|
|
144
146
|
return load_xcbm5x0(&) if p500? && !badline?
|
|
@@ -175,6 +177,7 @@ module Badline
|
|
|
175
177
|
def restore(computer, &)
|
|
176
178
|
refuse_old_layout
|
|
177
179
|
report = if badline? then restore_modules(computer)
|
|
180
|
+
elsif vic20? then Vice::Vic20::Restore.import(@container, computer)
|
|
178
181
|
elsif pet? then Vice::PET::Restore.import(@container, computer)
|
|
179
182
|
elsif cbm2? then Vice::CBM2::Restore.import(@container, computer)
|
|
180
183
|
elsif p500? then Vice::P500::Restore.import(@container, computer)
|
|
@@ -192,6 +195,12 @@ module Badline
|
|
|
192
195
|
computer
|
|
193
196
|
end
|
|
194
197
|
|
|
198
|
+
def load_xvic(&)
|
|
199
|
+
computer = Vice::Vic20.setup(@container).build
|
|
200
|
+
tell(Vice::Vic20::Restore.apply(@container, computer), &)
|
|
201
|
+
computer
|
|
202
|
+
end
|
|
203
|
+
|
|
195
204
|
def load_xpet(&)
|
|
196
205
|
computer = Vice::PET.setup(@container).build
|
|
197
206
|
tell(Vice::PET::Restore.apply(@container, computer), &)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
module PET
|
|
7
|
+
# The SuperPET's 6702 in PETMEM's settings at the end, from their
|
|
8
|
+
# fifth byte: the byte the 6702 reads, the last write that shifted
|
|
9
|
+
# it, 1 while the last write left D0 clear, then a little-endian word
|
|
10
|
+
# for each data line's shift register. Bit k of a word, below the
|
|
11
|
+
# register's last stage, is stage k XORed with stage k + 1, and its
|
|
12
|
+
# last stage's bit is that stage XORed with the stage the next shift
|
|
13
|
+
# brings in if the latch then holds the last write that shifted.
|
|
14
|
+
# Every PET's settings hold a 6702 as the RES line leaves it.
|
|
15
|
+
#
|
|
16
|
+
# Mapped from xpet 3.10's snapshots after each of 56 writes to $EFE0
|
|
17
|
+
# from its monitor, against PET::SuperPET::Dongle after the same
|
|
18
|
+
# writes.
|
|
19
|
+
#
|
|
20
|
+
# Written and read: the registers, and whether D0 was clear in the
|
|
21
|
+
# last write. A write the 6702 hasn't shifted on yet is written as
|
|
22
|
+
# shifted. Read, the latch is $00 or $FF by D0, and no shift waits.
|
|
23
|
+
module Dongle
|
|
24
|
+
# Offsets in PETMEM's settings.
|
|
25
|
+
AT = 4
|
|
26
|
+
LENGTH = 19
|
|
27
|
+
OUTPUT = 0
|
|
28
|
+
ARMED = 2
|
|
29
|
+
WORDS = 3
|
|
30
|
+
# The last write that shifted, as xpet has it after RES.
|
|
31
|
+
RESET_LAST = 0x01
|
|
32
|
+
|
|
33
|
+
module_function
|
|
34
|
+
|
|
35
|
+
# The settings' bytes for a 6702 with `registers`, after a write
|
|
36
|
+
# of `latch`.
|
|
37
|
+
def export(registers, latch)
|
|
38
|
+
incoming = RESET_LAST ^ Badline::PET::SuperPET::Dongle::FEEDBACK ^ 0xff
|
|
39
|
+
output = 0
|
|
40
|
+
words = []
|
|
41
|
+
8.times do |bit|
|
|
42
|
+
register = registers[bit]
|
|
43
|
+
output |= (register & 1) << bit
|
|
44
|
+
top = Badline::PET::SuperPET::Dongle::LENGTHS[bit] - 1
|
|
45
|
+
last = ((register >> top) ^ register ^ (incoming >> bit)) & 1
|
|
46
|
+
word = ((register ^ (register >> 1)) & ((1 << top) - 1)) | (last << top)
|
|
47
|
+
words << (word & 0xff) << (word >> 8)
|
|
48
|
+
end
|
|
49
|
+
[output ^ Badline::PET::SuperPET::Dongle::INVERTED, RESET_LAST, latch.nobits?(0x01) ? 1 : 0, *words]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# The 6702's BL-SUPERPET keys from the settings' `bytes`, in a keyed
|
|
53
|
+
# module's writer.
|
|
54
|
+
def import(bytes, out)
|
|
55
|
+
stages = bytes[OUTPUT] ^ Badline::PET::SuperPET::Dongle::INVERTED
|
|
56
|
+
registers = Array.new(8) do |bit|
|
|
57
|
+
word = bytes[WORDS + (bit * 2)] | (bytes[WORDS + (bit * 2) + 1] << 8)
|
|
58
|
+
stage = (stages >> bit) & 1
|
|
59
|
+
register = stage
|
|
60
|
+
(Badline::PET::SuperPET::Dongle::LENGTHS[bit] - 1).times do |index|
|
|
61
|
+
stage ^= (word >> index) & 1
|
|
62
|
+
register |= stage << (index + 1)
|
|
63
|
+
end
|
|
64
|
+
register
|
|
65
|
+
end
|
|
66
|
+
out.ints("dongle.registers", registers).int("dongle.latch", bytes[ARMED].zero? ? 0xff : 0x00)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -9,7 +9,7 @@ module Badline
|
|
|
9
9
|
# the RAM, the screen RAM, 1K or 2K as the board has, or on the 8296
|
|
10
10
|
# its 32K from $8000, an 8096's, 8296's or SuperPET's 64K board, and
|
|
11
11
|
# 24 bytes of xpet's own settings, which name the 2001, and on the
|
|
12
|
-
# SuperPET the CPU that has the bus.
|
|
12
|
+
# SuperPET the CPU that has the bus and the 6702.
|
|
13
13
|
#
|
|
14
14
|
# The board is 0 for the discrete video, 1 for a 40-column CRTC, 2
|
|
15
15
|
# for an 80-column one, 3 for the SuperPET, 4 for the 8096 and 5 for
|
|
@@ -21,8 +21,8 @@ module Badline
|
|
|
21
21
|
# while the 6809 has the bus. Both were inferred from xpet 3.10's
|
|
22
22
|
# snapshots after writes to the bank latch and $EFFE, and in both
|
|
23
23
|
# modes. xpet has no system latch, so the bank latch's bit 7 and the
|
|
24
|
-
# system latch aren't saved, and a restore clears them.
|
|
25
|
-
#
|
|
24
|
+
# system latch aren't saved, and a restore clears them. The 6702 is
|
|
25
|
+
# in the settings from their fifth byte (Dongle).
|
|
26
26
|
module PETMem
|
|
27
27
|
NAME = "PETMEM"
|
|
28
28
|
MAJOR = 1
|
|
@@ -77,7 +77,11 @@ module Badline
|
|
|
77
77
|
fields.bytes(machine.board.cells) if superpet
|
|
78
78
|
settings = SETTINGS.dup
|
|
79
79
|
settings[1] = SETTINGS_2001 if machine.model.basic1?
|
|
80
|
-
|
|
80
|
+
if superpet
|
|
81
|
+
settings[SETTINGS_CPU] = SETTINGS_M6809 if machine.m6809_running?
|
|
82
|
+
dongle = machine.dongle
|
|
83
|
+
settings[Dongle::AT, Dongle::LENGTH] = Dongle.export(dongle.settled_registers, dongle.latch)
|
|
84
|
+
end
|
|
81
85
|
fields.bytes(settings).section(NAME, MAJOR, MINOR)
|
|
82
86
|
end
|
|
83
87
|
|
|
@@ -90,7 +94,7 @@ module Badline
|
|
|
90
94
|
offset >= 0 && data.getbyte(offset).to_i.anybits?(SETTINGS_M6809)
|
|
91
95
|
end
|
|
92
96
|
|
|
93
|
-
# A SuperPET's bank latch, socket bit
|
|
97
|
+
# A SuperPET's bank latch, socket bit, 64K board and 6702 by their
|
|
94
98
|
# BL-SUPERPET keys, in a keyed module's writer.
|
|
95
99
|
def import_superpet(section, out)
|
|
96
100
|
fields = FieldReader.new(section).skip(4)
|
|
@@ -98,6 +102,8 @@ module Badline
|
|
|
98
102
|
fields.skip(fields_before_board(section))
|
|
99
103
|
out.int("bank_latch", latches >> 4).int("socket", latches.anybits?(BANK_RAM) ? 0 : 1)
|
|
100
104
|
out.int("system_latch", 0).bytes("ram", fields.string(EXPANSION))
|
|
105
|
+
fields.skip(Dongle::AT)
|
|
106
|
+
Dongle.import(fields.bytes(Dongle::LENGTH), out)
|
|
101
107
|
end
|
|
102
108
|
|
|
103
109
|
# The RAM and the screen RAM before a SuperPET's board.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/snapshot/vice/vic20_cpu"
|
|
4
|
+
require "badline/snapshot/vice/vic20_mem"
|
|
5
|
+
require "badline/snapshot/vice/vic20_vici"
|
|
6
|
+
require "badline/snapshot/vice/vic20_restore"
|
|
7
|
+
|
|
8
|
+
module Badline
|
|
9
|
+
module Snapshot
|
|
10
|
+
module Vice
|
|
11
|
+
# xvic's modules for the VIC-20, PAL and NTSC: the CPU, the RAM, the
|
|
12
|
+
# VIC-I with the colour RAM, and both VIAs, written from badline's
|
|
13
|
+
# machine and read into it. Each module's file says what maps and
|
|
14
|
+
# what is approximated. The VIAs' are xpet's (PET::VIA). They go
|
|
15
|
+
# through the machine's own keyed fields (Keyed), which a save writes
|
|
16
|
+
# and a load reads.
|
|
17
|
+
module Vic20
|
|
18
|
+
# The modules xvic 3.10 resets the machine without, each as xvic
|
|
19
|
+
# leaves them with nothing attached: no cartridge, no file system
|
|
20
|
+
# drive open, the cassette port on with a datasette in it, the
|
|
21
|
+
# control port on with the joystick released, and nothing in the
|
|
22
|
+
# user port. With no drive modules xvic keeps its own drives as they
|
|
23
|
+
# are.
|
|
24
|
+
CARTRIDGE = ["VIC20CART", 2, 1, [0xff, 0xff, 0xff, 0xff, 0x00]].freeze
|
|
25
|
+
PERIPHERALS = [
|
|
26
|
+
["FSDRIVE", 0, 0, Array.new(258, 0)], ["TAPEPORT", 1, 0, [0x01, 0x01]], ["JOYPORT0", 0, 0, [0x01]],
|
|
27
|
+
["JOYSTICK0", 1, 2, [0x00, 0x00]], ["USERPORT", 1, 0, [0x01, 0x00]]
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
30
|
+
module_function
|
|
31
|
+
|
|
32
|
+
# The modules xvic 3.10 needs, in its order, describing `machine`,
|
|
33
|
+
# which Machine::Settling has brought between instructions, from
|
|
34
|
+
# its keyed modules, `sections`.
|
|
35
|
+
def export(machine, sections)
|
|
36
|
+
readers = {}
|
|
37
|
+
sections.each { |section| readers[section.name] = Keyed::Reader.parse(section) }
|
|
38
|
+
[MainCPU.export(machine), Mem.export(machine, reader(readers, "BL-MACHINE")), section(*CARTRIDGE),
|
|
39
|
+
VICI.export(machine, reader(readers, "BL-VICI"), reader(readers, "BL-COLORRAM")),
|
|
40
|
+
PET::VIA.export(reader(readers, "BL-VIA1"), "VIA1"), PET::VIA.export(reader(readers, "BL-VIA2"), "VIA2")] +
|
|
41
|
+
PERIPHERALS.map { |entry| section(*entry) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def section(name, major, minor, bytes) = FieldWriter.new.bytes(bytes).section(name, major, minor)
|
|
45
|
+
|
|
46
|
+
def reader(readers, name) = readers[name] || Keyed::Reader.missing(name)
|
|
47
|
+
|
|
48
|
+
# How the machine xvic saved was built: the RAM from VIC20MEM's
|
|
49
|
+
# blocks, and the model from the lines of the VIC-I's picture.
|
|
50
|
+
def setup(container)
|
|
51
|
+
memory = container[Mem::NAME]
|
|
52
|
+
raise FormatError, "the snapshot has no #{Mem::NAME} module" if memory.nil?
|
|
53
|
+
raise FormatError, "#{memory}: badline doesn't read this version" unless Mem.reads?(memory)
|
|
54
|
+
|
|
55
|
+
ram = Mem.ram_configuration(memory)
|
|
56
|
+
raise FormatError, "#{memory}: RAM in blocks badline doesn't fit together" if ram.nil?
|
|
57
|
+
|
|
58
|
+
Vic20Setup.new(ram:, model: model(container))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def model(container)
|
|
62
|
+
vic = container[VICI::NAME]
|
|
63
|
+
return "pal" if vic.nil? || !VICI.reads?(vic)
|
|
64
|
+
|
|
65
|
+
VICI.model(vic) || raise(FormatError, "#{vic}: a picture of a size badline's VIC-Is don't draw")
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
module Vic20
|
|
7
|
+
# The 6502 as xvic 3.10 saves it, MAINCPU 1.4, 83 bytes: the clock,
|
|
8
|
+
# the registers, four dwords (the last opcode and three xvic keeps
|
|
9
|
+
# at 0 between instructions), five clocks of the IRQ and NMI
|
|
10
|
+
# bookkeeping, then the IRQ and NMI source counts and the pending
|
|
11
|
+
# interrupts. It is x64sc's MAINCPU 1.4 without the BA flags and
|
|
12
|
+
# the two clocks at its end, and xpet's MAINCPU 1.3 with one more
|
|
13
|
+
# dword. The layout was inferred from xvic's snapshots against its
|
|
14
|
+
# monitor's registers and clock.
|
|
15
|
+
#
|
|
16
|
+
# Written: the clock and the registers, which between instructions
|
|
17
|
+
# are the whole CPU, with VIA 2 counted as an IRQ source while it
|
|
18
|
+
# pulls the line, and VIA 1 as an NMI source. VICE's interrupt
|
|
19
|
+
# clocks are written as long past.
|
|
20
|
+
#
|
|
21
|
+
# Read: the clock and the registers. The CPU starts on an
|
|
22
|
+
# instruction boundary, as xvic's snapshot is taken on one.
|
|
23
|
+
module MainCPU
|
|
24
|
+
NAME = "MAINCPU"
|
|
25
|
+
MAJOR = 1
|
|
26
|
+
MINOR = 4
|
|
27
|
+
SIZE = 83
|
|
28
|
+
IK_IRQ = 0x02
|
|
29
|
+
|
|
30
|
+
module_function
|
|
31
|
+
|
|
32
|
+
def reads?(section) = section.major == MAJOR && section.minor == MINOR && section.data.bytesize == SIZE
|
|
33
|
+
|
|
34
|
+
def export(machine)
|
|
35
|
+
cpu = machine.cpu
|
|
36
|
+
fields = FieldWriter.new.qword(machine.cycles)
|
|
37
|
+
[cpu.a, cpu.x, cpu.y, cpu.stack_pointer].each { |register| fields.byte(register) }
|
|
38
|
+
fields.word(cpu.program_counter).byte(cpu.p)
|
|
39
|
+
4.times { fields.dword(0) }
|
|
40
|
+
5.times { fields.qword(0) }
|
|
41
|
+
irqs = machine.via2.irq? ? 1 : 0
|
|
42
|
+
fields.dword(irqs).dword(machine.via1.irq? ? 1 : 0).dword(irqs.positive? ? IK_IRQ : 0)
|
|
43
|
+
fields.section(NAME, MAJOR, MINOR)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# The clock and the registers by their BL-CPU keys, in a keyed
|
|
47
|
+
# module's writer, and the clock.
|
|
48
|
+
def import(section, out)
|
|
49
|
+
fields = FieldReader.new(section)
|
|
50
|
+
clock = fields.qword
|
|
51
|
+
out.int("a", fields.byte).int("x", fields.byte).int("y", fields.byte).int("sp", fields.byte)
|
|
52
|
+
out.int("pc", fields.word).int("p", fields.byte).int("cycles", clock)
|
|
53
|
+
clock
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def clock(section) = FieldReader.new(section).qword
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
module Vic20
|
|
7
|
+
# The VIC-20's RAM as xvic 3.10 saves it, VIC20MEM 1.1: a byte of
|
|
8
|
+
# the expansion blocks that hold RAM (bit 0 the 3K at $0400, bits
|
|
9
|
+
# 1-3 BLK1-BLK3, bit 5 BLK5), the bytes the CPU's and the VIC's
|
|
10
|
+
# data lines last held, a byte badline doesn't model (0, 1 or 6 in
|
|
11
|
+
# xvic's snapshots, not following the blocks), then the RAM: $0000-$03FF, $1000-$1FFF, the 3K at
|
|
12
|
+
# $0400-$0FFF, and BLK1, BLK2, BLK3 and BLK5 in turn, each only if
|
|
13
|
+
# it holds RAM. The layout was inferred from xvic's snapshots of
|
|
14
|
+
# each -memory setting, with each block filled with a byte of its
|
|
15
|
+
# own and the data lines left at a byte the monitor wrote. Colour
|
|
16
|
+
# RAM is in the VIC-I's module (VICI).
|
|
17
|
+
#
|
|
18
|
+
# Written and read: the RAM and both data lines' bytes. The fourth
|
|
19
|
+
# byte is written as 0, and not read.
|
|
20
|
+
module Mem
|
|
21
|
+
NAME = "VIC20MEM"
|
|
22
|
+
MAJOR = 1
|
|
23
|
+
MINOR = 1
|
|
24
|
+
# Each expansion block's bit in the first byte, and the bytes it
|
|
25
|
+
# holds, as Badline::Vic20::Bus::BLOCKS names them.
|
|
26
|
+
BLOCK_BITS = { ram123: 0x01, blk1: 0x02, blk2: 0x04, blk3: 0x08, blk5: 0x20 }.freeze
|
|
27
|
+
INTERNAL = [[0x0000, 0x0400], [0x1000, 0x1000]].freeze
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
def reads?(section) = section.major == MAJOR && section.minor == MINOR
|
|
32
|
+
|
|
33
|
+
# The blocks the first byte names, or nil for bits no block has.
|
|
34
|
+
def blocks(section)
|
|
35
|
+
bits = FieldReader.new(section).byte
|
|
36
|
+
names = BLOCK_BITS.select { |_, bit| bits.anybits?(bit) }.keys
|
|
37
|
+
names.sum { |name| BLOCK_BITS[name] } == bits ? names : nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The RAM configuration (Badline::Vic20::Bus::RAM_CONFIGURATIONS)
|
|
41
|
+
# whose blocks the module has, or nil.
|
|
42
|
+
def ram_configuration(section)
|
|
43
|
+
names = blocks(section)
|
|
44
|
+
return nil if names.nil?
|
|
45
|
+
|
|
46
|
+
Badline::Vic20::Bus::RAM_CONFIGURATIONS.find { |_, blocks| blocks.sort == names.sort }&.first
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The regions the RAM comes in, as [address, length].
|
|
50
|
+
def regions(names)
|
|
51
|
+
INTERNAL + Badline::Vic20::Bus::BLOCKS.filter_map do |name, (page, pages)|
|
|
52
|
+
[page << 8, pages << 8] if names.include?(name)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# The module for `machine`, with the data lines' bytes from its
|
|
57
|
+
# BL-MACHINE fields, `machine_fields`.
|
|
58
|
+
def export(machine, machine_fields)
|
|
59
|
+
names = Badline::Vic20::Bus::RAM_CONFIGURATIONS.fetch(machine.ram_configuration)
|
|
60
|
+
fields = FieldWriter.new.byte(names.sum { |name| BLOCK_BITS[name] })
|
|
61
|
+
fields.byte(machine_fields.int("bus.data", 0)).byte(machine_fields.int("bus.video_data", 0))
|
|
62
|
+
fields.byte(0)
|
|
63
|
+
ram = machine.ram
|
|
64
|
+
regions(names).each do |address, length|
|
|
65
|
+
fields.bytes(Array.new(length) { |offset| ram.peek(address + offset) })
|
|
66
|
+
end
|
|
67
|
+
fields.section(NAME, MAJOR, MINOR)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# The RAM into `ram`, the machine's 48K at power-on, and the data
|
|
71
|
+
# lines' bytes into BL-MACHINE's writer `out`.
|
|
72
|
+
def import(section, ram, out)
|
|
73
|
+
fields = FieldReader.new(section)
|
|
74
|
+
names = blocks(section) || []
|
|
75
|
+
fields.skip(1)
|
|
76
|
+
out.int("bus.data", fields.byte).int("bus.video_data", fields.byte)
|
|
77
|
+
fields.skip(1)
|
|
78
|
+
regions(names).each { |address, length| ram[address, length] = fields.bytes(length) }
|
|
79
|
+
ram
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
module Vic20
|
|
7
|
+
# Restores a VIC-20 from a snapshot xvic wrote. The modules badline
|
|
8
|
+
# reads become the machine's own keyed modules (Keyed), which it
|
|
9
|
+
# loads as it loads its own snapshot. Whatever they don't set stays
|
|
10
|
+
# as the machine has it at power-on: the VIC-I's sound and picture,
|
|
11
|
+
# which it paints afresh, a cartridge, the datasette's tape, the
|
|
12
|
+
# disk device 8 serves and a true drive, which xvic's drive modules
|
|
13
|
+
# don't reach.
|
|
14
|
+
module Restore
|
|
15
|
+
# MAINCPU and VIC20MEM 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, Mem::NAME, VICI::NAME, "VIA1", "VIA2"].freeze
|
|
19
|
+
REQUIRED = [MainCPU::NAME, Mem::NAME].freeze
|
|
20
|
+
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
# Applies the modules badline reads to `computer`, and returns a
|
|
24
|
+
# Report. The snapshot goes into a new machine first, so one that
|
|
25
|
+
# fails leaves `computer` as it was, and one of a machine built
|
|
26
|
+
# another way fails too.
|
|
27
|
+
def import(container, computer)
|
|
28
|
+
unless computer.family == :vic20
|
|
29
|
+
raise FormatError, "the snapshot is of a VIC-20, not a #{Image::FAMILIES.fetch(computer.family.to_s)}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
setup = Vic20.setup(container)
|
|
33
|
+
ours = Vic20Setup.new(ram: computer.ram_configuration, model: computer.model)
|
|
34
|
+
raise FormatError, "the snapshot is of a VIC-20 with #{setup}, not #{ours}" unless setup == ours
|
|
35
|
+
|
|
36
|
+
apply(container, setup.build)
|
|
37
|
+
computer.power_cycle!
|
|
38
|
+
report = apply(container, computer)
|
|
39
|
+
kept = computer.mounted? ? ["the disk mounted in device 8 stays mounted"] : []
|
|
40
|
+
Report.new(applied: report.applied, ignored: report.ignored + kept)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Applies the modules badline reads to `computer`, a machine built
|
|
44
|
+
# as the snapshot's was, and returns the Report.
|
|
45
|
+
def apply(container, computer)
|
|
46
|
+
applied = []
|
|
47
|
+
ignored = []
|
|
48
|
+
READ.each do |name|
|
|
49
|
+
section = container[name]
|
|
50
|
+
next if section.nil?
|
|
51
|
+
|
|
52
|
+
if reads?(section)
|
|
53
|
+
applied << name
|
|
54
|
+
else
|
|
55
|
+
raise FormatError, "#{section}: badline doesn't read this version" if REQUIRED.include?(name)
|
|
56
|
+
|
|
57
|
+
ignored << "#{section}: badline doesn't read this version, left out"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
REQUIRED.each do |name|
|
|
61
|
+
raise FormatError, "the snapshot has no #{name} module" unless applied.include?(name)
|
|
62
|
+
end
|
|
63
|
+
computer.load_modules(Keyed::Modules.new(modules(container, computer, applied)))
|
|
64
|
+
Report.new(applied:, ignored: ignored + left_out(container, applied))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def reads?(section)
|
|
68
|
+
case section.name
|
|
69
|
+
when MainCPU::NAME then MainCPU.reads?(section)
|
|
70
|
+
when Mem::NAME then Mem.reads?(section)
|
|
71
|
+
when VICI::NAME then VICI.reads?(section)
|
|
72
|
+
else PET::VIA.reads?(section)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# The machine's keyed modules: its own as they stand, with those
|
|
77
|
+
# the modules `applied` names stand for in their place.
|
|
78
|
+
def modules(container, computer, applied)
|
|
79
|
+
own = {}
|
|
80
|
+
computer.save_modules.each { |section| own[section.name] = section }
|
|
81
|
+
ram = Keyed::Reader.parse(own.fetch("BL-RAM")).bytes("ram", "".b).bytes
|
|
82
|
+
own["BL-MACHINE"] = Keyed.section("BL-MACHINE") do |out|
|
|
83
|
+
out.text("family", "vic20")
|
|
84
|
+
Vic20Setup.new(ram: computer.ram_configuration, model: computer.model).write_fields(out)
|
|
85
|
+
out.int("cycles", MainCPU.clock(container[MainCPU::NAME]))
|
|
86
|
+
out.bool("nmi_asserted", via_irq?(container, "VIA1", applied))
|
|
87
|
+
Mem.import(container[Mem::NAME], ram, out)
|
|
88
|
+
end
|
|
89
|
+
own["BL-RAM"] = Keyed.section("BL-RAM") { |out| out.bytes("ram", ram) }
|
|
90
|
+
own["BL-CPU"] = Keyed.section("BL-CPU") { |out| MainCPU.import(container[MainCPU::NAME], out) }
|
|
91
|
+
vic(container, computer, applied, own)
|
|
92
|
+
%w[VIA1 VIA2].each do |name|
|
|
93
|
+
next unless applied.include?(name)
|
|
94
|
+
|
|
95
|
+
own["BL-#{name}"] = Keyed.section("BL-#{name}") { |out| PET::VIA.import(container[name], out) }
|
|
96
|
+
end
|
|
97
|
+
own.values
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def vic(container, computer, applied, own)
|
|
101
|
+
return unless applied.include?(VICI::NAME)
|
|
102
|
+
|
|
103
|
+
section = container[VICI::NAME]
|
|
104
|
+
own["BL-VICI"] = Keyed.section("BL-VICI") { |out| VICI.import(section, out, computer.vic.profile) }
|
|
105
|
+
own["BL-COLORRAM"] = Keyed.section("BL-COLORRAM") { |out| out.bytes("ram", VICI.colors(section)) }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Whether the VIA pulls its interrupt line: a flag it has enabled.
|
|
109
|
+
def via_irq?(container, name, applied)
|
|
110
|
+
return false unless applied.include?(name)
|
|
111
|
+
|
|
112
|
+
bytes = FieldReader.new(container[name]).bytes(PET::VIA::SIZE)
|
|
113
|
+
bytes[0x12].anybits?(bytes[0x13] & 0x7f)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def left_out(container, applied)
|
|
117
|
+
container.sections.reject { |section| applied.include?(section.name) }
|
|
118
|
+
.map { |section| "#{section}: badline doesn't read this module, left out" }
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|