badline 0.13.0 → 0.14.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 (254) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +49 -1
  3. data/CLAUDE.md +27 -7
  4. data/CONTRIBUTING.md +4 -3
  5. data/README.md +8 -5
  6. data/doc/media.md +2 -1
  7. data/doc/pinned-behaviour.md +29 -0
  8. data/doc/snapshot-format.md +1047 -0
  9. data/doc/snapshots.md +72 -39
  10. data/lib/badline/audio/bare_player.rb +50 -17
  11. data/lib/badline/audio/machine_player.rb +15 -9
  12. data/lib/badline/audio/renderer.rb +32 -21
  13. data/lib/badline/c128/banks.rb +6 -3
  14. data/lib/badline/c128/bus/keyed_state.rb +45 -0
  15. data/lib/badline/c128/bus.rb +17 -3
  16. data/lib/badline/c128/drives.rb +6 -25
  17. data/lib/badline/c128/keyed_modules.rb +117 -0
  18. data/lib/badline/c128/keyed_state.rb +91 -0
  19. data/lib/badline/c128/keys.rb +9 -0
  20. data/lib/badline/c128/mmu.rb +9 -8
  21. data/lib/badline/c128/mmu_pages.rb +20 -0
  22. data/lib/badline/c128/modes.rb +19 -9
  23. data/lib/badline/c128/z80_turns.rb +1 -1
  24. data/lib/badline/c128.rb +8 -8
  25. data/lib/badline/c64/attachments.rb +0 -44
  26. data/lib/badline/c64/banked_ram/plus256k.rb +18 -10
  27. data/lib/badline/c64/banked_ram/plus60k.rb +8 -6
  28. data/lib/badline/c64/banked_ram/unexpanded.rb +2 -2
  29. data/lib/badline/c64/bus/debug_register.rb +32 -0
  30. data/lib/badline/c64/bus/keyed_state.rb +41 -0
  31. data/lib/badline/c64/bus.rb +2 -1
  32. data/lib/badline/c64/cartridge/action_replay.rb +0 -13
  33. data/lib/badline/c64/cartridge/easy_flash.rb +0 -15
  34. data/lib/badline/c64/cartridge/epyx_fastload.rb +0 -8
  35. data/lib/badline/c64/cartridge/final_cartridge3.rb +0 -12
  36. data/lib/badline/c64/cartridge/flash.rb +3 -3
  37. data/lib/badline/c64/cartridge/g_mod2.rb +0 -12
  38. data/lib/badline/c64/cartridge/geo_ram.rb +0 -16
  39. data/lib/badline/c64/cartridge/kcs_power.rb +0 -8
  40. data/lib/badline/c64/cartridge/keyed_state.rb +393 -0
  41. data/lib/badline/c64/cartridge/pagefox.rb +0 -10
  42. data/lib/badline/c64/cartridge/retro_replay.rb +0 -8
  43. data/lib/badline/c64/cartridge/rgcd.rb +0 -10
  44. data/lib/badline/c64/cartridge/super_games.rb +0 -10
  45. data/lib/badline/c64/cartridge/zaxxon.rb +0 -16
  46. data/lib/badline/c64/cartridge.rb +1 -27
  47. data/lib/badline/c64/kernal_traps.rb +28 -25
  48. data/lib/badline/c64/keyed_modules.rb +111 -0
  49. data/lib/badline/c64/keyed_state.rb +68 -0
  50. data/lib/badline/c64/reu/dma.rb +0 -4
  51. data/lib/badline/c64/reu/keyed_state.rb +140 -0
  52. data/lib/badline/c64/reu/ram.rb +0 -15
  53. data/lib/badline/c64/reu.rb +1 -3
  54. data/lib/badline/c64/true_drives.rb +56 -5
  55. data/lib/badline/c64.rb +7 -3
  56. data/lib/badline/chips/cia/interrupt_register.rb +0 -17
  57. data/lib/badline/chips/cia/keyed_parts.rb +213 -0
  58. data/lib/badline/chips/cia/serial.rb +0 -4
  59. data/lib/badline/chips/cia/time_of_day.rb +0 -22
  60. data/lib/badline/chips/cia/timer.rb +0 -20
  61. data/lib/badline/chips/cia.rb +2 -2
  62. data/lib/badline/chips/cpu/core.rb +18 -2
  63. data/lib/badline/chips/cpu/keyed_state.rb +177 -0
  64. data/lib/badline/chips/cpu/microcode.rb +16 -0
  65. data/lib/badline/chips/cpu/port.rb +20 -3
  66. data/lib/badline/chips/cpu.rb +5 -0
  67. data/lib/badline/chips/sid/decimator.rb +10 -8
  68. data/lib/badline/chips/sid/envelope.rb +1 -6
  69. data/lib/badline/chips/sid/filter.rb +0 -14
  70. data/lib/badline/chips/sid/keyed_parts.rb +286 -0
  71. data/lib/badline/chips/sid/voice.rb +0 -10
  72. data/lib/badline/chips/sid/waveform.rb +0 -2
  73. data/lib/badline/chips/sid.rb +1 -2
  74. data/lib/badline/chips/ted/beam.rb +119 -0
  75. data/lib/badline/chips/ted/timers.rb +66 -0
  76. data/lib/badline/chips/ted.rb +143 -0
  77. data/lib/badline/chips/tia.rb +49 -0
  78. data/lib/badline/chips/vdc/keyed_parts.rb +105 -0
  79. data/lib/badline/chips/vdc/painter.rb +0 -12
  80. data/lib/badline/chips/vdc/raster.rb +0 -16
  81. data/lib/badline/chips/vdc/window.rb +0 -9
  82. data/lib/badline/chips/vdc.rb +1 -2
  83. data/lib/badline/chips/via/control_lines.rb +0 -15
  84. data/lib/badline/chips/via/interrupt_register.rb +0 -9
  85. data/lib/badline/chips/via/keyed_parts.rb +163 -0
  86. data/lib/badline/chips/via/shift_register.rb +0 -14
  87. data/lib/badline/chips/via/timer1.rb +0 -13
  88. data/lib/badline/chips/via/timer2.rb +0 -18
  89. data/lib/badline/chips/via.rb +1 -2
  90. data/lib/badline/chips/vic/border_mask.rb +0 -10
  91. data/lib/badline/chips/vic/collisions.rb +0 -11
  92. data/lib/badline/chips/vic/color_patches.rb +0 -11
  93. data/lib/badline/chips/vic/display_state.rb +0 -19
  94. data/lib/badline/chips/vic/graphics_shifter.rb +0 -4
  95. data/lib/badline/chips/vic/keyed_parts.rb +228 -0
  96. data/lib/badline/chips/vic/keyed_sprites.rb +225 -0
  97. data/lib/badline/chips/vic/keyed_state.rb +102 -0
  98. data/lib/badline/chips/vic/register_log.rb +0 -18
  99. data/lib/badline/chips/vic/registers.rb +0 -9
  100. data/lib/badline/chips/vic/sequencer.rb +0 -2
  101. data/lib/badline/chips/vic/sprite/internal_bus.rb +0 -11
  102. data/lib/badline/chips/vic/sprite.rb +0 -2
  103. data/lib/badline/chips/vic/sprites.rb +0 -3
  104. data/lib/badline/chips/vic.rb +6 -2
  105. data/lib/badline/chips/vic_i/keyed_parts.rb +142 -0
  106. data/lib/badline/chips/vic_i/painter.rb +0 -4
  107. data/lib/badline/chips/vic_i/sound.rb +38 -46
  108. data/lib/badline/chips/vic_i.rb +2 -2
  109. data/lib/badline/chips/z80/core.rb +2 -2
  110. data/lib/badline/chips/z80/keyed_state.rb +73 -0
  111. data/lib/badline/chips.rb +10 -0
  112. data/lib/badline/core.rb +4 -21
  113. data/lib/badline/drives/datasette/keyed_state.rb +38 -0
  114. data/lib/badline/drives/datasette.rb +40 -7
  115. data/lib/badline/drives/engine/core.rb +3 -0
  116. data/lib/badline/drives/engine/serial_lines.rb +4 -7
  117. data/lib/badline/drives/model1541/bus.rb +0 -13
  118. data/lib/badline/drives/model1541/disk.rb +9 -8
  119. data/lib/badline/drives/model1541/keyed_state.rb +251 -0
  120. data/lib/badline/drives/model1541/mechanism.rb +0 -2
  121. data/lib/badline/drives/model1541.rb +1 -2
  122. data/lib/badline/drives/model1571/bus.rb +0 -13
  123. data/lib/badline/drives/model1571/keyed_state.rb +108 -0
  124. data/lib/badline/drives/model1571/wd1770.rb +0 -14
  125. data/lib/badline/drives/model1571.rb +1 -2
  126. data/lib/badline/drives/model1581/bus.rb +0 -13
  127. data/lib/badline/drives/model1581/disk.rb +0 -4
  128. data/lib/badline/drives/model1581/keyed_state.rb +280 -0
  129. data/lib/badline/drives/model1581/mechanism.rb +0 -21
  130. data/lib/badline/drives/model1581/slot.rb +0 -18
  131. data/lib/badline/drives/model1581/wd1772.rb +0 -2
  132. data/lib/badline/drives/model1581.rb +2 -2
  133. data/lib/badline/drives.rb +8 -0
  134. data/lib/badline/frontend/app.rb +3 -3
  135. data/lib/badline/frontend/boot.rb +1 -1
  136. data/lib/badline/frontend/controls.rb +19 -3
  137. data/lib/badline/frontend/drive_led.rb +44 -21
  138. data/lib/badline/frontend/menu/menu_dialogs.rb +2 -2
  139. data/lib/badline/frontend/snapshots.rb +12 -1
  140. data/lib/badline/frontend/timeline.rb +28 -10
  141. data/lib/badline/input/matrix_typist.rb +98 -0
  142. data/lib/badline/input.rb +1 -0
  143. data/lib/badline/kernal_trap/chrout.rb +45 -0
  144. data/lib/badline/kernal_trap/dos/channel.rb +0 -27
  145. data/lib/badline/kernal_trap/dos/channels.rb +0 -25
  146. data/lib/badline/kernal_trap/dos/memory.rb +0 -9
  147. data/lib/badline/kernal_trap/dos/status.rb +0 -8
  148. data/lib/badline/kernal_trap/dos/write_file.rb +0 -18
  149. data/lib/badline/kernal_trap/dos.rb +0 -19
  150. data/lib/badline/kernal_trap/file_routine.rb +4 -4
  151. data/lib/badline/kernal_trap/keyed_state.rb +150 -0
  152. data/lib/badline/kernal_trap/layout.rb +75 -11
  153. data/lib/badline/kernal_trap/load.rb +14 -16
  154. data/lib/badline/kernal_trap/routine.rb +12 -0
  155. data/lib/badline/kernal_trap/save.rb +8 -18
  156. data/lib/badline/kernal_trap/serial.rb +1 -17
  157. data/lib/badline/kernal_trap.rb +2 -0
  158. data/lib/badline/machine/keyboard_buffer.rb +49 -0
  159. data/lib/badline/machine/settling.rb +29 -0
  160. data/lib/badline/machine.rb +5 -2
  161. data/lib/badline/media/boot_disk.rb +14 -2
  162. data/lib/badline/media/extensions.rb +10 -0
  163. data/lib/badline/memory.rb +3 -6
  164. data/lib/badline/options/family.rb +5 -2
  165. data/lib/badline/options/help.rb +42 -16
  166. data/lib/badline/options/table.rb +2 -0
  167. data/lib/badline/options/validation.rb +2 -0
  168. data/lib/badline/options.rb +6 -1
  169. data/lib/badline/plus4/acia.rb +45 -0
  170. data/lib/badline/plus4/bus.rb +312 -0
  171. data/lib/badline/plus4/cpu.rb +10 -0
  172. data/lib/badline/plus4/cpu_port.rb +93 -0
  173. data/lib/badline/plus4/keyboard_port.rb +51 -0
  174. data/lib/badline/plus4/model.rb +38 -0
  175. data/lib/badline/plus4/user_port.rb +31 -0
  176. data/lib/badline/plus4.rb +164 -0
  177. data/lib/badline/roms/README +38 -0
  178. data/lib/badline/roms/plus4/basic.rom +0 -0
  179. data/lib/badline/roms/plus4/function-high.rom +0 -0
  180. data/lib/badline/roms/plus4/function-low.rom +0 -0
  181. data/lib/badline/roms/plus4/kernal-ntsc.rom +0 -0
  182. data/lib/badline/roms/plus4/kernal-pal.rom +0 -0
  183. data/lib/badline/snapshot/c128_setup.rb +9 -20
  184. data/lib/badline/snapshot/c64_setup.rb +28 -24
  185. data/lib/badline/snapshot/container.rb +4 -0
  186. data/lib/badline/snapshot/field_doc/markdown.rb +112 -0
  187. data/lib/badline/snapshot/field_doc.rb +109 -0
  188. data/lib/badline/snapshot/image.rb +134 -28
  189. data/lib/badline/snapshot/keyed/cursor.rb +40 -0
  190. data/lib/badline/snapshot/keyed/modules.rb +66 -0
  191. data/lib/badline/snapshot/keyed/reader.rb +188 -0
  192. data/lib/badline/snapshot/keyed/recorder.rb +88 -0
  193. data/lib/badline/snapshot/keyed/report.rb +26 -0
  194. data/lib/badline/snapshot/keyed/writer.rb +55 -0
  195. data/lib/badline/snapshot/keyed.rb +64 -0
  196. data/lib/badline/snapshot/machine_state.rb +17 -64
  197. data/lib/badline/snapshot/storage_setup.rb +29 -34
  198. data/lib/badline/snapshot/vic20_setup.rb +6 -15
  199. data/lib/badline/snapshot/vice/reu1764.rb +2 -2
  200. data/lib/badline/snapshot/vice.rb +7 -31
  201. data/lib/badline/snapshot.rb +43 -20
  202. data/lib/badline/storage/crt_file.rb +3 -1
  203. data/lib/badline/storage/g64_image.rb +8 -3
  204. data/lib/badline/storage/tap.rb +43 -15
  205. data/lib/badline/version.rb +1 -1
  206. data/lib/badline/vic20/attachments.rb +3 -0
  207. data/lib/badline/vic20/bus/keyed_state.rb +52 -0
  208. data/lib/badline/vic20/bus.rb +1 -2
  209. data/lib/badline/vic20/keyed_modules.rb +73 -0
  210. data/lib/badline/vic20/keyed_state.rb +45 -0
  211. data/lib/badline/vic20.rb +5 -5
  212. data/lib/badline.rb +1 -0
  213. metadata +61 -42
  214. data/lib/badline/c128/bus/saved_state.rb +0 -53
  215. data/lib/badline/c128/saved_state.rb +0 -117
  216. data/lib/badline/c64/bus/saved_state.rb +0 -68
  217. data/lib/badline/c64/cartridge/flash/saved_state.rb +0 -40
  218. data/lib/badline/c64/cartridge/retro_replay/saved_state.rb +0 -47
  219. data/lib/badline/c64/cartridge/saved_state.rb +0 -63
  220. data/lib/badline/c64/reu/dma/saved_state.rb +0 -52
  221. data/lib/badline/c64/reu/saved_state.rb +0 -55
  222. data/lib/badline/c64/saved_state.rb +0 -112
  223. data/lib/badline/chips/cia/saved_state.rb +0 -49
  224. data/lib/badline/chips/cia/serial/saved_state.rb +0 -45
  225. data/lib/badline/chips/cpu/saved_state.rb +0 -117
  226. data/lib/badline/chips/sid/envelope/saved_state.rb +0 -70
  227. data/lib/badline/chips/sid/filter/saved_state.rb +0 -42
  228. data/lib/badline/chips/sid/saved_state.rb +0 -52
  229. data/lib/badline/chips/sid/waveform/saved_state.rb +0 -68
  230. data/lib/badline/chips/vdc/saved_state.rb +0 -36
  231. data/lib/badline/chips/via/saved_state.rb +0 -44
  232. data/lib/badline/chips/vic/graphics_shifter/saved_state.rb +0 -40
  233. data/lib/badline/chips/vic/saved_state.rb +0 -82
  234. data/lib/badline/chips/vic/sequencer/saved_state.rb +0 -91
  235. data/lib/badline/chips/vic/sprite/saved_state.rb +0 -66
  236. data/lib/badline/chips/vic/sprites/saved_state.rb +0 -59
  237. data/lib/badline/chips/vic_i/painter/saved_state.rb +0 -40
  238. data/lib/badline/chips/vic_i/saved_state.rb +0 -55
  239. data/lib/badline/chips/z80/saved_state.rb +0 -71
  240. data/lib/badline/chrout_trap.rb +0 -43
  241. data/lib/badline/debug_register.rb +0 -28
  242. data/lib/badline/drives/datasette/saved_state.rb +0 -37
  243. data/lib/badline/drives/model1541/disk/saved_state.rb +0 -88
  244. data/lib/badline/drives/model1541/mechanism/saved_state.rb +0 -72
  245. data/lib/badline/drives/model1541/saved_state.rb +0 -44
  246. data/lib/badline/drives/model1571/saved_state.rb +0 -47
  247. data/lib/badline/drives/model1581/disk/saved_state.rb +0 -89
  248. data/lib/badline/drives/model1581/saved_state.rb +0 -42
  249. data/lib/badline/drives/model1581/wd1772/saved_state.rb +0 -68
  250. data/lib/badline/keyboard_buffer.rb +0 -33
  251. data/lib/badline/snapshot/setup.rb +0 -31
  252. data/lib/badline/snapshot/state.rb +0 -179
  253. data/lib/badline/vic20/bus/saved_state.rb +0 -35
  254. data/lib/badline/vic20/saved_state.rb +0 -110
