badline 0.16.1 → 0.18.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 (256) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +213 -0
  3. data/CLAUDE.md +11 -7
  4. data/CONTRIBUTING.md +7 -1
  5. data/README.md +265 -26
  6. data/doc/media.md +5 -2
  7. data/doc/pinned-behaviour.md +189 -0
  8. data/doc/snapshot-format.md +616 -74
  9. data/doc/snapshots.md +192 -24
  10. data/exe/badline-ruby +5 -0
  11. data/lib/badline/audio/terminal.rb +34 -2
  12. data/lib/badline/c128/keyed_state.rb +2 -0
  13. data/lib/badline/c128.rb +1 -0
  14. data/lib/badline/c64/keyed_state.rb +2 -0
  15. data/lib/badline/c64.rb +1 -0
  16. data/lib/badline/cbm2/acia.rb +10 -0
  17. data/lib/badline/cbm2/bus.rb +263 -0
  18. data/lib/badline/cbm2/cpu.rb +58 -0
  19. data/lib/badline/cbm2/crtc_video.rb +133 -0
  20. data/lib/badline/cbm2/ieee_drive.rb +18 -0
  21. data/lib/badline/cbm2/ieee_layout.rb +69 -0
  22. data/lib/badline/cbm2/ieee_ports.rb +104 -0
  23. data/lib/badline/cbm2/ieee_traps.rb +81 -0
  24. data/lib/badline/cbm2/keyboard_ports.rb +60 -0
  25. data/lib/badline/cbm2/keyed_modules.rb +67 -0
  26. data/lib/badline/cbm2/keyed_state.rb +95 -0
  27. data/lib/badline/cbm2/model.rb +44 -0
  28. data/lib/badline/cbm2/p500/control_ports.rb +80 -0
  29. data/lib/badline/cbm2/p500/ieee_layout.rb +41 -0
  30. data/lib/badline/cbm2/p500/keyed_state.rb +85 -0
  31. data/lib/badline/cbm2/p500/model.rb +39 -0
  32. data/lib/badline/cbm2/p500/pages.rb +57 -0
  33. data/lib/badline/cbm2/p500.rb +264 -0
  34. data/lib/badline/cbm2/sid_page.rb +19 -0
  35. data/lib/badline/cbm2.rb +270 -0
  36. data/lib/badline/{plus4 → chips}/acia/frames.rb +1 -1
  37. data/lib/badline/chips/acia/keyed_state.rb +47 -0
  38. data/lib/badline/chips/acia.rb +307 -0
  39. data/lib/badline/chips/cia/timer.rb +2 -2
  40. data/lib/badline/chips/cpu/instruction_set.rb +1 -1
  41. data/lib/badline/chips/crtc.rb +3 -2
  42. data/lib/badline/chips/m6809/arithmetic.rb +140 -0
  43. data/lib/badline/chips/m6809/bus_steps.rb +223 -0
  44. data/lib/badline/chips/m6809/core.rb +149 -0
  45. data/lib/badline/chips/m6809/indexed.rb +82 -0
  46. data/lib/badline/chips/m6809/interrupts.rb +159 -0
  47. data/lib/badline/chips/m6809/keyed_state.rb +147 -0
  48. data/lib/badline/chips/m6809/opcodes.rb +159 -0
  49. data/lib/badline/chips/m6809/operations.rb +258 -0
  50. data/lib/badline/chips/m6809/plans.rb +143 -0
  51. data/lib/badline/chips/m6809/registers.rb +82 -0
  52. data/lib/badline/chips/m6809/stack.rb +140 -0
  53. data/lib/badline/chips/m6809.rb +13 -0
  54. data/lib/badline/chips/riot/fast_forward.rb +72 -0
  55. data/lib/badline/chips/riot/keyed_parts.rb +69 -0
  56. data/lib/badline/chips/riot/timer.rb +91 -0
  57. data/lib/badline/chips/riot.rb +162 -0
  58. data/lib/badline/chips/rriot/keyed_state.rb +30 -0
  59. data/lib/badline/chips/rriot.rb +134 -0
  60. data/lib/badline/chips/sid/filter.rb +4 -4
  61. data/lib/badline/chips/sid/waveform.rb +6 -6
  62. data/lib/badline/chips/tpi/keyed_state.rb +38 -0
  63. data/lib/badline/chips/tpi.rb +233 -0
  64. data/lib/badline/chips/vic/bank.rb +60 -5
  65. data/lib/badline/chips/vic/sprite.rb +4 -3
  66. data/lib/badline/chips/vic_i/keyed_parts.rb +7 -2
  67. data/lib/badline/chips/vic_i.rb +84 -21
  68. data/lib/badline/data_path.rb +3 -3
  69. data/lib/badline/drives/datasette/keyed_state.rb +9 -1
  70. data/lib/badline/drives/datasette.rb +27 -3
  71. data/lib/badline/drives/ieee_models.rb +54 -0
  72. data/lib/badline/drives/model1001.rb +37 -0
  73. data/lib/badline/drives/model2031.rb +6 -0
  74. data/lib/badline/drives/model4040/disk.rb +62 -0
  75. data/lib/badline/drives/model4040/ip_bus.rb +46 -0
  76. data/lib/badline/drives/model4040/keyed_state.rb +57 -0
  77. data/lib/badline/drives/model4040/mechanism.rb +192 -0
  78. data/lib/badline/drives/model4040/parts.rb +20 -0
  79. data/lib/badline/drives/model4040.rb +179 -0
  80. data/lib/badline/drives/model8050/board.rb +77 -0
  81. data/lib/badline/drives/model8050/disk.rb +163 -0
  82. data/lib/badline/drives/model8050/dos_board.rb +91 -0
  83. data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
  84. data/lib/badline/drives/model8050/idle.rb +181 -0
  85. data/lib/badline/drives/model8050/ieee_port.rb +147 -0
  86. data/lib/badline/drives/model8050/ip_bus.rb +93 -0
  87. data/lib/badline/drives/model8050/keyed_state.rb +163 -0
  88. data/lib/badline/drives/model8050/mechanism.rb +230 -0
  89. data/lib/badline/drives/model8050/parts.rb +37 -0
  90. data/lib/badline/drives/model8050.rb +131 -0
  91. data/lib/badline/drives/model9060/idle.rb +149 -0
  92. data/lib/badline/drives/model9060/keyed_state.rb +123 -0
  93. data/lib/badline/drives/model9060/parts.rb +98 -0
  94. data/lib/badline/drives/model9060/sasi_bus.rb +88 -0
  95. data/lib/badline/drives/model9060/sasi_commands.rb +168 -0
  96. data/lib/badline/drives/model9060/sasi_target.rb +161 -0
  97. data/lib/badline/drives/model9060.rb +195 -0
  98. data/lib/badline/frontend/app.rb +18 -3
  99. data/lib/badline/frontend/boot.rb +4 -3
  100. data/lib/badline/frontend/controls.rb +107 -15
  101. data/lib/badline/frontend/gamepads.rb +3 -3
  102. data/lib/badline/frontend/keys.rb +97 -2
  103. data/lib/badline/frontend/kim1_panel.rb +208 -0
  104. data/lib/badline/frontend/media_slots.rb +8 -1
  105. data/lib/badline/frontend/menu/drive_types.rb +56 -0
  106. data/lib/badline/frontend/menu/menu_dialogs.rb +61 -14
  107. data/lib/badline/frontend/menu/menu_media.rb +47 -14
  108. data/lib/badline/frontend/menu/menu_pages.rb +113 -4
  109. data/lib/badline/frontend/menu/menu_ports.rb +77 -4
  110. data/lib/badline/frontend/menu/pause_menu.rb +6 -2
  111. data/lib/badline/frontend/pet_keyboard.rb +45 -7
  112. data/lib/badline/frontend/screen.rb +1 -1
  113. data/lib/badline/frontend/screens.rb +40 -2
  114. data/lib/badline/frontend/teletype_pane.rb +152 -0
  115. data/lib/badline/frontend/timeline.rb +49 -11
  116. data/lib/badline/frontend.rb +3 -0
  117. data/lib/badline/kernal_trap/dos/disk_commands.rb +80 -0
  118. data/lib/badline/kernal_trap/dos/status.rb +3 -0
  119. data/lib/badline/kernal_trap/dos.rb +18 -5
  120. data/lib/badline/kernal_trap/ieee.rb +4 -2
  121. data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
  122. data/lib/badline/kim1/bus.rb +117 -0
  123. data/lib/badline/kim1/cpu.rb +25 -0
  124. data/lib/badline/kim1/host_terminal.rb +74 -0
  125. data/lib/badline/kim1/key_entry.rb +74 -0
  126. data/lib/badline/kim1/keyed_modules.rb +53 -0
  127. data/lib/badline/kim1/keyed_state.rb +133 -0
  128. data/lib/badline/kim1/panel.rb +176 -0
  129. data/lib/badline/kim1/rriots.rb +28 -0
  130. data/lib/badline/kim1/sound.rb +29 -0
  131. data/lib/badline/kim1/tape.rb +116 -0
  132. data/lib/badline/kim1/teletype.rb +193 -0
  133. data/lib/badline/kim1.rb +190 -0
  134. data/lib/badline/machine/ieee_drives.rb +171 -0
  135. data/lib/badline/machine/keyboard_buffer.rb +20 -3
  136. data/lib/badline/machine/line_sound.rb +122 -0
  137. data/lib/badline/machine/settling.rb +4 -0
  138. data/lib/badline/machine.rb +62 -14
  139. data/lib/badline/media/cbm2_media.rb +29 -0
  140. data/lib/badline/media/extensions.rb +8 -4
  141. data/lib/badline/media/kim1_media.rb +150 -0
  142. data/lib/badline/media/pet_media.rb +3 -1
  143. data/lib/badline/media/program.rb +6 -3
  144. data/lib/badline/media/true_drive.rb +51 -25
  145. data/lib/badline/media.rb +23 -8
  146. data/lib/badline/options/family.rb +108 -6
  147. data/lib/badline/options/help.rb +58 -12
  148. data/lib/badline/options/table.rb +60 -14
  149. data/lib/badline/options/validation.rb +71 -11
  150. data/lib/badline/options.rb +46 -13
  151. data/lib/badline/pet/bus.rb +16 -2
  152. data/lib/badline/pet/cassettes.rb +22 -1
  153. data/lib/badline/pet/ieee_drive.rb +5 -64
  154. data/lib/badline/pet/ieee_ports.rb +7 -0
  155. data/lib/badline/pet/keyboard_ports.rb +9 -2
  156. data/lib/badline/pet/keyed_state.rb +4 -0
  157. data/lib/badline/pet/model.rb +11 -2
  158. data/lib/badline/pet/rom_sets.rb +1 -0
  159. data/lib/badline/pet/sound.rb +5 -105
  160. data/lib/badline/pet/superpet/acia.rb +12 -0
  161. data/lib/badline/pet/superpet/board.rb +221 -0
  162. data/lib/badline/pet/superpet/dongle.rb +81 -0
  163. data/lib/badline/pet/superpet/keyed_modules.rb +63 -0
  164. data/lib/badline/pet/superpet/keyed_parts.rb +76 -0
  165. data/lib/badline/pet/superpet/m6809.rb +32 -0
  166. data/lib/badline/pet/superpet/waterloo_traps.rb +199 -0
  167. data/lib/badline/pet/superpet.rb +216 -0
  168. data/lib/badline/pet.rb +33 -6
  169. data/lib/badline/plus4/acia.rb +4 -298
  170. data/lib/badline/plus4/keyed_parts.rb +0 -44
  171. data/lib/badline/plus4/keyed_state.rb +2 -0
  172. data/lib/badline/plus4.rb +2 -0
  173. data/lib/badline/roms/1001/fdc-251257-02a.rom +0 -0
  174. data/lib/badline/roms/4040/dos-1.2.rom +0 -0
  175. data/lib/badline/roms/4040/dos-2.1-rev.rom +0 -0
  176. data/lib/badline/roms/4040/dos-2.1.rom +0 -0
  177. data/lib/badline/roms/4040/fdc-901466-02.rom +0 -0
  178. data/lib/badline/roms/4040/fdc-901466-04.rom +0 -0
  179. data/lib/badline/roms/8050/dos-2.7.rom +0 -0
  180. data/lib/badline/roms/8050/fdc-901869-01.rom +0 -0
  181. data/lib/badline/roms/8050/fdc-901885-04.rom +0 -0
  182. data/lib/badline/roms/9060/dos-3.0.rom +0 -0
  183. data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
  184. data/lib/badline/roms/README +277 -0
  185. data/lib/badline/roms/cbm2/basic128-02b.rom +0 -0
  186. data/lib/badline/roms/cbm2/basic128.rom +0 -0
  187. data/lib/badline/roms/cbm2/basic256.rom +0 -0
  188. data/lib/badline/roms/cbm2/characters-600.rom +0 -0
  189. data/lib/badline/roms/cbm2/characters-700.rom +0 -0
  190. data/lib/badline/roms/cbm2/kernal-03b.rom +0 -0
  191. data/lib/badline/roms/cbm2/kernal-04a.rom +0 -0
  192. data/lib/badline/roms/cbm2/p500-basic.rom +0 -0
  193. data/lib/badline/roms/cbm2/p500-kernal.rom +0 -0
  194. data/lib/badline/roms/kim1/6530-002.rom +0 -0
  195. data/lib/badline/roms/kim1/6530-003.rom +0 -0
  196. data/lib/badline/roms/pet/characters-superpet.rom +0 -0
  197. data/lib/badline/roms/pet/superpet-a000.rom +0 -0
  198. data/lib/badline/roms/pet/superpet-c000.rom +0 -0
  199. data/lib/badline/roms/pet/superpet-e000-50hz.rom +0 -0
  200. data/lib/badline/roms/pet/superpet-e000.rom +0 -0
  201. data/lib/badline/snapshot/cbm2_setup.rb +53 -0
  202. data/lib/badline/snapshot/container.rb +9 -2
  203. data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
  204. data/lib/badline/snapshot/field_doc.rb +59 -3
  205. data/lib/badline/snapshot/image.rb +65 -9
  206. data/lib/badline/snapshot/kim1_setup.rb +28 -0
  207. data/lib/badline/snapshot/pet_setup.rb +27 -11
  208. data/lib/badline/snapshot/storage_setup.rb +4 -1
  209. data/lib/badline/snapshot/vic20_setup.rb +11 -7
  210. data/lib/badline/snapshot/vice/cbm2.rb +93 -0
  211. data/lib/badline/snapshot/vice/cbm2_acia.rb +75 -0
  212. data/lib/badline/snapshot/vice/cbm2_crtc.rb +127 -0
  213. data/lib/badline/snapshot/vice/cbm2_mains.rb +49 -0
  214. data/lib/badline/snapshot/vice/cbm2_mem.rb +75 -0
  215. data/lib/badline/snapshot/vice/cbm2_restore.rb +204 -0
  216. data/lib/badline/snapshot/vice/cbm2_tpi.rb +58 -0
  217. data/lib/badline/snapshot/vice/cias.rb +13 -9
  218. data/lib/badline/snapshot/vice/p500.rb +96 -0
  219. data/lib/badline/snapshot/vice/p500_restore.rb +130 -0
  220. data/lib/badline/snapshot/vice/p500_vicii.rb +166 -0
  221. data/lib/badline/snapshot/vice/pet.rb +20 -10
  222. data/lib/badline/snapshot/vice/pet_cpu.rb +42 -5
  223. data/lib/badline/snapshot/vice/pet_crtc.rb +25 -17
  224. data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
  225. data/lib/badline/snapshot/vice/pet_drive8050.rb +336 -0
  226. data/lib/badline/snapshot/vice/pet_mem.rb +49 -5
  227. data/lib/badline/snapshot/vice/pet_restore.rb +100 -7
  228. data/lib/badline/snapshot/vice/pet_via.rb +6 -5
  229. data/lib/badline/snapshot/vice/sid_registers.rb +2 -1
  230. data/lib/badline/snapshot/vice/vicii_import.rb +10 -6
  231. data/lib/badline/snapshot/vice.rb +3 -0
  232. data/lib/badline/snapshot.rb +15 -3
  233. data/lib/badline/status.rb +30 -30
  234. data/lib/badline/storage/crt_file.rb +7 -1
  235. data/lib/badline/storage/d64_image.rb +7 -0
  236. data/lib/badline/storage/d67_image.rb +55 -0
  237. data/lib/badline/storage/d80_image.rb +17 -1
  238. data/lib/badline/storage/d81_image.rb +14 -0
  239. data/lib/badline/storage/d90_image.rb +180 -0
  240. data/lib/badline/storage/disk_image/bam.rb +18 -3
  241. data/lib/badline/storage/disk_image/formatting.rb +69 -0
  242. data/lib/badline/storage/disk_image/writing.rb +37 -2
  243. data/lib/badline/storage/disk_image.rb +2 -0
  244. data/lib/badline/storage/kim_tape.rb +254 -0
  245. data/lib/badline/storage/papertape.rb +90 -0
  246. data/lib/badline/storage.rb +5 -0
  247. data/lib/badline/support/cbm2_checkpoint.rb +48 -0
  248. data/lib/badline/support/kim1_checkpoint.rb +41 -0
  249. data/lib/badline/support/p500_checkpoint.rb +35 -0
  250. data/lib/badline/support/superpet_checkpoint.rb +35 -0
  251. data/lib/badline/version.rb +1 -1
  252. data/lib/badline/vic20/keyed_modules.rb +6 -3
  253. data/lib/badline/vic20/keyed_state.rb +6 -4
  254. data/lib/badline/vic20.rb +26 -12
  255. data/lib/badline.rb +2 -0
  256. metadata +148 -2
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ # How a KIM-1 was built: its RAM, one of KIM1::Bus::RAM_CONFIGURATIONS
6
+ # by name, the argument KIM1.new takes. The ROMs are the board's own.
7
+ KIM1Setup = Data.define(:ram)
8
+
9
+ class KIM1Setup
10
+ RAM = KIM1::Bus::RAM_CONFIGURATIONS.map(&:to_s).freeze
11
+
12
+ # The setup of `machine`.
13
+ def self.of(machine) = new(ram: machine.bus.ram_configuration.to_s)
14
+
15
+ # The RAM by name, in a keyed snapshot module.
16
+ def write_fields(out)
17
+ out.text("ram", ram)
18
+ end
19
+
20
+ def self.read_fields(input) = new(ram: input.choice("ram", RAM, "1k"))
21
+
22
+ # A machine built this way, at power-on.
23
+ def build = KIM1.new(ram: ram.to_sym)
24
+
25
+ def to_s = "KIM-1 with #{ram == 'expanded' ? 'expanded' : ram.upcase} RAM"
26
+ end
27
+ end
28
+ end
@@ -5,9 +5,11 @@ module Badline
5
5
  # How a PET was built: its model's name, one of PET::Model::ALL, its
