badline 0.17.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 (195) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +193 -0
  3. data/CLAUDE.md +19 -7
  4. data/CONTRIBUTING.md +11 -1
  5. data/README.md +178 -26
  6. data/doc/library.md +5 -1
  7. data/doc/media.md +3 -2
  8. data/doc/pinned-behaviour.md +169 -1
  9. data/doc/snapshot-format.md +302 -35
  10. data/doc/snapshots.md +97 -26
  11. data/doc/vice.md +344 -0
  12. data/exe/badline-ruby +5 -0
  13. data/lib/badline/audio/renderer.rb +4 -8
  14. data/lib/badline/audio/terminal.rb +34 -2
  15. data/lib/badline/c128/keyed_state.rb +2 -0
  16. data/lib/badline/c128.rb +5 -0
  17. data/lib/badline/c64/bus/fittings.rb +6 -0
  18. data/lib/badline/c64/keyed_state.rb +2 -0
  19. data/lib/badline/c64.rb +7 -0
  20. data/lib/badline/cbm2/keyed_state.rb +4 -0
  21. data/lib/badline/cbm2/p500.rb +9 -3
  22. data/lib/badline/cbm2.rb +10 -3
  23. data/lib/badline/chips/cia/timer.rb +2 -2
  24. data/lib/badline/chips/cpu/core.rb +29 -4
  25. data/lib/badline/chips/cpu/instruction_set.rb +1 -1
  26. data/lib/badline/chips/cpu/interrupts.rb +9 -0
  27. data/lib/badline/chips/cpu/keyed_state.rb +15 -9
  28. data/lib/badline/chips/cpu/microcode.rb +10 -0
  29. data/lib/badline/chips/m6809/arithmetic.rb +140 -0
  30. data/lib/badline/chips/m6809/bus_steps.rb +223 -0
  31. data/lib/badline/chips/m6809/core.rb +149 -0
  32. data/lib/badline/chips/m6809/indexed.rb +82 -0
  33. data/lib/badline/chips/m6809/interrupts.rb +159 -0
  34. data/lib/badline/chips/m6809/keyed_state.rb +147 -0
  35. data/lib/badline/chips/m6809/opcodes.rb +159 -0
  36. data/lib/badline/chips/m6809/operations.rb +258 -0
  37. data/lib/badline/chips/m6809/plans.rb +143 -0
  38. data/lib/badline/chips/m6809/registers.rb +82 -0
  39. data/lib/badline/chips/m6809/stack.rb +140 -0
  40. data/lib/badline/chips/m6809.rb +13 -0
  41. data/lib/badline/chips/riot/timer.rb +14 -3
  42. data/lib/badline/chips/rriot.rb +8 -3
  43. data/lib/badline/chips/sid/decimator.rb +1 -1
  44. data/lib/badline/chips/sid/filter.rb +4 -4
  45. data/lib/badline/chips/sid/keyed_parts.rb +51 -18
  46. data/lib/badline/chips/sid/waveform.rb +6 -6
  47. data/lib/badline/chips/sid.rb +6 -2
  48. data/lib/badline/chips/vic_i/keyed_parts.rb +12 -2
  49. data/lib/badline/chips/vic_i/light_pen.rb +89 -0
  50. data/lib/badline/chips/vic_i.rb +91 -23
  51. data/lib/badline/data_path.rb +3 -3
  52. data/lib/badline/drives/ieee_models.rb +54 -0
  53. data/lib/badline/drives/model1001.rb +37 -0
  54. data/lib/badline/drives/model1541/sector_reader.rb +20 -3
  55. data/lib/badline/drives/model1541.rb +1 -0
  56. data/lib/badline/drives/model1551.rb +1 -0
  57. data/lib/badline/drives/model1571.rb +1 -0
  58. data/lib/badline/drives/model1581.rb +1 -0
  59. data/lib/badline/drives/model2031.rb +7 -0
  60. data/lib/badline/drives/model4040/disk.rb +62 -0
  61. data/lib/badline/drives/model4040/ip_bus.rb +46 -0
  62. data/lib/badline/drives/model4040/keyed_state.rb +57 -0
  63. data/lib/badline/drives/model4040/mechanism.rb +195 -0
  64. data/lib/badline/drives/model4040/parts.rb +20 -0
  65. data/lib/badline/drives/model4040.rb +195 -0
  66. data/lib/badline/drives/model8050/board.rb +79 -0
  67. data/lib/badline/drives/model8050/dos_board.rb +134 -0
  68. data/lib/badline/drives/model8050/fdc_bus.rb +7 -0
  69. data/lib/badline/drives/model8050/idle.rb +148 -31
  70. data/lib/badline/drives/model8050/keyed_state.rb +5 -2
  71. data/lib/badline/drives/model8050.rb +32 -130
  72. data/lib/badline/drives/model9060/idle.rb +169 -0
  73. data/lib/badline/drives/model9060/keyed_state.rb +123 -0
  74. data/lib/badline/drives/model9060/parts.rb +98 -0
  75. data/lib/badline/drives/model9060/sasi_bus.rb +88 -0
  76. data/lib/badline/drives/model9060/sasi_commands.rb +168 -0
  77. data/lib/badline/drives/model9060/sasi_target.rb +161 -0
  78. data/lib/badline/drives/model9060.rb +197 -0
  79. data/lib/badline/frontend/app.rb +8 -0
  80. data/lib/badline/frontend/boot.rb +3 -2
  81. data/lib/badline/frontend/controls.rb +62 -6
  82. data/lib/badline/frontend/keys.rb +36 -0
  83. data/lib/badline/frontend/kim1_panel.rb +208 -0
  84. data/lib/badline/frontend/media_slots.rb +8 -1
  85. data/lib/badline/frontend/menu/drive_types.rb +56 -0
  86. data/lib/badline/frontend/menu/menu_dialogs.rb +61 -14
  87. data/lib/badline/frontend/menu/menu_media.rb +42 -12
  88. data/lib/badline/frontend/menu/menu_pages.rb +97 -2
  89. data/lib/badline/frontend/menu/menu_ports.rb +38 -1
  90. data/lib/badline/frontend/menu/pause_menu.rb +5 -1
  91. data/lib/badline/frontend/pet_keyboard.rb +23 -1
  92. data/lib/badline/frontend/screen.rb +1 -1
  93. data/lib/badline/frontend/screens.rb +40 -2
  94. data/lib/badline/frontend/teletype_pane.rb +152 -0
  95. data/lib/badline/frontend/timeline.rb +31 -2
  96. data/lib/badline/frontend.rb +3 -0
  97. data/lib/badline/input/joystick.rb +15 -2
  98. data/lib/badline/kim1/bus.rb +117 -0
  99. data/lib/badline/kim1/cpu.rb +25 -0
  100. data/lib/badline/kim1/host_terminal.rb +74 -0
  101. data/lib/badline/kim1/key_entry.rb +74 -0
  102. data/lib/badline/kim1/keyed_modules.rb +53 -0
  103. data/lib/badline/kim1/keyed_state.rb +133 -0
  104. data/lib/badline/kim1/panel.rb +176 -0
  105. data/lib/badline/kim1/rriots.rb +28 -0
  106. data/lib/badline/kim1/sound.rb +29 -0
  107. data/lib/badline/kim1/tape.rb +118 -0
  108. data/lib/badline/kim1/teletype.rb +193 -0
  109. data/lib/badline/kim1.rb +194 -0
  110. data/lib/badline/machine/ieee_drives.rb +94 -117
  111. data/lib/badline/machine/keyboard_buffer.rb +20 -3
  112. data/lib/badline/machine/line_sound.rb +122 -0
  113. data/lib/badline/machine.rb +49 -15
  114. data/lib/badline/media/extensions.rb +8 -4
  115. data/lib/badline/media/kim1_media.rb +150 -0
  116. data/lib/badline/media/pet_media.rb +3 -1
  117. data/lib/badline/media/true_drive.rb +52 -45
  118. data/lib/badline/media/vic20_media.rb +4 -2
  119. data/lib/badline/media.rb +16 -6
  120. data/lib/badline/memory.rb +2 -4
  121. data/lib/badline/options/family.rb +98 -8
  122. data/lib/badline/options/help.rb +44 -12
  123. data/lib/badline/options/table.rb +44 -12
  124. data/lib/badline/options/validation.rb +44 -8
  125. data/lib/badline/options.rb +27 -8
  126. data/lib/badline/pet/bus.rb +16 -2
  127. data/lib/badline/pet/keyboard_ports.rb +9 -2
  128. data/lib/badline/pet/keyed_state.rb +4 -0
  129. data/lib/badline/pet/model.rb +11 -2
  130. data/lib/badline/pet/rom_sets.rb +1 -0
  131. data/lib/badline/pet/sound.rb +5 -105
  132. data/lib/badline/pet/superpet/acia.rb +12 -0
  133. data/lib/badline/pet/superpet/board.rb +221 -0
  134. data/lib/badline/pet/superpet/dongle.rb +92 -0
  135. data/lib/badline/pet/superpet/keyed_modules.rb +63 -0
  136. data/lib/badline/pet/superpet/keyed_parts.rb +76 -0
  137. data/lib/badline/pet/superpet/m6809.rb +32 -0
  138. data/lib/badline/pet/superpet/waterloo_traps.rb +199 -0
  139. data/lib/badline/pet/superpet.rb +221 -0
  140. data/lib/badline/pet.rb +23 -3
  141. data/lib/badline/plus4/keyed_state.rb +2 -0
  142. data/lib/badline/plus4.rb +5 -0
  143. data/lib/badline/roms/1001/fdc-251257-02a.rom +0 -0
  144. data/lib/badline/roms/4040/dos-1.2.rom +0 -0
  145. data/lib/badline/roms/4040/dos-2.1-rev.rom +0 -0
  146. data/lib/badline/roms/4040/dos-2.1.rom +0 -0
  147. data/lib/badline/roms/4040/fdc-901466-02.rom +0 -0
  148. data/lib/badline/roms/4040/fdc-901466-04.rom +0 -0
  149. data/lib/badline/roms/9060/dos-3.0.rom +0 -0
  150. data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
  151. data/lib/badline/roms/README +176 -0
  152. data/lib/badline/roms/kim1/6530-002.rom +0 -0
  153. data/lib/badline/roms/kim1/6530-003.rom +0 -0
  154. data/lib/badline/roms/pet/characters-superpet.rom +0 -0
  155. data/lib/badline/roms/pet/superpet-a000.rom +0 -0
  156. data/lib/badline/roms/pet/superpet-c000.rom +0 -0
  157. data/lib/badline/roms/pet/superpet-e000-50hz.rom +0 -0
  158. data/lib/badline/roms/pet/superpet-e000.rom +0 -0
  159. data/lib/badline/snapshot/container.rb +8 -4
  160. data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
  161. data/lib/badline/snapshot/field_doc.rb +35 -10
  162. data/lib/badline/snapshot/image.rb +19 -7
  163. data/lib/badline/snapshot/kim1_setup.rb +28 -0
  164. data/lib/badline/snapshot/pet_setup.rb +27 -11
  165. data/lib/badline/snapshot/storage_setup.rb +4 -1
  166. data/lib/badline/snapshot/vic20_setup.rb +11 -7
  167. data/lib/badline/snapshot/vice/pet.rb +14 -7
  168. data/lib/badline/snapshot/vice/pet_cpu.rb +39 -5
  169. data/lib/badline/snapshot/vice/pet_crtc.rb +15 -2
  170. data/lib/badline/snapshot/vice/pet_dongle.rb +72 -0
  171. data/lib/badline/snapshot/vice/pet_drive8050.rb +188 -68
  172. data/lib/badline/snapshot/vice/pet_mem.rb +55 -5
  173. data/lib/badline/snapshot/vice/pet_restore.rb +40 -11
  174. data/lib/badline/snapshot/vice/vic20.rb +70 -0
  175. data/lib/badline/snapshot/vice/vic20_cpu.rb +61 -0
  176. data/lib/badline/snapshot/vice/vic20_mem.rb +85 -0
  177. data/lib/badline/snapshot/vice/vic20_restore.rb +124 -0
  178. data/lib/badline/snapshot/vice/vic20_vici.rb +200 -0
  179. data/lib/badline/snapshot/vice.rb +1 -0
  180. data/lib/badline/snapshot.rb +8 -4
  181. data/lib/badline/status.rb +30 -30
  182. data/lib/badline/storage/d67_image.rb +55 -0
  183. data/lib/badline/storage/d90_image.rb +180 -0
  184. data/lib/badline/storage/kim_tape.rb +254 -0
  185. data/lib/badline/storage/papertape.rb +90 -0
  186. data/lib/badline/storage.rb +4 -0
  187. data/lib/badline/support/kim1_checkpoint.rb +41 -0
  188. data/lib/badline/support/superpet_checkpoint.rb +35 -0
  189. data/lib/badline/version.rb +1 -1
  190. data/lib/badline/vic20/keyed_modules.rb +6 -3
  191. data/lib/badline/vic20/keyed_state.rb +6 -4
  192. data/lib/badline/vic20/port_wiring.rb +11 -0
  193. data/lib/badline/vic20.rb +30 -12
  194. data/lib/badline.rb +1 -0
  195. metadata +85 -1