@@ -0,0 +1,393 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C64
5
+ class Cartridge
6
+ # A cartridge's fields in a keyed snapshot module (Snapshot::Keyed,
7
+ # BL-CART): the CRT image it was built from and its jumpers, which
8
+ # build it again (Cartridge.from_fields), the EXROM and GAME lines,
9
+ # its pull on NMI, the button, and where ROML and ROMH point, each by
10
+ # the name of the bank or view it shows (window_labels), and whatever
11
+ # each mapper adds.
12
+ module KeyedState
13
+ def save_fields(out)
14
+ save_setup_fields(out)
15
+ out.int("exrom", @exrom).int("game", @game).bool("nmi", @nmi).bool("button", @button == true)
16
+ save_mapper_fields(out)
17
+ out
18
+ end
19
+
20
+ def load_fields(input)
21
+ @exrom = input.masked("exrom", @exrom, 1)
22
+ @game = input.masked("game", @game, 1)
23
+ @nmi = input.bool("nmi", @nmi)
24
+ @button = input.bool("button", false)
25
+ load_mapper_fields(input)
26
+ end
27
+
28
+ private
29
+
30
+ def save_setup_fields(out)
31
+ out.bytes("crt", Storage::CRTFile.encode(@crt))
32
+ save_jumper_fields(out)
33
+ end
34
+
35
+ def save_jumper_fields(_out); end
36
+
37
+ def save_mapper_fields(out)
38
+ save_window_fields(out)
39
+ end
40
+
41
+ def load_mapper_fields(input)
42
+ load_window_fields(input)
43
+ end
44
+
45
+ # The name of each of `windows`, in the same order: bank<N> for the
46
+ # mapper's banks, and empty for erased ROM.
47
+ def window_labels
48
+ banks = @banks
49
+ return [] unless banks
50
+
51
+ [*Array.new(banks.length) { |number| "bank#{number}" }, "empty"]
52
+ end
53
+
54
+ def save_window_fields(out)
55
+ choices = windows
56
+ return unless choices
57
+
58
+ labels = window_labels
59
+ save_window(out, "roml", @roml, choices, labels)
60
+ save_window(out, "romh", @romh, choices, labels)
61
+ end
62
+
63
+ def save_window(out, key, window, choices, labels)
64
+ return if window.nil?
65
+
66
+ out.text(key, labels.fetch(window_index(window, choices)))
67
+ end
68
+
69
+ def load_window_fields(input)
70
+ choices = windows
71
+ return unless choices
72
+
73
+ labels = window_labels
74
+ @roml = window_named(input.choice("roml", [*labels, "none"], "none"), choices, labels)
75
+ @romh = window_named(input.choice("romh", [*labels, "none"], "none"), choices, labels)
76
+ end
77
+
78
+ def window_named(label, choices, labels)
79
+ index = labels.index(label)
80
+ index.nil? ? nil : choices.fetch(index)
81
+ end
82
+ end
83
+
84
+ include KeyedState
85
+
86
+ class << self
87
+ # A cartridge built afresh from the CRT image and the jumpers a
88
+ # keyed snapshot module holds, before load_fields puts its state in.
89
+ def from_fields(input)
90
+ kb = input.int("geo_ram_kb", 0)
91
+ return GeoRAM.new(size: kb) if kb.positive?
92
+
93
+ crt = Storage::CRTFile.new(bytes: input.bytes("crt", "".b))
94
+ mapper_for(crt).from_jumper_fields(crt, input)
95
+ end
96
+
97
+ def from_jumper_fields(crt, _input) = new(crt)
98
+ end
99
+
100
+ # Two banks of ROM, one behind each window: roml<N> and romh<N>.
101
+ module SplitBanks
102
+ private
103
+
104
+ def window_labels
105
+ [*Array.new(@roml_banks.length) { |number| "roml#{number}" },
106
+ *Array.new(@romh_banks.length) { |number| "romh#{number}" }, "empty"]
107
+ end
108
+ end
109
+
110
+ class SuperGames
111
+ include SplitBanks
112
+
113
+ private
114
+
115
+ def save_mapper_fields(out)
116
+ super
117
+ out.bool("locked", @locked)
118
+ end
119
+
120
+ def load_mapper_fields(input)
121
+ super
122
+ @locked = input.bool("locked", @locked)
123
+ end
124
+ end
125
+
126
+ class Comal80
127
+ include SplitBanks
128
+ end
129
+
130
+ class FinalCartridge3
131
+ include SplitBanks
132
+
133
+ private
134
+
135
+ def save_mapper_fields(out)
136
+ super
137
+ out.bool("register_visible", @register_visible).bool("phi1_ultimax", @phi1_ultimax)
138
+ end
139
+
140
+ def load_mapper_fields(input)
141
+ super
142
+ @io_bank = @roml
143
+ @register_visible = input.bool("register_visible", @register_visible)
144
+ @phi1_ultimax = input.bool("phi1_ultimax", @phi1_ultimax)
145
+ end
146
+ end
147
+
148
+ class RGCD
149
+ private
150
+
151
+ def save_mapper_fields(out)
152
+ super
153
+ out.bool("disabled", @disabled)
154
+ end
155
+
156
+ def load_mapper_fields(input)
157
+ super
158
+ @disabled = input.bool("disabled", @disabled)
159
+ end
160
+ end
161
+
162
+ class Zaxxon
163
+ class High
164
+ def save_fields(out)
165
+ out.int("high_bank", [*@banks, EMPTY_BANK].index { |bank| bank.equal?(@bank) }.to_i)
166
+ end
167
+
168
+ def load_fields(input)
169
+ choices = [*@banks, EMPTY_BANK]
170
+ @bank = choices.fetch(input.int("high_bank", 0).clamp(0, choices.length - 1))
171
+ end
172
+ end
173
+
174
+ private
175
+
176
+ def save_mapper_fields(out) = @romh.save_fields(out)
177
+
178
+ def load_mapper_fields(input) = @romh.load_fields(input)
179
+ end
180
+
181
+ class EpyxFastload
182
+ private
183
+
184
+ # How long ago the capacitor was discharged, on the machine's clock.
185
+ def save_mapper_fields(out)
186
+ out.int("discharged_ago", now_cycles - @discharged_at)
187
+ end
188
+
189
+ def load_mapper_fields(input)
190
+ @discharged_at = now_cycles - input.int("discharged_ago", now_cycles - @discharged_at)
191
+ end
192
+
193
+ def now_cycles
194
+ clock = @clock
195
+ clock ? clock.call : 0
196
+ end
197
+ end
198
+
199
+ class ActionReplay
200
+ private
201
+
202
+ def window_labels
203
+ ["through", "isolated", "contended", *Array.new(@rom_banks.length) { |number| "rom#{number}" }, "empty"]
204
+ end
205
+
206
+ def save_mapper_fields(out)
207
+ super
208
+ out.text("rom", window_labels.fetch(window_index(@rom, windows))).bool("active", @active)
209
+ out.bool("io2_ram", @io2_ram).bytes("ram", @ram_data)
210
+ end
211
+
212
+ def load_mapper_fields(input)
213
+ super
214
+ labels = window_labels
215
+ @rom = window_named(input.choice("rom", labels, "rom0"), windows, labels)
216
+ @active = input.bool("active", @active)
217
+ @io2_ram = input.bool("io2_ram", @io2_ram)
218
+ input.bytes_into("ram", @ram_data)
219
+ end
220
+ end
221
+
222
+ class KCSPower
223
+ private
224
+
225
+ def save_mapper_fields(out) = out.bytes("io_ram", @io_ram)
226
+
227
+ def load_mapper_fields(input) = input.bytes_into("io_ram", @io_ram)
228
+ end
229
+
230
+ class Pagefox
231
+ private
232
+
233
+ def window_labels
234
+ [*Array.new(@roml_banks.length) { |number| "roml#{number}" },
235
+ *Array.new(@romh_banks.length) { |number| "romh#{number}" },
236
+ *Array.new(@ram_banks.length) { |number| "ram#{number}" }, "open_bus", "empty"]
237
+ end
238
+
239
+ def save_mapper_fields(out)
240
+ super
241
+ @ram_banks.each_with_index { |ram, number| out.bytes("ram#{number}.data", ram.data) }
242
+ end
243
+
244
+ def load_mapper_fields(input)
245
+ super
246
+ @ram_banks.each_with_index { |ram, number| input.bytes_into("ram#{number}.data", ram.data) }
247
+ end
248
+ end
249
+
250
+ class GeoRAM
251
+ private
252
+
253
+ def save_setup_fields(out) = out.int("geo_ram_kb", @size)
254
+
255
+ def save_mapper_fields(out)
256
+ out.bytes("ram", @ram_data).int("block", @block).int("page", @page).int("window", @window)
257
+ end
258
+
259
+ def load_mapper_fields(input)
260
+ input.bytes_into("ram", @ram_data)
261
+ @block = input.int("block", @block)
262
+ @page = input.int("page", @page)
263
+ @window = input.int("window", @window)
264
+ end
265
+ end
266
+
267
+ class Flash
268
+ # The array, the command state by name, how far through a command
269
+ # sequence, the toggle bit, a timed operation's end in cycles from
270
+ # now, and the windows handed out so far, under `prefix`.
271
+ module KeyedState
272
+ STATE_NAMES = STATES.map(&:to_s).freeze
273
+
274
+ def save_fields(out, prefix)
275
+ out.bytes("#{prefix}data", @data).text("#{prefix}state", @state.to_s)
276
+ out.text("#{prefix}base_state", @base_state.to_s).int("#{prefix}cycle", @cycle)
277
+ out.int("#{prefix}toggle", @toggle)
278
+ done_at = @done_at
279
+ out.int("#{prefix}done_in", done_at - @clock.call) unless done_at.nil?
280
+ out.optional_int("#{prefix}programmed", @programmed).optional_int("#{prefix}remaining", @remaining)
281
+ sectors = @erasing_sectors
282
+ out.ints("#{prefix}erasing_sectors", sectors) if sectors
283
+ out.ints("#{prefix}windows", @windows.keys).ints("#{prefix}status_windows", @status_windows.keys)
284
+ end
285
+
286
+ def load_fields(input, prefix)
287
+ input.bytes_into("#{prefix}data", @data)
288
+ @state = state_named(input.choice("#{prefix}state", STATE_NAMES, "read"))
289
+ @base_state = state_named(input.choice("#{prefix}base_state", STATE_NAMES, "read"))
290
+ @cycle = input.int("#{prefix}cycle", @cycle)
291
+ @toggle = input.int("#{prefix}toggle", @toggle)
292
+ done_in = input.optional_int("#{prefix}done_in")
293
+ @done_at = done_in.nil? ? nil : @clock.call + done_in
294
+ @programmed = input.optional_int("#{prefix}programmed")
295
+ @remaining = input.optional_int("#{prefix}remaining")
296
+ sectors = input.ints("#{prefix}erasing_sectors", [])
297
+ @erasing_sectors = sectors.empty? ? nil : sectors.dup
298
+ rebuild_windows(input.ints("#{prefix}windows", []), input.ints("#{prefix}status_windows", []))
299
+ end
300
+
301
+ private
302
+
303
+ def state_named(name)
304
+ STATES.find { |state| state.to_s == name } || :read
305
+ end
306
+ end
307
+
308
+ include KeyedState
309
+ end
310
+
311
+ class GMod2
312
+ private
313
+
314
+ def save_mapper_fields(out)
315
+ out.int("bank", @bank).bool("flash_writes", @flash_writes)
316
+ @flash.save_fields(out, "flash.")
317
+ end
318
+
319
+ def load_mapper_fields(input)
320
+ @bank = input.int("bank", @bank)
321
+ @flash_writes = input.bool("flash_writes", @flash_writes)
322
+ @flash.load_fields(input, "flash.")
323
+ @roml = @flash.window(@bank * BANK_SIZE)
324
+ end
325
+ end
326
+
327
+ class EasyFlash
328
+ private
329
+
330
+ def save_mapper_fields(out)
331
+ out.int("bank", @bank).int("control", @control).bytes("io_ram", @io_ram)
332
+ @low_flash.save_fields(out, "flash_low.")
333
+ @high_flash.save_fields(out, "flash_high.")
334
+ end
335
+
336
+ def load_mapper_fields(input)
337
+ @bank = input.int("bank", @bank)
338
+ @control = input.byte("control", @control)
339
+ input.bytes_into("io_ram", @io_ram)
340
+ @low_flash.load_fields(input, "flash_low.")
341
+ @high_flash.load_fields(input, "flash_high.")
342
+ map_bank
343
+ end
344
+ end
345
+
346
+ class RetroReplay
347
+ CONFIG_NAMES = CONTROL_MODES.map(&:to_s).freeze
348
+
349
+ # The flash and bank jumpers, as the board has them set.
350
+ def self.from_jumper_fields(crt, input)
351
+ new(crt, flash_jumper: input.bool("jumper.flash", false), bank_jumper: input.bool("jumper.bank", false))
352
+ end
353
+
354
+ private
355
+
356
+ def save_jumper_fields(out)
357
+ out.bool("jumper.flash", @flash_jumper).bool("jumper.bank", @bank_jumper)
358
+ end
359
+
360
+ def save_mapper_fields(out)
361
+ out.bool("active", @active).bool("frozen", @frozen).bool("phi1_ultimax", @phi1_ultimax)
362
+ out.bool("ram_enabled", @ram_enabled).bool("ram_at_a000", @ram_at_a000).bool("allow_bank", @allow_bank)
363
+ out.bool("no_freeze", @no_freeze).bool("reu_mapping", @reu_mapping).bool("write_once", @write_once)
364
+ out.int("bank", @bank).text("config", @config.to_s)
365
+ @ram_data.each_with_index { |data, number| out.bytes("ram#{number}", data) }
366
+ @flash.save_fields(out, "flash.")
367
+ end
368
+
369
+ def load_mapper_fields(input)
370
+ load_flag_fields(input)
371
+ @bank = input.int("bank", @bank)
372
+ config = input.choice("config", CONFIG_NAMES, "rom8k")
373
+ @config = CONTROL_MODES.find { |mode| mode.to_s == config } || :rom8k
374
+ @ram_data.each_with_index { |data, number| input.bytes_into("ram#{number}", data) }
375
+ @flash.load_fields(input, "flash.")
376
+ map_windows
377
+ end
378
+
379
+ def load_flag_fields(input)
380
+ @active = input.bool("active", @active)
381
+ @frozen = input.bool("frozen", @frozen)
382
+ @phi1_ultimax = input.bool("phi1_ultimax", @phi1_ultimax)
383
+ @ram_enabled = input.bool("ram_enabled", @ram_enabled)
384
+ @ram_at_a000 = input.bool("ram_at_a000", @ram_at_a000)
385
+ @allow_bank = input.bool("allow_bank", @allow_bank)
386
+ @no_freeze = input.bool("no_freeze", @no_freeze)
387
+ @reu_mapping = input.bool("reu_mapping", @reu_mapping)
388
+ @write_once = input.bool("write_once", @write_once)
389
+ end
390
+ end
391
+ end
392
+ end
393
+ end
@@ -30,16 +30,6 @@ module Badline
30
30
 