6
6
  # RAM below $8000 in K and its editor's frame rate, the arguments
7
7
  # PET.new takes, the ROMs it runs by file: BASIC, the editor, the
8
- # KERNAL and the characters, and the 8296 board's jumpers, by name and
9
- # comma-separated.
10
- PETSetup = Data.define(:model, :ram_kb, :refresh_hz, :basic, :editor, :kernal, :characters, :jumpers)
8
+ # KERNAL and the characters, the 8296 board's jumpers, by name and
9
+ # comma-separated, and the SuperPET's CPU and RAM switches, as
10
+ # PET::SuperPET::Board names their positions, empty on the others.
11
+ PETSetup = Data.define(:model, :ram_kb, :refresh_hz, :basic, :editor, :kernal, :characters, :jumpers,
12
+ :cpu_switch, :ram_switch)
11
13
 
12
14
  class PETSetup
13
15
  MODELS = PET::Model::ALL.map(&:name).freeze
@@ -20,20 +22,24 @@ module Badline
20
22
  refresh_hz = machine.refresh_hz
21
23
  new(model: model.name, ram_kb: machine.ram.length >> 10, refresh_hz:, basic: model.basic,
22
24
  editor: model.editor(refresh_hz), kernal: model.kernal, characters: model.characters,
23
- jumpers: machine.jumpers.join(","))
25
+ jumpers: machine.jumpers.join(","), cpu_switch: machine.superpet? ? machine.board.cpu_switch : "",
26
+ ram_switch: machine.superpet? ? machine.board.ram_switch : "")
24
27
  end