@@ -5,14 +5,24 @@ module Badline
5
5
  module Vice
6
6
  module PET
7
7
  # PETMEM 1.3: the board, the RAM below $8000 in K and the 8096's
8
- # control register at $FFF0, then the RAM, the screen RAM, 1K or 2K
9
- # as the board has, or on the 8296 its 32K from $8000, an 8096's or
10
- # 8296's 64K board, and 24 bytes of xpet's own settings, which name
11
- # the 2001.
8
+ # control register at $FFF0, a byte for the SuperPET's latches, then
9
+ # the RAM, the screen RAM, 1K or 2K as the board has, or on the 8296
10
+ # its 32K from $8000, an 8096's, 8296's or SuperPET's 64K board, and
11
+ # 24 bytes of xpet's own settings, which name the 2001, and on the
12
+ # SuperPET the CPU that has the bus and the 6702.
12
13
  #
13
14
  # The board is 0 for the discrete video, 1 for a 40-column CRTC, 2
14
15
  # for an 80-column one, 3 for the SuperPET, 4 for the 8096 and 5 for
15
16
  # the 8296. xpet's ROMs aren't in the snapshot.
17
+ #
18
+ # The SuperPET's latch byte holds the bank at $9000 in its high
19
+ # nibble, and sets bit 0 while the bank RAM is there rather than the
20
+ # empty socket, and bit 2 always. Its settings hold 1 at offset 2
21
+ # while the 6809 has the bus. Both were inferred from xpet 3.10's
22
+ # snapshots after writes to the bank latch and $EFFE, and in both
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. The 6702 is
25
+ # in the settings from their fifth byte (Dongle).
16
26
  module PETMem