31
31
  private
32
32
 
33
- def save_mapper(out)
34
- super
35
- @ram_banks.each { |ram| out.blob(ram.data) }
36
- end
37
-
38
- def load_mapper(input)
39
- super
40
- @ram_banks.each { |ram| input.blob_into(ram.data) }
41
- end
42
-
43
33
  def windows = [*@roml_banks, *@romh_banks, *@ram_banks, @open_bus || EMPTY_BANK, EMPTY_BANK]
44
34
 
45
35
  def select(value)
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "badline/c64/cartridge/retro_replay/saved_state"
4
-
5
3
  module Badline
6
4
  class C64
7
5
  class Cartridge
@@ -47,7 +45,6 @@ module Badline
47
45
  # the half in bit 5.
48
46
  class RetroReplay < Cartridge
49
47
  include Freezer
50
- include SavedState
51
48
 
52
49
  CONTROL_MODES = %i[rom8k rom16k off ultimax].freeze
53
50
  IO1_PAGE = 0x1e00
@@ -55,11 +52,6 @@ module Badline
55
52
 
56
53
  attr_reader :flash
57
54
 
58
- def self.from_jumpers(crt, input)
59
- input.boolean?
60
- new(crt, flash_jumper: input.boolean?, bank_jumper: input.boolean?)
61
- end
62
-
63
55
  def initialize(crt, flash_jumper: false, bank_jumper: false)