25
28
 
26
- # The model by name, the RAM, the frame rate, the ROMs and the
27
- # jumpers where a board has any, in a keyed snapshot module.
29
+ # The model by name, the RAM, the frame rate, the ROMs, the jumpers
30
+ # where a board has any and the switches where it has them, in a
31
+ # keyed snapshot module.
28
32
  def write_fields(out)
29
33
  out.text("model", model).int("ram_kb", ram_kb).int("refresh_hz", refresh_hz)
30
34
  out.text("rom.basic", basic).text("rom.editor", editor).text("rom.kernal", kernal)
31
35
  out.text("rom.characters", characters)
32
36
  out.text("jumpers", jumpers) unless jumpers.empty?
37
+ out.text("cpu_switch", cpu_switch).text("ram_switch", ram_switch) unless cpu_switch.empty?
33
38
  end
34
39
 
35
- # A ROM the snapshot doesn't name is the model's own, and a board
36
- # without jumpers named has none fitted.
40
+ # A ROM the snapshot doesn't name is the model's own, a board without
41
+ # jumpers named has none fitted, and a SuperPET without its switches
42
+ # named has them at 6502 and R/W.
37
43
  def self.read_fields(input)
38
44
  profile = PET::Model.named(input.choice("model", MODELS, PET::Model::DEFAULT))