17
27
  NAME = "PETMEM"
18
28
  MAJOR = 1
@@ -30,6 +40,14 @@ module Badline
30
40
  SETTINGS = [0x00, 0x00, 0x00, 0x00, 0xd6, 0x01, 0x00, 0x20, 0x00, 0x04, 0x00, 0x40, 0x00, 0x00, 0x00,
31
41
  0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00].freeze
32
42
  SETTINGS_2001 = 0x03
43
+ # The SuperPET's settings byte for the CPU, and its value while the
44
+ # 6809 has the bus.
45
+ SETTINGS_CPU = 2
46
+ SETTINGS_M6809 = 0x01
47
+ # The SuperPET's latch byte: the bit always set, and the one for
48
+ # the bank RAM at $9000.
49
+ LATCHES = 0x04
50
+ BANK_RAM = 0x01
33
51
  EXPANSION = 0x10000
34
52
 
35
53
  module_function
@@ -37,6 +55,7 @@ module Badline
37
55
  def reads?(section) = section.major == MAJOR && section.minor == MINOR
38
56
 
39
57
  def board(model)
58
+ return SUPERPET if model.superpet?
40
59
  return CBM8296 if model.cbm8296?
41
60
  return CBM8096 if model.expansion
42
61
 
@@ -50,15 +69,46 @@ module Badline
50
69
  def export(machine)