64
56
  @nordic = crt.subtype == 1
65
57
  @flash_jumper = flash_jumper
@@ -29,16 +29,6 @@ module Badline
29
29
 
30
30
  private
31
31
 
32
- def save_mapper(out)
33
- super
34
- out.boolean(@disabled)
35
- end
36
-
37
- def load_mapper(input)
38
- super
39
- @disabled = input.boolean?
40
- end
41
-
42
32
  def select(value)
43
33
  value ^= 0x07 if @hucky
44
34
  if value.anybits?(0x08)
@@ -21,16 +21,6 @@ module Badline
21
21
 
22
22
  private
23
23
 
24
- def save_mapper(out)
25
- super
26
- out.boolean(@locked)
27
- end
28
-
29
- def load_mapper(input)
30
- super
31
- @locked = input.boolean?
32
- end
33
-
34
24
  def windows = [*@roml_banks, *@romh_banks, EMPTY_BANK]
35
25
 
36
26
  def select(value)
@@ -32,14 +32,6 @@ module Badline
32
32
  def peek(addr)
33
33
  @bank.peek(addr)
34
34
  end
35
-
36
- def save_state(out)
37
- out.int([*@banks, EMPTY_BANK].index { |bank| bank.equal?(@bank) })
38
- end
39
-
40
- def load_state(input)
41
- @bank = [*@banks, EMPTY_BANK].fetch(input.int)
42
- end
43
35
  end