39
45
  ram_kb = input.int("ram_kb", profile.ram_kb)
@@ -47,18 +53,27 @@ module Badline
47
53
  end
48
54
 
49
55
  refresh_hz = 60 unless profile.crtc?
56
+ superpet = profile.superpet?
50
57
  new(model: profile.name, ram_kb:, refresh_hz:, basic: input.text("rom.basic", profile.basic),
51
58
  editor: input.text("rom.editor", profile.editor(refresh_hz)),
52
59
  kernal: input.text("rom.kernal", profile.kernal),
53
- characters: input.text("rom.characters", profile.characters), jumpers: input.text("jumpers", ""))
60
+ characters: input.text("rom.characters", profile.characters), jumpers: input.text("jumpers", ""),
61
+ cpu_switch: superpet ? input.choice("cpu_switch", PET::SuperPET::Board::CPU_SWITCH, "6502") : "",
62
+ ram_switch: superpet ? input.choice("ram_switch", PET::SuperPET::Board::RAM_SWITCH, "rw") : "")
54
63
  end
55
64
 
56
65
  # A machine built this way, at power-on, with the BASIC, editor and
57
66
  # character ROMs PET::ROMSets names for its ROMs.
58
67
  def build
59
68
  version = PET::ROMSets::BASICS.find { |_, roms| roms.first == basic }&.first
60
- PET.new(model:, ram_kb:, refresh_hz:, basic: version, editor: PET::ROMSets::EDITORS.key(editor),
61
- characters: fitted_characters, jumpers: jumpers.split(","))
69
+ editor_name = PET::ROMSets::EDITORS.key(editor)
70
+ unless cpu_switch.empty?
71
+ return PET::SuperPET.new(refresh_hz:, basic: version, editor: editor_name, cpu: cpu_switch,
72
+ bank_ram: ram_switch)
73
+ end
74
+
75
+ PET.new(model:, ram_kb:, refresh_hz:, basic: version, editor: editor_name, characters: fitted_characters,
76
+ jumpers: jumpers.split(","))
62
77
  rescue ArgumentError => e
63
78
  raise FormatError, "the snapshot is of a #{self}, which badline doesn't build: #{e.message}"
64
79
  end
@@ -68,6 +83,7 @@ module Badline
68
83
  def to_s
69
84
  text = "#{model} with #{ram_kb}K at #{refresh_hz} Hz"
70
85
  text = "#{text} jumpered #{jumpers}" unless jumpers.empty?
86
+ text = "#{text} with its CPU switch at #{cpu_switch} and RAM switch at #{ram_switch}" unless cpu_switch.empty?
71
87
  roms = [basic, editor, kernal, characters]
72
88
  profile = PET::Model.named(model)
73
89
  stock = [profile.basic, profile.editor(refresh_hz), profile.kernal, profile.characters]
@@ -13,6 +13,7 @@ module Badline
13
13
  T64_ARCHIVE = 4
14
14
  D80 = 5
15
15
  D82 = 6
16
+ D67 = 7
16
17
 
17
18
  module_function
18
19
 