51
70
  bus = machine.bus
52
71
  expansion = bus.expansion
72
+ superpet = machine.superpet?
53
73
  fields = FieldWriter.new.byte(board(machine.model)).byte(0).byte(machine.ram_kb)
54
- fields.byte(expansion ? expansion.control : 0).byte(HEADER_END)
74
+ fields.byte(expansion ? expansion.control : 0).byte(superpet ? latches(machine.board) : HEADER_END)
55
75
  fields.bytes(machine.ram.snapshot).bytes(bus.screen.cells)
56
76
  fields.bytes(expansion.cells) if expansion
77
+ fields.bytes(machine.board.cells) if superpet
57
78
  settings = SETTINGS.dup
58
79
  settings[1] = SETTINGS_2001 if machine.model.basic1?
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
59
85
  fields.bytes(settings).section(NAME, MAJOR, MINOR)
60
86
  end
61
87
 
88
+ def latches(board) = (board.bank << 4) | LATCHES | (board.socket.zero? ? BANK_RAM : 0)
89
+
90
+ # Whether a SuperPET's settings say the 6809 has the bus.
91
+ def m6809?(section)
92
+ data = section.data
93
+ offset = data.bytesize - SETTINGS.length + SETTINGS_CPU
94
+ offset >= 0 && data.getbyte(offset).to_i.anybits?(SETTINGS_M6809)
95
+ end
96
+
97
+ # A SuperPET's bank latch, socket bit, 64K board and 6702 by their
98
+ # BL-SUPERPET keys, in a keyed module's writer.
99
+ def import_superpet(section, out)
100
+ fields = FieldReader.new(section).skip(4)
101
+ latches = fields.byte
102
+ fields.skip(fields_before_board(section))
103
+ out.int("bank_latch", latches >> 4).int("socket", latches.anybits?(BANK_RAM) ? 0 : 1)
104
+ out.int("system_latch", 0).bytes("ram", fields.string(EXPANSION))
105
+ fields.skip(Dongle::AT)
106
+ Dongle.import(fields.bytes(Dongle::LENGTH), out)
107
+ end
108
+
109
+ # The RAM and the screen RAM before a SuperPET's board.
110
+ def fields_before_board(section) = (ram_kb(section) * 1024) + 0x800
111
+
62
112
  # The board's number in the header.