44
36
 
45
37
  def reset
@@ -48,14 +40,6 @@ module Badline
48
40
 
49
41
  private
50
42
 
51
- def save_mapper(out)
52
- @romh.save_state(out)
53
- end
54
-
55
- def load_mapper(input)
56
- @romh.load_state(input)
57
- end
58
-
59
43
  def install_chips(chips)
60
44
  roml, romh = banks_from(chips)
61
45
  @romh = High.new(romh)
@@ -27,15 +27,11 @@ require "badline/c64/cartridge/final_cartridge3"
27
27
  require "badline/c64/cartridge/retro_replay"
28
28
  require "badline/c64/cartridge/kcs_power"
29
29
  require "badline/c64/cartridge/geo_ram"
30
- require "badline/c64/cartridge/saved_state"
31
30
 
32
31
  module Badline
33
32
  class C64
34
33
  class Cartridge
35
- include SavedState
36
-
37
34
  class UnsupportedTypeError < StandardError; end
38
-
39
35
  ROMH_START = 0xa000
40
36
  BANK_SIZE = 0x2000
41
37
 
@@ -70,22 +66,6 @@ module Badline
70
66
  mapper_for(crt).new(crt, **)
71
67
  end
72
68
 
73
- # A cartridge built afresh from what save_setup wrote, before
74
- # load_state puts its state in.
75
- def from_setup(input)
76
- return GeoRAM.new(size: input.int) if input.int == GEO_RAM_SETUP
77
-
78
- crt = Storage::CRTFile.new(bytes: input.string)
79
- mapper_for(crt).from_jumpers(crt, input)
80
- end
81
-
82
- # A cartridge from its image and what save_jumpers wrote: whether
83
- # there are jumpers, then their settings on a mapper that has them.
84
- def from_jumpers(crt, input)
85
- input.boolean?
86
- new(crt)
87
- end
88
-
89
69
  def mapper_for(crt)