@@ -22,6 +23,7 @@ module Badline
22
23
  return D81 if image.is_a?(Storage::D81Image)
23
24
  return D82 if image.is_a?(Storage::D82Image)
24
25
  return D80 if image.is_a?(Storage::D80Image)
26
+ return D67 if image.is_a?(Storage::D67Image)
25
27
 
26
28
  raise ArgumentError, "#{image.class} can't go in a snapshot"
27
29
  end
@@ -41,7 +43,7 @@ module Badline
41
43
  end
42
44
  end
43
45
 
44
- KIND_NAMES = %w[directory d64 d71 d81 t64 d80 d82].freeze
46
+ KIND_NAMES = %w[directory d64 d71 d81 t64 d80 d82 d67].freeze
45
47
 
46
48
  # Opens the storage write_fields wrote: the same directory by its
47
49
  # path, an archive with the bytes it held, or a disk image with the
@@ -61,6 +63,7 @@ module Badline
61
63
  when D71 then Storage::D71Image.new(path, read_only:, bytes:)
62
64
  when D80 then Storage::D80Image.new(path, read_only:, bytes:)
63
65
  when D82 then Storage::D82Image.new(path, read_only:, bytes:)
66
+ when D67 then Storage::D67Image.new(path, read_only:, bytes:)
64
67
  else Storage::D81Image.new(path, read_only:, bytes:)
65
68
  end
66
69
  end
@@ -3,26 +3,30 @@
3
3
  module Badline
4
4
  module Snapshot
5
5
  # How a VIC-20 was built: its RAM expansion, a key of
6
- # Vic20::Bus::RAM_CONFIGURATIONS, the argument Vic20.new takes.
7
- Vic20Setup = Data.define(:ram)
6
+ # Vic20::Bus::RAM_CONFIGURATIONS, and its model, one of Vic20::MODELS,
7
+ # the arguments Vic20.new takes.
8
+ Vic20Setup = Data.define(:ram, :model)
8
9
 
9
10
  class Vic20Setup
10
11
  RAM_CONFIGURATIONS = Vic20::Bus::RAM_CONFIGURATIONS.keys.freeze
11
12
 
12
- # The RAM expansion by name, in a keyed snapshot module.
13
+ # The RAM expansion and the model by name, in a keyed snapshot module.
14
+ # A snapshot without a model is of a PAL machine, the only one badline
15
+ # built before the NTSC one.
13
16
  def write_fields(out)
14
- out.text("ram", ram.to_s)
17
+ out.text("ram", ram.to_s).text("model", model)
15
18
  end
16
19
 
17
20
  def self.read_fields(input)
18
21
  text = input.choice("ram", RAM_CONFIGURATIONS.map(&:to_s), "unexpanded")
19
- new(ram: RAM_CONFIGURATIONS.find { |name| name.to_s == text } || :unexpanded)
22
+ new(ram: RAM_CONFIGURATIONS.find { |name| name.to_s == text } || :unexpanded,
23
+ model: input.choice("model", Vic20::MODELS, "pal"))
20
24
  end
21
25
 
22
26
  # A machine built this way, at power-on.
23
- def build = Vic20.new(ram:)
27
+ def build = Vic20.new(ram:, model:)
24
28
 
25
- def to_s = ram.to_s
29
+ def to_s = model == "pal" ? ram.to_s : "#{ram} #{model}"
26
30
  end
27
31
  end
28
32
  end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/snapshot/vice/cbm2_mem"