63
113
  def board_of(section) = FieldReader.new(section).byte
64
114
 
@@ -8,18 +8,23 @@ module Badline
8
8
  # reads become the PET's own keyed modules (Keyed), which it loads
9
9
  # as it loads its own snapshot. Whatever they don't set stays as the
10
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.
11
+ # disk device 8 serves stay as they are. A snapshot with a true drive
12
+ # puts one on the bus, with the disks badline's drive of that model
13
+ # had in it (Drive2031, Drive8050), and one without takes badline's
14
+ # off.
14
15
  module Restore
15
16
  # MAINCPU and PETMEM hold the machine itself, so a version badline
16
17
  # doesn't read fails the restore. The others are left out and
17
18
  # reported.
18
19
  READ = [MainCPU::NAME, PETMem::NAME, CRTC::NAME, "PIA1", "PIA2", VIA::NAME].freeze
20
+ # The SuperPET's too: its 6809 and its ACIA. A SuperPET's board
21
+ # comes from PETMEM.
22
+ SUPERPET_READ = [CPU6809::NAME, CBM2::ACIA::NAME].freeze
19
23
  # The host's media, which a restore keeps.
20
24
  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
25
+ # The disks in a true drive's drives, and a hard disk's controller
26
+ # with its image, which a restore keeps.
27
+ DISKS = %w[BL-D8-DISK BL-D8-DISK1 BL-D8-SASI].freeze
23
28
 
24
29
  module_function
25
30
 
@@ -53,7 +58,8 @@ module Badline
53
58
  applied = []
54
59
  ignored = []
55
60
  sections = []
56
- READ.each do |name|
61
+ names = read_names(computer)
62
+ names.each do |name|
57
63
  section = container[name]
58
64
  next if section.nil?