90
70
  HARDWARE_TYPES.fetch(crt.hardware_type) do
91
71
  raise UnsupportedTypeError,
@@ -94,11 +74,6 @@ module Badline
94
74
  end
95
75
  end
96
76
 
97
- # What save_setup writes first: a cartridge from a CRT image, or a
98
- # GEO-RAM.
99
- CRT_SETUP = 0
100
- GEO_RAM_SETUP = 1
101
-
102
77
  # Keeps the image it was built from, so a snapshot's machine can build
103
78
  # the same cartridge before the snapshot's state goes into it.
104
79
  def initialize(crt)
@@ -191,8 +166,6 @@ module Badline
191
166
  raise(ArgumentError, "#{self.class.name}: a window that isn't among its banks")
192
167
  end
193
168
 
194
- def window_at(index, windows) = index.negative? ? nil : windows.fetch(index)
195
-
196
169
  def nmi=(level)
197
170
  return if @nmi == level
198
171
 
@@ -256,3 +229,4 @@ module Badline
256
229
  end
257
230
  end
258
231
  end
232
+ require "badline/c64/cartridge/keyed_state"
@@ -36,13 +36,38 @@ module Badline
36
36
  # traps, or an empty one.
37
37
  def mounted_path = @dos.nil? ? "" : @dos.path
