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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/CLAUDE.md +8 -0
  4. data/CONTRIBUTING.md +4 -0
  5. data/README.md +10 -7
  6. data/doc/library.md +5 -1
  7. data/doc/media.md +3 -2
  8. data/doc/pinned-behaviour.md +41 -1
  9. data/doc/snapshot-format.md +56 -24
  10. data/doc/snapshots.md +45 -8
  11. data/doc/vice.md +344 -0
  12. data/lib/badline/audio/renderer.rb +4 -8
  13. data/lib/badline/c128.rb +4 -0
  14. data/lib/badline/c64/bus/fittings.rb +6 -0
  15. data/lib/badline/c64.rb +6 -0
  16. data/lib/badline/cbm2/p500.rb +4 -0
  17. data/lib/badline/cbm2.rb +4 -0
  18. data/lib/badline/chips/cpu/core.rb +29 -4
  19. data/lib/badline/chips/cpu/interrupts.rb +9 -0
  20. data/lib/badline/chips/cpu/keyed_state.rb +15 -9
  21. data/lib/badline/chips/cpu/microcode.rb +10 -0
  22. data/lib/badline/chips/rriot.rb +4 -1
  23. data/lib/badline/chips/sid/decimator.rb +1 -1
  24. data/lib/badline/chips/sid/keyed_parts.rb +51 -18
  25. data/lib/badline/chips/sid.rb +6 -2
  26. data/lib/badline/chips/vic_i/keyed_parts.rb +6 -1
  27. data/lib/badline/chips/vic_i/light_pen.rb +89 -0
  28. data/lib/badline/chips/vic_i.rb +12 -7
  29. data/lib/badline/drives/model1541/sector_reader.rb +20 -3
  30. data/lib/badline/drives/model1541.rb +1 -0
  31. data/lib/badline/drives/model1551.rb +1 -0
  32. data/lib/badline/drives/model1571.rb +1 -0
  33. data/lib/badline/drives/model1581.rb +1 -0
  34. data/lib/badline/drives/model2031.rb +1 -0
  35. data/lib/badline/drives/model4040/mechanism.rb +6 -3
  36. data/lib/badline/drives/model4040.rb +17 -1
  37. data/lib/badline/drives/model8050/board.rb +2 -0
  38. data/lib/badline/drives/model8050/dos_board.rb +49 -6
  39. data/lib/badline/drives/model8050/fdc_bus.rb +7 -0
  40. data/lib/badline/drives/model8050/idle.rb +137 -24
  41. data/lib/badline/drives/model8050.rb +14 -0
  42. data/lib/badline/drives/model9060/idle.rb +30 -10
  43. data/lib/badline/drives/model9060.rb +2 -0
  44. data/lib/badline/input/joystick.rb +15 -2
  45. data/lib/badline/kim1/tape.rb +3 -1
  46. data/lib/badline/kim1.rb +4 -0
  47. data/lib/badline/machine.rb +3 -0
  48. data/lib/badline/media/true_drive.rb +10 -0
  49. data/lib/badline/media/vic20_media.rb +4 -2
  50. data/lib/badline/media.rb +5 -1
  51. data/lib/badline/memory.rb +2 -4
  52. data/lib/badline/options/table.rb +1 -1
  53. data/lib/badline/pet/superpet/dongle.rb +13 -2
  54. data/lib/badline/pet/superpet.rb +5 -0
  55. data/lib/badline/pet.rb +4 -0
  56. data/lib/badline/plus4.rb +4 -0
  57. data/lib/badline/roms/README +11 -1
  58. data/lib/badline/snapshot/container.rb +6 -4
  59. data/lib/badline/snapshot/field_doc.rb +8 -4
  60. data/lib/badline/snapshot/image.rb +15 -6
  61. data/lib/badline/snapshot/vice/pet.rb +1 -0
  62. data/lib/badline/snapshot/vice/pet_dongle.rb +72 -0
  63. data/lib/badline/snapshot/vice/pet_mem.rb +11 -5
  64. data/lib/badline/snapshot/vice/vic20.rb +70 -0
  65. data/lib/badline/snapshot/vice/vic20_cpu.rb +61 -0
  66. data/lib/badline/snapshot/vice/vic20_mem.rb +85 -0
  67. data/lib/badline/snapshot/vice/vic20_restore.rb +124 -0
  68. data/lib/badline/snapshot/vice/vic20_vici.rb +200 -0
  69. data/lib/badline/snapshot/vice.rb +1 -0
  70. data/lib/badline/snapshot.rb +4 -4
  71. data/lib/badline/storage/disk_image/writing.rb +2 -2
  72. data/lib/badline/version.rb +1 -1
  73. data/lib/badline/vic20/port_wiring.rb +11 -0
  74. data/lib/badline/vic20.rb +4 -0
  75. 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, and a second SID,
39
- # the 264 series' models, its TCBM interface, cartridge, tape and
40
- # true drives, the 1541 and the 1551, a CRTC PET, a 2001, an 8096,
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::VIC20 => "VIC-20 snapshots, not xvic's", Container::C128 => "C128 snapshots, not x128's",
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, xpet, xcbm2 or
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, xpet's, xcbm2's or xcbm5x0's. xvic'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), &)
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "badline/snapshot/vice/cbm2_acia"
4
4
  require "badline/snapshot/vice/pet_cpu"
5
+ require "badline/snapshot/vice/pet_dongle"
5
6
  require "badline/snapshot/vice/pet_mem"
6
7
  require "badline/snapshot/vice/pet_pias"
7
8
  require "badline/snapshot/vice/pet_via"
@@ -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. Nor is the
25
- # 6702: a restore leaves it as the RES line does.
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
- settings[SETTINGS_CPU] = SETTINGS_M6809 if superpet && machine.m6809_running?
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 and 64K board by their
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