59
65
 
@@ -72,17 +78,20 @@ module Badline
72
78
  end
73
79
  sections += drive(container, applied, ignored, kept_disks(container, computer, disks))
74
80
  computer.load_modules(Keyed::Modules.new(modules(container, computer, applied) + sections))
75
- Report.new(applied:, ignored: ignored + left_out(container, applied))
81
+ Report.new(applied:, ignored: ignored + left_out(container, names, applied))
76
82
  end
77
83
 
84
+ def read_names(computer) = computer.superpet? ? READ + SUPERPET_READ : READ
85
+
78
86
  # 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.
87
+ # snapshot puts on the bus when it is of the same model, or of the
88
+ # same xpet type.
80
89
  def kept_disks(container, computer, disks)
81
90
  drive = computer.true_drive
82
91
  return [] if drive.nil?
83
92
 
84
93
  if Drive8050.present?(container)
85
- Drive8050.model(container) == drive.model_name ? disks : []
94
+ Drive8050.same_type?(Drive8050.model(container), drive.model_name) ? disks : []
86
95
  else
87
96
  drive.model_name == "2031" ? disks.first(1) : []
88
97
  end
@@ -113,6 +122,7 @@ module Badline
113
122
  end
114
123
 
115
124
  applied.concat(Drive8050::NAMES)
125
+ model = Drive8050.hard_disk_model(disks) if Drive8050.hard_disk?(model)
116
126
  Drive8050.import(container, Drive8050.reference(model), disks)
117
127
  end
118
128
 
@@ -134,6 +144,8 @@ module Badline
134
144
  when PETMem::NAME then PETMem.reads?(section)
135
145
  when CRTC::NAME then CRTC.reads?(section)
136
146
  when VIA::NAME then VIA.reads?(section)
147
+ when CPU6809::NAME then CPU6809.reads?(section)
148
+ when CBM2::ACIA::NAME then CBM2::ACIA.reads?(section)
137
149
  else PIAs.reads?(section)
138
150
  end
139
151
  end
@@ -160,9 +172,26 @@ module Badline
160
172
  if applied.include?(VIA::NAME)
161
173
  sections << Keyed.section("BL-VIA") { |out| VIA.import(container[VIA::NAME], out) }
162
174
  end
175
+ sections += superpet(container, applied) if computer.superpet?
163
176
  sections + video
164
177
  end
165
178
 
179
+ # The SuperPET's board, and its 6809 while it has the bus and its
180
+ # ACIA where the snapshot has them. The 6809 the 6502 has the bus
181
+ # from, and a module left out, stay as the machine has them at
182
+ # power-on.
183
+ def superpet(container, applied)
184
+ cycles = FieldReader.new(container[MainCPU::NAME]).qword
185
+ memory = container[PETMem::NAME]
186
+ sections = [Keyed.section("BL-SUPERPET") { |out| PETMem.import_superpet(memory, out) }]
187
+ if applied.include?(CPU6809::NAME) && PETMem.m6809?(memory)
188
+ sections << Keyed.section("BL-M6809") { |out| CPU6809.import(container[CPU6809::NAME], out, cycles) }
189
+ end
190
+ return sections unless applied.include?(CBM2::ACIA::NAME)
191
+
192
+ sections << Keyed.section("BL-ACIA") { |out| CBM2::ACIA.import(container[CBM2::ACIA::NAME], out, cycles) }
193
+ end
194
+
166
195
  def machine(out, container, computer, drive)
167
196
  out.text("family", "pet")
168
197
  PETSetup.of(computer).write_fields(out)
@@ -203,8 +232,8 @@ module Badline
203
232
  [Keyed.section("BL-CRTC") { |out| CRTC.copy(fields, out) }]
204
233
  end
205
234
 
206
- def left_out(container, applied)
207
- container.sections.reject { |section| READ.include?(section.name) || applied.include?(section.name) }
235
+ def left_out(container, names, applied)
236
+ container.sections.reject { |section| names.include?(section.name) || applied.include?(section.name) }
208
237
  .map { |section| "#{section}: badline doesn't read this module, left out" }
209
238
  end
210
239
  end
@@ -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