38
38
 
39
- # Records what the KERNAL prints through CHROUT (ChroutTrap).
39
+ # Records what the KERNAL prints through CHROUT (KernalTrap::Chrout).
40
40
  def capture_output
41
- @capture_output ||= ChroutTrap.new(cpu:, bus: trap_bus, layout: trap_layout).tap do |trap|
42
- cpu.install_trap(ChroutTrap::ADDRESS) { trap.call }
41
+ @capture_output ||= KernalTrap::Chrout.new(cpu:, bus: trap_bus, layout: trap_layout).tap do |trap|
42
+ cpu.install_trap(KernalTrap::Chrout::ADDRESS) { trap.call }
43
43
  end
44
44
  end
45
45
 
46
+ # Device 8 through the KERNAL traps in a keyed snapshot module
47
+ # (BL-TRAPDOS): the storage under storage.*, the DOS, the serial
48
+ # trap under serial.* and the SAVE trap under save.*. Nothing mounted
49
+ # writes nothing.
50
+ def save_trap_dos_fields(out)
51
+ dos = @dos
52
+ return unless dos
53
+
54
+ Snapshot::StorageSetup.write_fields(dos.storage, out)
55
+ dos.save_fields(out)
56
+ @serial_trap&.save_fields(out, "serial.")
57
+ @save_trap&.save_fields(out, "save.")
58
+ end
59
+
60
+ # `detached` mounts a disk image write-protected and no directory.
61
+ def load_trap_dos_fields(input, detached: false)
62
+ unmount
63
+ return if input.missing?
64
+
65
+ mount(Snapshot::StorageSetup.read_fields(input, detached:))
66
+ @dos&.load_fields(input)
67
+ @serial_trap&.load_fields(input, "serial.")
68
+ @save_trap&.load_fields(input, "save.")
69
+ end
70
+
46
71
  private
47
72
 
48
73
  def install_kernal_traps(layout)
@@ -62,28 +87,6 @@ module Badline
62
87
  @serial_trap = nil
63
88
  @save_trap = nil
64
89
  end
65
-
66
- # The disk device 8 serves through the traps, with the traps' own
67
- # state: open channels, the drive's status and RAM, a SAVE under way.
68
- def save_trap_dos(out)
69
- out.boolean(!@dos.nil?)
70
- return unless @dos
71
-
72
- @dos.save_state(out)
73
- @serial_trap.save_state(out)
74
- @save_trap.save_state(out)
75
- end
76
-
77
- def load_trap_dos(input)
78
- return unmount unless input.boolean?
79
-
80
- storage = Snapshot::StorageSetup.read(input)
81
- unmount
82
- mount(storage)
83
- @dos.load_state(input)
84
- @serial_trap.load_state(input)
85
- @save_trap.load_state(input)
86
- end
87
90
  end
88
91
  end
89
92
  end