4
+ require "badline/snapshot/vice/cbm2_tpi"
5
+ require "badline/snapshot/vice/cbm2_acia"
6
+ require "badline/snapshot/vice/cbm2_crtc"
7
+ require "badline/snapshot/vice/cbm2_mains"
8
+ require "badline/snapshot/vice/cbm2_restore"
9
+
10
+ module Badline
11
+ module Snapshot
12
+ module Vice
13
+ # xcbm2's modules for the CBM-II's B series: the CPU, the RAM, the
14
+ # CRTC, the CIA, both TPIs, the ACIA and the SID, written from
15
+ # badline's machine and read into it. Each module's file says what
16
+ # maps and what is approximated. The CPU's, the CIA's and the SID's
17
+ # are the PET's and the C64's. They go through the machine's own
18
+ # keyed fields (Keyed), which a save writes and a load reads, but for
19
+ # the CIA and the SID, which go through the chips as the C64's do.
20
+ module CBM2
21
+ # The models badline builds by the RAM board's size in K and
22
+ # whether the model line is a 700's.
23
+ MODELS = { [128, false] => "610", [256, false] => "620", [128, true] => "710", [256, true] => "720" }.freeze
24
+ # The modules xcbm2 resets the machine without, each as xcbm2 leaves
25
+ # them with nothing attached: no cartridge, no file system drive
26
+ # open, the cassette port on with a datasette in it, and nothing in
27
+ # the user port.
28
+ PERIPHERALS = [["CBM2CART", 0, 1, [0x00]], ["FSDRIVE", 0, 0, Array.new(258, 0)],
29
+ ["TAPEPORT", 1, 0, [0x01, 0x01]], ["USERPORT", 1, 0, [0x01, 0x00]]].freeze
30
+
31
+ module_function
32
+
33
+ # The modules xcbm2 3.10 reads, in its order, describing `machine`,
34
+ # which Machine::Settling has brought between instructions, from
35
+ # its keyed modules, `sections`.
36
+ def export(machine, sections)
37
+ readers = {}
38
+ sections.each { |section| readers[section.name] = Keyed::Reader.parse(section) }
39
+ machine.sid.catch_up!
40
+ cart, *rest = PERIPHERALS.map do |name, major, minor, bytes|
41
+ FieldWriter.new.bytes(bytes).section(name, major, minor)
42
+ end
43
+ [PET::MainCPU.write(machine, machine.tpi1.irq? ? 1 : 0), CBM2Mem.export(machine), cart,
44
+ CRTC.export(machine, reader(readers, "BL-CRTC"), reader(readers, "BL-VIDEO")),
45
+ CIAs.section(machine.cia, "CIA1"),
46
+ *TPI::NAMES.map { |name| TPI.export(name, reader(readers, "BL-#{name}")) },
47
+ ACIA.export(reader(readers, "BL-ACIA"), machine.cycles),
48
+ SIDRegisters.export(machine), SIDExtended.export(machine), *drives(machine, readers), *rest]
49
+ end
50
+
51
+ # The true drive's modules, as xpet's (PET::Drive2031,
52
+ # PET::Drive8050), or with no true drive, DRIVE8 to DRIVE11 as
53
+ # xcbm2 and xcbm5x0 save no drive.
54
+ def drives(machine, readers)
55
+ return no_drives unless machine.true_drive
56
+
57
+ PET::Drive2031.export(machine, readers) + PET::Drive8050.export(machine, readers)
58
+ end
59
+
60
+ def no_drives = PET::Drive2031::UNITS.map { |name| PET::Drive2031.unit(PET::Drive2031::NONE, name) }
61
+
62
+ def reader(readers, name) = readers[name] || Keyed::Reader.missing(name)
63
+
64
+ # How the machine xcbm2 saved was built: the model from the RAM
65
+ # board's size and the model line, and the mains from the model
66
+ # line too, 60 Hz on a 600 whose line says so and 50 Hz otherwise:
67
+ # xcbm2 has no 700 at 60 Hz. xcbm2 doesn't save its ROMs, so the
68
+ # KERNAL is 901244-04a, xcbm2's own.
69
+ def setup(container)
70
+ memory = memory(container)
71
+ ram_kb = CBM2Mem.ram_kb(memory)
72
+ line = CBM2Mem.line(memory)
73
+ unless CBM2Mem.mapped(memory).zero?
74
+ raise FormatError, "#{memory}: RAM in segment 15 beyond the B series', which badline doesn't build"
75
+ end
76
+
77
+ model = MODELS[[ram_kb, line == CBM2Mem::M700]]
78
+ raise FormatError, "#{memory}: a CBM-II with #{ram_kb}K of RAM, which badline doesn't build" if model.nil?
79
+
80
+ CBM2Setup.new(model:, kernal: "04a", mains_hz: line == CBM2Mem::M600_60HZ ? 60 : 50, refresh: true)
81
+ end
82
+
83
+ def memory(container)
84
+ section = container[CBM2Mem::NAME]
85
+ raise FormatError, "the snapshot has no #{CBM2Mem::NAME} module" if section.nil?
86
+ raise FormatError, "#{section}: badline doesn't read this version" unless CBM2Mem.reads?(section)
87
+
88
+ section
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Vice
6
+ module CBM2
7
+ # Acia1 1.1, the 6551: the transmitter's and the receiver's data
8
+ # registers, the status, the command and control registers, the
9
+ # transmitter's state (0 idle, 2 a byte waiting), then four clocks:
10
+ # the cycles to the transmitter's next character end, a clock
11
+ # xcbm2 leaves 0, the cycles to its next receiver poll and another
12
+ # 0.
13
+ #
14
+ # Written: the registers, the status as a read finds it, and while
15
+ # the transmitter is on, the cycles to its character's end in both
16
+ # clocks. Read: the registers, the receiver full, the transmitter
17
+ # empty, the interrupt and the errors from the status, and the
18
+ # transmitter's character end from its clock, or one a character's
19
+ # time on when the clock has run out. Nothing is plugged
20
+ # in, in either, and a character being shifted out is lost.
21
+ module ACIA
22
+ NAME = "Acia1"
23
+ MAJOR = 1
24
+ MINOR = 1
25
+ WAITING = 2
26
+ ERRORS = 0x07
27
+ RECEIVER_FULL = 0x08
28
+ TRANSMITTER_EMPTY = 0x10
29
+ IRQ = 0x80
30
+
31
+ module_function
32
+
33
+ def reads?(section) = section.major == MAJOR && section.minor == MINOR
34
+
35
+ # The module from the ACIA's BL-ACIA fields in `input`, for a
36
+ # machine at `cycles`.
37
+ def export(input, cycles)
38
+ empty = input.bool("transmitter_empty", true)
39
+ status = input.int("errors", 0) | Chips::ACIA::DCD | Chips::ACIA::DSR
40
+ status |= RECEIVER_FULL if input.bool("receiver_full", false)
41
+ status |= TRANSMITTER_EMPTY if empty
42
+ status |= IRQ if input.bool("irq", false)
43
+ at = input.int("transmit_at", Chips::ACIA::NEVER)
44
+ due = at == Chips::ACIA::NEVER ? 0 : [at - cycles, 0].max
45
+ FieldWriter.new.byte(input.int("transmit_data", 0)).byte(input.int("receive_data", 0)).byte(status)
46
+ .byte(input.int("command", 0)).byte(input.int("control", 0)).byte(empty ? 0 : WAITING)
47
+ .dword(due).dword(0).dword(due).dword(0).section(NAME, MAJOR, MINOR)
48
+ end
49
+
50
+ # The ACIA by its BL-ACIA keys, in a keyed module's writer, for a
51
+ # machine at `cycles`.
52
+ def import(section, out, cycles)
53
+ fields = FieldReader.new(section)
54
+ transmit, receive, status, command, control = fields.bytes(5)
55
+ fields.skip(1)
56
+ due = fields.dword
57
+ out.int("command", command).int("control", control).int("errors", status & ERRORS)
58
+ out.bool("receiver_full", status.anybits?(RECEIVER_FULL))
59
+ out.bool("transmitter_empty", status.anybits?(TRANSMITTER_EMPTY)).bool("irq", status.anybits?(IRQ))
60
+ out.int("receive_data", receive).int("transmit_data", transmit).int("shifting", Chips::ACIA::IDLE)
61
+ out.int("transmit_remainder", 0)
62
+ return out.int("transmit_at", Chips::ACIA::NEVER) unless transmitting?(command)
63
+
64
+ out.optional_int("transmit_at", due.positive? ? cycles + due : nil)
65
+ end
66
+
67
+ def transmitting?(command)
68
+ control = command & 0x0c
69
+ control != 0 && control != 0x0c
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Vice
6
+ module CBM2
7
+ # CRTC 1.2 as xcbm2 saves it, laid out as xpet's (PET::CRTC): the
8
+ # board's screen settings, the registers, the one selected, the
9
+ # beam, the picture's size and the frame's lines, then xcbm2's
10
+ # picture. xcbm2's board has the 6845's cursor and reads one byte a
11
+ # character from 2K of screen RAM. After the beam's line come the
12
+ # fields left in the cursor's blink phase, counted down from 32, and
13
+ # whether the cursor shows: xcbm2 blinks it 32 fields on and 32 off,
14
+ # where badline's 6845 blinks it 16 and 16, or 8 and 8.
15
+ #
16
+ # xcbm2 ticks the mains once a frame, as vertical sync ends, so its
17
+ # mains follows the CRTC (Mains).
18
+ #
19
+ # Written: the registers and the beam as badline's CRTC has them,
20
+ # and the cursor shown as badline's is, its phase scaled to xcbm2's.
21
+ # The picture is left blank, as xcbm2 paints it again. Read: the
22
+ # registers and the beam as xpet's are (PET::CRTC), the cursor shown
23
+ # as xcbm2's is, its phase scaled to badline's, and where the
24
+ # picture is in its frame. The picture starts again with the next
25
+ # frame.
26
+ module CRTC
27
+ NAME = PET::CRTC::NAME
28
+ SETTINGS = [0xff, 0x07, 0x00, 0x10, 0x00, 0x20, 0x00, 0xe0, 0xff, 0x3f].freeze
29
+ # The 6845's cursor, one byte a character, and not a 2001.
30
+ BOARD = [0x01, 0x01, 0x00].freeze
31
+ WIDTH = 704
32
+ # The picture's lines and the line its first row starts on, for a
33
+ # 600 and a 700.
34
+ FRAMES = { false => [266, 33], true => [366, 8] }.freeze
35
+ BLINK = 32
36
+ # Where R10 is in the module, and the blink's fields left.
37
+ R10_AT = PET::CRTC::SETTINGS.length + 5 + 10
38
+ BLINK_AT = PET::CRTC::SELECTED + 4
39
+ # R10's blink of 8 fields on and 8 off.
40
+ FAST_BLINK = 0x40
41
+
42
+ module_function
43
+
44
+ def reads?(section) = PET::CRTC.reads?(section)
45
+
46
+ def fresh_crtc(machine) = CBM2Setup.of(machine).build.crtc
47
+
48
+ # The module from the BL-CRTC and BL-VIDEO fields in `crtc_input`
49
+ # and `video_input`.
50
+ def export(machine, crtc_input, video_input)
51
+ registers = PET::CRTC.crtc_registers(crtc_input)
52
+ beam = beam(machine, crtc_input, registers)
53
+ height, top = FRAMES.fetch(machine.model.high_profile)
54
+ fields = FieldWriter.new.bytes(SETTINGS).word(0).bytes(BOARD).bytes(registers)
55
+ fields.byte(crtc_input.int("selected", 0)).byte(beam.column).byte(beam.row).byte(beam.raster)
56
+ fields.bytes(blink(video_input.int("frames", 0), registers[10])).byte(1)
57
+ fields.word(0).word(beam.start & PET::CRTC::SCREEN_BITS).byte(beam.vsync).byte(0).flag(beam.shown)
58
+ fields.word(WIDTH).word(height).byte(PET::CRTC::AFTER_SIZE).zeros(3).byte(top).byte(0)
59
+ fields.word(PET::CRTC.frame_lines(registers)).word(beam.frame_line).byte(1)
60
+ fields.dword(0).dword(WIDTH).dword(height).dword(WIDTH).zeros(WIDTH * (height + 4))
61
+ fields.section(NAME, PET::CRTC::MAJOR, PET::CRTC::MINOR)
62
+ end
63
+
64
+ # The beam where xcbm2's is with badline's CRTC where `input` has
65
+ # it (PET::CRTC::CRTC_LEAD). On a line's last character xcbm2's
66
+ # beam has moved on to the next line, a cycle before it starts.
67
+ def beam(machine, input, registers)
68
+ cycles = -PET::CRTC::CRTC_LEAD % PET::CRTC.frame_cycles(registers)
69
+ beam = PET::CRTC.crtc_beam(PET::CRTC.run_on(fresh_crtc(machine), input, cycles), registers)
70
+ return beam unless beam.column == registers[0]
71
+
72
+ PET::CRTC.crtc_beam(PET::CRTC.run_on(fresh_crtc(machine), input, cycles + 1), registers).with(column: 0xff)
73
+ end
74
+
75
+ # The fields left in xcbm2's blink phase and whether its cursor
76
+ # shows, for badline's fields counted, `frames`, and R10.
77
+ def blink(frames, r10)
78
+ fast = r10 & 0x60 == FAST_BLINK
79
+ shown = frames.nobits?(fast ? 0x08 : 0x10)
80
+ phase = fast ? (frames & 0x07) << 1 : frames & 0x0f
81
+ [BLINK - (phase << 1), shown ? 1 : 0]
82
+ end
83
+
84
+ # badline's fields counted for xcbm2's blink in `section`.
85
+ def frames(section)
86
+ r10 = FieldReader.new(section).skip(R10_AT).byte
87
+ left, shown = FieldReader.new(section).skip(BLINK_AT).bytes(2)
88
+ phase = ((BLINK - [left, BLINK].min) >> 1) & 0x0f
89
+ return (shown.zero? ? 0x08 : 0) | (phase >> 1) if r10 & 0x60 == FAST_BLINK
90
+
91
+ (shown.zero? ? 0x10 : 0) | phase
92
+ end
93
+
94
+ # The BL-CRTC module, run on from xcbm2's beam as xpet's is.
95
+ def import(section, machine)
96
+ counters = Keyed.section("BL-CRTC") { |out| PET::CRTC.import(section, out, 1, 0) }
97
+ frame = PET::CRTC.frame_cycles(PET::CRTC.registers(section))
98
+ cycles = (PET::CRTC::CRTC_LEAD + PET::CRTC.column_of(section)) % frame
99
+ fields = PET::CRTC.run_on(fresh_crtc(machine), Keyed::Reader.parse(counters), cycles)
100
+ Keyed.section("BL-CRTC") { |out| PET::CRTC.copy(fields, out) }
101
+ end
102
+
103
+ # The BL-VIDEO module for a CRTC whose BL-CRTC fields `crtc` holds:
104
+ # the cursor's blink, the line the frame paints next and the frame's
105
+ # layout. The picture stays as the machine has it.
106
+ def video(section, crtc)
107
+ registers = PET::CRTC.crtc_registers(crtc)
108
+ Keyed.section("BL-VIDEO") do |out|
109
+ out.int("frames", frames(section)).int("line", painted(crtc, registers)).bool("line_shown", false)
110
+ out.int("origin", Badline::CBM2::CRTCVideo::LEFT - ((registers[0] + 1 - registers[2]) << 3))
111
+ out.ints("layout", registers.values_at(0, 1, 2, 4, 5, 6, 9, 12, 13))
112
+ end
113
+ end
114
+
115
+ # The lines the frame has displayed so far.
116
+ def painted(crtc, registers)
117
+ lines = registers[9] + 1
118
+ shown = registers[6] * lines
119
+ return shown if crtc.bool("adjusting", false) || !crtc.bool("row_display", true)
120
+
121
+ [(crtc.int("row", 0) * lines) + crtc.int("line", 0), shown].min
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Vice
6
+ module CBM2
7
+ # The mains on TPI1's I0. xcbm2 saves no mains: it ticks it once a
8
+ # frame of its CRTC, as vertical sync ends, whatever the mains is
9
+ # set to. A machine read from its snapshot has its mains' next
10
+ # falling edge where xcbm2's tick would come, and runs it at its own
11
+ # 50 or 60 Hz from there. A snapshot written for xcbm2 ticks where
12
+ # its CRTC says.
13
+ module Mains
14
+ # The cycle of the line where vertical sync ends that the tick
15
+ # latches on, counted as badline's CRTC counts columns.
16
+ TICK_COLUMN = 0
17
+
18
+ module_function
19
+
20
+ # The cycles to the mains' next falling edge for a CRTC whose
21
+ # BL-CRTC fields `crtc` holds.
22
+ def next_tick(crtc)
23
+ registers = crtc.bytes("registers", "".b).bytes
24
+ regs = Array.new(Chips::CRTC::MASKS.length) { |index| registers[index] || 0 }
25
+ line_cycles = regs[0] + 1
26
+ lines = regs[9] + 1
27
+ rows = regs[4] + 1
28
+ frame_lines = (rows * lines) + regs[5]
29
+ line = crtc.int("line", 0)
30
+ line += crtc.bool("adjusting", false) ? rows * lines : crtc.int("row", 0) * lines
31
+ tick_line = ((regs[7] * lines) + Chips::CRTC::VSYNC_LINES) % frame_lines
32
+ frame = line_cycles * frame_lines
33
+ cycles = ((tick_line * line_cycles) + TICK_COLUMN - ((line * line_cycles) + crtc.int("column", 0))) % frame
34
+ cycles.zero? ? frame : cycles
35
+ end
36
+
37
+ # The BL-MACHINE mains fields for a mains `half_period` cycles high
38
+ # and as long low, whose next falling edge is `cycles` away.
39
+ def write(out, cycles, half_period)
40
+ high = cycles <= half_period
41
+ countdown = high ? cycles : [cycles - half_period, half_period].min
42
+ out.int("mains_countdown", countdown).bool("mains_high", high)
43
+ high
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Vice
6
+ module CBM2
7
+ # CBM2MEM 1.0: the RAM board's size in units of 128K, the RAM xcbm2
8
+ # maps into segment 15 beyond the B series' own (none on a B series
9
+ # as built, $40 on a P500), its model line (0 a 700, 1 a 600 at
10
+ # 60 Hz, 2 a 600 at 50 Hz), the execution and indirection segment
11
+ # registers, then segment 15's 2K of RAM, the 2K of screen RAM, and
12
+ # the board's segments from segment 1 up.
13
+ #
14
+ # xcbm2 keeps the segment registers in segment 15's bytes $0000 and
15
+ # $0001 too, and they are written there. Under the registers
16
+ # badline's RAM is never written, so those two bytes read as 0.
17
+ # xcbm2's ROMs aren't in the snapshot.
18
+ module CBM2Mem
19
+ NAME = "CBM2MEM"
20
+ MAJOR = 1
21
+ MINOR = 0
22
+ SEGMENT = 0x10000
23
+ SYSTEM_RAM = 0x800
24
+ SCREEN_RAM = 0x800
25
+ HEADER = 5
26
+ # The model lines.
27
+ M700 = 0
28
+ M600_60HZ = 1
29
+ M600_50HZ = 2
30
+
31
+ module_function
32
+
33
+ def reads?(section) = section.major == MAJOR && section.minor == MINOR
34
+
35
+ def model_line(machine)
36
+ return M700 if machine.model.high_profile
37
+
38
+ machine.mains_hz == 60 ? M600_60HZ : M600_50HZ
39
+ end
40
+
41
+ def export(machine)
42
+ bus = machine.bus
43
+ system = bus.ram.snapshot
44
+ system[0] = bus.execution_segment
45
+ system[1] = bus.indirect_segment
46
+ fields = FieldWriter.new.byte(machine.model.ram_segments.length / 2).byte(0).byte(model_line(machine))
47
+ fields.byte(bus.execution_segment).byte(bus.indirect_segment)
48
+ fields.bytes(system).bytes(bus.screen.snapshot)
49
+ machine.model.ram_segments.each { |segment| fields.bytes(bus.segment_ram[segment].snapshot) }
50
+ fields.section(NAME, MAJOR, MINOR)
51
+ end
52
+
53
+ # The RAM in K, the RAM mapped into segment 15 and the model line.
54
+ def ram_kb(section) = FieldReader.new(section).byte * 128
55
+
56
+ def mapped(section) = FieldReader.new(section).skip(1).byte
57
+
58
+ def line(section) = FieldReader.new(section).skip(2).byte
59
+
60
+ # The RAM and the segment registers by their BL-RAM keys, in a
61
+ # keyed module's writer, for a machine whose board fills
62
+ # `segments`.
63
+ def import(section, out, segments)
64
+ fields = FieldReader.new(section).skip(3)
65
+ out.int("execution_segment", fields.byte & 0x0f).int("indirect_segment", fields.byte & 0x0f)
66
+ system = fields.string(SYSTEM_RAM)
67
+ out.bytes("system", "\0\0".b + system.byteslice(2, SYSTEM_RAM - 2))
68
+ out.bytes("screen", fields.string(SCREEN_RAM))
69
+ segments.each { |segment| out.bytes("segment#{segment}", fields.string(SEGMENT)) }
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end