badline 0.13.0 → 0.15.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 (286) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +72 -1
  3. data/CLAUDE.md +42 -12
  4. data/CONTRIBUTING.md +4 -3
  5. data/README.md +76 -13
  6. data/doc/media.md +2 -1
  7. data/doc/pinned-behaviour.md +174 -0
  8. data/doc/snapshot-format.md +1197 -0
  9. data/doc/snapshots.md +90 -40
  10. data/lib/badline/audio/bare_player.rb +46 -17
  11. data/lib/badline/audio/machine_player.rb +6 -10
  12. data/lib/badline/audio/renderer.rb +32 -21
  13. data/lib/badline/c128/banks.rb +7 -6
  14. data/lib/badline/c128/bus/keyed_state.rb +45 -0
  15. data/lib/badline/c128/bus.rb +36 -4
  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 +27 -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/extra_sids.rb +80 -0
  31. data/lib/badline/c64/bus/keyed_state.rb +41 -0
  32. data/lib/badline/c64/bus.rb +3 -1
  33. data/lib/badline/c64/cartridge/action_replay.rb +6 -15
  34. data/lib/badline/c64/cartridge/comal80.rb +1 -0
  35. data/lib/badline/c64/cartridge/dinamic.rb +3 -1
  36. data/lib/badline/c64/cartridge/easy_flash.rb +0 -15
  37. data/lib/badline/c64/cartridge/epyx_fastload.rb +0 -8
  38. data/lib/badline/c64/cartridge/final_cartridge3.rb +1 -12
  39. data/lib/badline/c64/cartridge/flash.rb +3 -3
  40. data/lib/badline/c64/cartridge/fun_play.rb +2 -1
  41. data/lib/badline/c64/cartridge/g_mod2.rb +0 -12
  42. data/lib/badline/c64/cartridge/game_system.rb +2 -1
  43. data/lib/badline/c64/cartridge/geo_ram.rb +0 -16
  44. data/lib/badline/c64/cartridge/kcs_power.rb +0 -8
  45. data/lib/badline/c64/cartridge/keyed_state.rb +514 -0
  46. data/lib/badline/c64/cartridge/magic_desk.rb +2 -1
  47. data/lib/badline/c64/cartridge/ocean.rb +1 -0
  48. data/lib/badline/c64/cartridge/pagefox.rb +1 -10
  49. data/lib/badline/c64/cartridge/retro_replay.rb +0 -8
  50. data/lib/badline/c64/cartridge/rgcd.rb +2 -11
  51. data/lib/badline/c64/cartridge/super_games.rb +3 -12
  52. data/lib/badline/c64/cartridge/zaxxon.rb +1 -16
  53. data/lib/badline/c64/cartridge.rb +4 -30
  54. data/lib/badline/c64/kernal_traps.rb +28 -25
  55. data/lib/badline/c64/keyed_modules.rb +115 -0
  56. data/lib/badline/c64/keyed_state.rb +68 -0
  57. data/lib/badline/c64/reu/dma.rb +0 -4
  58. data/lib/badline/c64/reu/keyed_state.rb +140 -0
  59. data/lib/badline/c64/reu/ram.rb +0 -15
  60. data/lib/badline/c64/reu.rb +1 -3
  61. data/lib/badline/c64/true_drives.rb +56 -5
  62. data/lib/badline/c64.rb +7 -3
  63. data/lib/badline/chips/cia/interrupt_register.rb +0 -17
  64. data/lib/badline/chips/cia/keyed_parts.rb +213 -0
  65. data/lib/badline/chips/cia/serial.rb +0 -4
  66. data/lib/badline/chips/cia/time_of_day.rb +0 -22
  67. data/lib/badline/chips/cia/timer.rb +0 -20
  68. data/lib/badline/chips/cia.rb +2 -2
  69. data/lib/badline/chips/cpu/core.rb +18 -2
  70. data/lib/badline/chips/cpu/keyed_state.rb +177 -0
  71. data/lib/badline/chips/cpu/microcode.rb +16 -0
  72. data/lib/badline/chips/cpu/port.rb +20 -3
  73. data/lib/badline/chips/cpu.rb +5 -0
  74. data/lib/badline/chips/sid/decimator.rb +10 -8
  75. data/lib/badline/chips/sid/envelope.rb +1 -6
  76. data/lib/badline/chips/sid/filter.rb +0 -14
  77. data/lib/badline/chips/sid/keyed_parts.rb +286 -0
  78. data/lib/badline/chips/sid/voice.rb +0 -10
  79. data/lib/badline/chips/sid/waveform.rb +0 -2
  80. data/lib/badline/chips/sid.rb +1 -2
  81. data/lib/badline/chips/ted/beam.rb +118 -0
  82. data/lib/badline/chips/ted/fetches.rb +199 -0
  83. data/lib/badline/chips/ted/keyed_state.rb +156 -0
  84. data/lib/badline/chips/ted/modes.rb +134 -0
  85. data/lib/badline/chips/ted/palette.rb +43 -0
  86. data/lib/badline/chips/ted/picture.rb +182 -0
  87. data/lib/badline/chips/ted/sound/output.rb +86 -0
  88. data/lib/badline/chips/ted/sound.rb +284 -0
  89. data/lib/badline/chips/ted/timers.rb +66 -0
  90. data/lib/badline/chips/ted.rb +190 -0
  91. data/lib/badline/chips/tia.rb +61 -0
  92. data/lib/badline/chips/vdc/keyed_parts.rb +105 -0
  93. data/lib/badline/chips/vdc/painter.rb +0 -12
  94. data/lib/badline/chips/vdc/raster.rb +0 -16
  95. data/lib/badline/chips/vdc/window.rb +0 -9
  96. data/lib/badline/chips/vdc.rb +1 -2
  97. data/lib/badline/chips/via/control_lines.rb +0 -15
  98. data/lib/badline/chips/via/interrupt_register.rb +0 -9
  99. data/lib/badline/chips/via/keyed_parts.rb +163 -0
  100. data/lib/badline/chips/via/shift_register.rb +0 -14
  101. data/lib/badline/chips/via/timer1.rb +0 -13
  102. data/lib/badline/chips/via/timer2.rb +0 -18
  103. data/lib/badline/chips/via.rb +1 -2
  104. data/lib/badline/chips/vic/border_mask.rb +0 -10
  105. data/lib/badline/chips/vic/collisions.rb +0 -11
  106. data/lib/badline/chips/vic/color_patches.rb +0 -11
  107. data/lib/badline/chips/vic/display_state.rb +0 -19
  108. data/lib/badline/chips/vic/graphics_shifter.rb +0 -4
  109. data/lib/badline/chips/vic/keyed_parts.rb +228 -0
  110. data/lib/badline/chips/vic/keyed_sprites.rb +225 -0
  111. data/lib/badline/chips/vic/keyed_state.rb +102 -0
  112. data/lib/badline/chips/vic/register_log.rb +0 -18
  113. data/lib/badline/chips/vic/registers.rb +0 -9
  114. data/lib/badline/chips/vic/sequencer.rb +0 -2
  115. data/lib/badline/chips/vic/sprite/internal_bus.rb +0 -11
  116. data/lib/badline/chips/vic/sprite.rb +0 -2
  117. data/lib/badline/chips/vic/sprites.rb +0 -3
  118. data/lib/badline/chips/vic.rb +6 -2
  119. data/lib/badline/chips/vic_i/keyed_parts.rb +142 -0
  120. data/lib/badline/chips/vic_i/painter.rb +0 -4
  121. data/lib/badline/chips/vic_i/sound.rb +38 -46
  122. data/lib/badline/chips/vic_i.rb +2 -2
  123. data/lib/badline/chips/z80/core.rb +2 -2
  124. data/lib/badline/chips/z80/keyed_state.rb +73 -0
  125. data/lib/badline/chips.rb +10 -0
  126. data/lib/badline/core.rb +4 -21
  127. data/lib/badline/drives/datasette/keyed_state.rb +43 -0
  128. data/lib/badline/drives/datasette.rb +62 -12
  129. data/lib/badline/drives/engine/core.rb +3 -0
  130. data/lib/badline/drives/engine/serial_lines.rb +4 -7
  131. data/lib/badline/drives/model1541/bus.rb +0 -13
  132. data/lib/badline/drives/model1541/disk.rb +9 -8
  133. data/lib/badline/drives/model1541/keyed_state.rb +251 -0
  134. data/lib/badline/drives/model1541/mechanism.rb +0 -2
  135. data/lib/badline/drives/model1541.rb +1 -2
  136. data/lib/badline/drives/model1571/bus.rb +0 -13
  137. data/lib/badline/drives/model1571/keyed_state.rb +108 -0
  138. data/lib/badline/drives/model1571/wd1770.rb +0 -14
  139. data/lib/badline/drives/model1571.rb +1 -2
  140. data/lib/badline/drives/model1581/bus.rb +0 -13
  141. data/lib/badline/drives/model1581/disk.rb +0 -4
  142. data/lib/badline/drives/model1581/keyed_state.rb +280 -0
  143. data/lib/badline/drives/model1581/mechanism.rb +0 -21
  144. data/lib/badline/drives/model1581/slot.rb +0 -18
  145. data/lib/badline/drives/model1581/wd1772.rb +0 -2
  146. data/lib/badline/drives/model1581.rb +2 -2
  147. data/lib/badline/drives.rb +8 -0
  148. data/lib/badline/frontend/app.rb +3 -3
  149. data/lib/badline/frontend/boot.rb +4 -2
  150. data/lib/badline/frontend/controls.rb +25 -5
  151. data/lib/badline/frontend/drive_led.rb +44 -21
  152. data/lib/badline/frontend/keys.rb +14 -2
  153. data/lib/badline/frontend/media_slots.rb +11 -1
  154. data/lib/badline/frontend/menu/menu_dialogs.rb +4 -3
  155. data/lib/badline/frontend/menu/menu_media.rb +13 -3
  156. data/lib/badline/frontend/menu/menu_pages.rb +8 -8
  157. data/lib/badline/frontend/menu/menu_ports.rb +13 -1
  158. data/lib/badline/frontend/screen.rb +33 -12
  159. data/lib/badline/frontend/screens.rb +2 -1
  160. data/lib/badline/frontend/snapshots.rb +12 -1
  161. data/lib/badline/frontend/sound.rb +4 -4
  162. data/lib/badline/frontend/timeline.rb +43 -12
  163. data/lib/badline/input/matrix_typist.rb +98 -0
  164. data/lib/badline/input.rb +1 -0
  165. data/lib/badline/kernal_trap/chrout.rb +45 -0
  166. data/lib/badline/kernal_trap/dos/channel.rb +0 -27
  167. data/lib/badline/kernal_trap/dos/channels.rb +0 -25
  168. data/lib/badline/kernal_trap/dos/memory.rb +0 -9
  169. data/lib/badline/kernal_trap/dos/status.rb +0 -8
  170. data/lib/badline/kernal_trap/dos/write_file.rb +0 -18
  171. data/lib/badline/kernal_trap/dos.rb +0 -19
  172. data/lib/badline/kernal_trap/file_routine.rb +4 -4
  173. data/lib/badline/kernal_trap/keyed_state.rb +150 -0
  174. data/lib/badline/kernal_trap/layout.rb +75 -11
  175. data/lib/badline/kernal_trap/load.rb +14 -16
  176. data/lib/badline/kernal_trap/routine.rb +12 -0
  177. data/lib/badline/kernal_trap/save.rb +8 -18
  178. data/lib/badline/kernal_trap/serial.rb +1 -17
  179. data/lib/badline/kernal_trap.rb +2 -0
  180. data/lib/badline/machine/keyboard_buffer.rb +49 -0
  181. data/lib/badline/machine/settling.rb +29 -0
  182. data/lib/badline/machine.rb +29 -6
  183. data/lib/badline/media/boot_disk.rb +14 -2
  184. data/lib/badline/media/extensions.rb +10 -0
  185. data/lib/badline/media/plus4_media.rb +68 -0
  186. data/lib/badline/media/program.rb +20 -6
  187. data/lib/badline/media.rb +33 -12
  188. data/lib/badline/memory.rb +3 -6
  189. data/lib/badline/options/family.rb +6 -2
  190. data/lib/badline/options/help.rb +66 -16
  191. data/lib/badline/options/table.rb +17 -2
  192. data/lib/badline/options/validation.rb +26 -2
  193. data/lib/badline/options.rb +42 -9
  194. data/lib/badline/plus4/acia.rb +45 -0
  195. data/lib/badline/plus4/bus.rb +375 -0
  196. data/lib/badline/plus4/cassette_sense.rb +21 -0
  197. data/lib/badline/plus4/cpu.rb +10 -0
  198. data/lib/badline/plus4/cpu_port.rb +93 -0
  199. data/lib/badline/plus4/keyboard_port.rb +76 -0
  200. data/lib/badline/plus4/keyed_modules.rb +99 -0
  201. data/lib/badline/plus4/keyed_parts.rb +143 -0
  202. data/lib/badline/plus4/keyed_state.rb +41 -0
  203. data/lib/badline/plus4/model.rb +38 -0
  204. data/lib/badline/plus4/user_port.rb +31 -0
  205. data/lib/badline/plus4.rb +240 -0
  206. data/lib/badline/roms/README +38 -0
  207. data/lib/badline/roms/plus4/basic.rom +0 -0
  208. data/lib/badline/roms/plus4/function-high.rom +0 -0
  209. data/lib/badline/roms/plus4/function-low.rom +0 -0
  210. data/lib/badline/roms/plus4/kernal-ntsc.rom +0 -0
  211. data/lib/badline/roms/plus4/kernal-pal.rom +0 -0
  212. data/lib/badline/snapshot/c128_setup.rb +9 -20
  213. data/lib/badline/snapshot/c64_setup.rb +28 -24
  214. data/lib/badline/snapshot/container.rb +6 -0
  215. data/lib/badline/snapshot/field_doc/markdown.rb +113 -0
  216. data/lib/badline/snapshot/field_doc.rb +148 -0
  217. data/lib/badline/snapshot/image.rb +143 -28
  218. data/lib/badline/snapshot/keyed/cursor.rb +40 -0
  219. data/lib/badline/snapshot/keyed/modules.rb +66 -0
  220. data/lib/badline/snapshot/keyed/reader.rb +188 -0
  221. data/lib/badline/snapshot/keyed/recorder.rb +88 -0
  222. data/lib/badline/snapshot/keyed/report.rb +26 -0
  223. data/lib/badline/snapshot/keyed/writer.rb +55 -0
  224. data/lib/badline/snapshot/keyed.rb +64 -0
  225. data/lib/badline/snapshot/machine_state.rb +17 -64
  226. data/lib/badline/snapshot/plus4_setup.rb +39 -0
  227. data/lib/badline/snapshot/storage_setup.rb +29 -34
  228. data/lib/badline/snapshot/vic20_setup.rb +6 -15
  229. data/lib/badline/snapshot/vice/reu1764.rb +2 -2
  230. data/lib/badline/snapshot/vice.rb +7 -31
  231. data/lib/badline/snapshot.rb +48 -20
  232. data/lib/badline/storage/crt_file.rb +3 -1
  233. data/lib/badline/storage/g64_image.rb +9 -4
  234. data/lib/badline/storage/tap.rb +57 -20
  235. data/lib/badline/support/checkpoint.rb +3 -2
  236. data/lib/badline/support/plus4_checkpoint.rb +42 -0
  237. data/lib/badline/version.rb +1 -1
  238. data/lib/badline/vic20/attachments.rb +3 -0
  239. data/lib/badline/vic20/bus/keyed_state.rb +52 -0
  240. data/lib/badline/vic20/bus.rb +1 -2
  241. data/lib/badline/vic20/keyed_modules.rb +73 -0
  242. data/lib/badline/vic20/keyed_state.rb +45 -0
  243. data/lib/badline/vic20.rb +5 -5
  244. data/lib/badline.rb +1 -0
  245. metadata +75 -42
  246. data/lib/badline/c128/bus/saved_state.rb +0 -53
  247. data/lib/badline/c128/saved_state.rb +0 -117
  248. data/lib/badline/c64/bus/saved_state.rb +0 -68
  249. data/lib/badline/c64/cartridge/flash/saved_state.rb +0 -40
  250. data/lib/badline/c64/cartridge/retro_replay/saved_state.rb +0 -47
  251. data/lib/badline/c64/cartridge/saved_state.rb +0 -63
  252. data/lib/badline/c64/reu/dma/saved_state.rb +0 -52
  253. data/lib/badline/c64/reu/saved_state.rb +0 -55
  254. data/lib/badline/c64/saved_state.rb +0 -112
  255. data/lib/badline/chips/cia/saved_state.rb +0 -49
  256. data/lib/badline/chips/cia/serial/saved_state.rb +0 -45
  257. data/lib/badline/chips/cpu/saved_state.rb +0 -117
  258. data/lib/badline/chips/sid/envelope/saved_state.rb +0 -70
  259. data/lib/badline/chips/sid/filter/saved_state.rb +0 -42
  260. data/lib/badline/chips/sid/saved_state.rb +0 -52
  261. data/lib/badline/chips/sid/waveform/saved_state.rb +0 -68
  262. data/lib/badline/chips/vdc/saved_state.rb +0 -36
  263. data/lib/badline/chips/via/saved_state.rb +0 -44
  264. data/lib/badline/chips/vic/graphics_shifter/saved_state.rb +0 -40
  265. data/lib/badline/chips/vic/saved_state.rb +0 -82
  266. data/lib/badline/chips/vic/sequencer/saved_state.rb +0 -91
  267. data/lib/badline/chips/vic/sprite/saved_state.rb +0 -66
  268. data/lib/badline/chips/vic/sprites/saved_state.rb +0 -59
  269. data/lib/badline/chips/vic_i/painter/saved_state.rb +0 -40
  270. data/lib/badline/chips/vic_i/saved_state.rb +0 -55
  271. data/lib/badline/chips/z80/saved_state.rb +0 -71
  272. data/lib/badline/chrout_trap.rb +0 -43
  273. data/lib/badline/debug_register.rb +0 -28
  274. data/lib/badline/drives/datasette/saved_state.rb +0 -37
  275. data/lib/badline/drives/model1541/disk/saved_state.rb +0 -88
  276. data/lib/badline/drives/model1541/mechanism/saved_state.rb +0 -72
  277. data/lib/badline/drives/model1541/saved_state.rb +0 -44
  278. data/lib/badline/drives/model1571/saved_state.rb +0 -47
  279. data/lib/badline/drives/model1581/disk/saved_state.rb +0 -89
  280. data/lib/badline/drives/model1581/saved_state.rb +0 -42
  281. data/lib/badline/drives/model1581/wd1772/saved_state.rb +0 -68
  282. data/lib/badline/keyboard_buffer.rb +0 -33
  283. data/lib/badline/snapshot/setup.rb +0 -31
  284. data/lib/badline/snapshot/state.rb +0 -179
  285. data/lib/badline/vic20/bus/saved_state.rb +0 -35
  286. data/lib/badline/vic20/saved_state.rb +0 -110
@@ -0,0 +1,514 @@
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 each mapper's own registers, from
10
+ # which a load works out where ROML and ROMH point again.
11
+ #
12
+ # Snapshots from 0.14.0 name the bank or view each window shows
13
+ # instead (window_labels), under roml and romh. A load reads those
14
+ # when the mapper's register isn't there, and works the register out
15
+ # from the windows.
16
+ module KeyedState
17
+ def save_fields(out)
18
+ save_setup_fields(out)
19
+ out.int("exrom", @exrom).int("game", @game).bool("nmi", @nmi).bool("button", @button == true)
20
+ save_mapper_fields(out)
21
+ out
22
+ end
23
+
24
+ def load_fields(input)
25
+ @exrom = input.masked("exrom", @exrom, 1)
26
+ @game = input.masked("game", @game, 1)
27
+ @nmi = input.bool("nmi", @nmi)
28
+ @button = input.bool("button", false)
29
+ load_mapper_fields(input)
30
+ end
31
+
32
+ private
33
+
34
+ def save_setup_fields(out)
35
+ out.bytes("crt", Storage::CRTFile.encode(@crt))
36
+ save_jumper_fields(out)
37
+ end
38
+
39
+ def save_jumper_fields(_out); end
40
+
41
+ def save_mapper_fields(_out); end
42
+
43
+ def load_mapper_fields(_input); end
44
+
45
+ # The register at `key`, cut to `mask`, or nil for a snapshot that
46
+ # names the windows instead, with the windows put back from their
47
+ # names.
48
+ def latch_or_windows(input, key, default, mask)
49
+ return input.masked(key, default, mask) if input.tags.key?(key)
50
+
51
+ load_window_fields(input)
52
+ nil
53
+ end
54
+
55
+ # The number of the bank among `banks` that `window` shows, or of the
56
+ # first the image leaves out for erased ROM.
57
+ def number_showing(banks, window)
58
+ number = banks.index { |each| each.equal?(window) }
59
+ return number if number
60
+ return 0 unless window.equal?(EMPTY_BANK)
61
+
62
+ banks.index(nil) || banks.length
63
+ end
64
+
65
+ # The name of each of `windows`, in the same order: bank<N> for the
66
+ # mapper's banks, and empty for erased ROM.
67
+ def window_labels
68
+ banks = @banks
69
+ return [] unless banks
70
+
71
+ [*Array.new(banks.length) { |number| "bank#{number}" }, "empty"]
72
+ end
73
+
74
+ def load_window_fields(input)
75
+ choices = windows
76
+ return unless choices
77
+
78
+ labels = window_labels
79
+ @roml = window_named(input.choice("roml", [*labels, "none"], "none"), choices, labels)
80
+ @romh = window_named(input.choice("romh", [*labels, "none"], "none"), choices, labels)
81
+ end
82
+
83
+ def window_named(label, choices, labels)
84
+ index = labels.index(label)
85
+ index.nil? ? nil : choices.fetch(index)
86
+ end
87
+ end
88
+
89
+ include KeyedState
90
+
91
+ class << self
92
+ # A cartridge built afresh from the CRT image and the jumpers a
93
+ # keyed snapshot module holds, before load_fields puts its state in.
94
+ def from_fields(input)
95
+ kb = input.int("geo_ram_kb", 0)
96
+ return GeoRAM.new(size: kb) if kb.positive?
97
+
98
+ crt = Storage::CRTFile.new(bytes: input.bytes("crt", "".b))
99
+ mapper_for(crt).from_jumper_fields(crt, input)
100
+ end
101
+
102
+ def from_jumper_fields(crt, _input) = new(crt)
103
+ end
104
+
105
+ # One bank register, under bank, selecting the bank ROML shows.
106
+ module BankLatch
107
+ private
108
+
109
+ def save_mapper_fields(out)
110
+ out.int("bank", @bank)
111
+ end
112
+
113
+ def load_mapper_fields(input)
114
+ latch = latch_or_windows(input, "bank", @bank, bank_latch_mask)
115
+ if latch.nil?
116
+ @bank = number_showing(@banks, @roml)
117
+ else
118
+ @bank = latch
119
+ show_bank
120
+ end
121
+ end
122
+
123
+ def show_bank
124
+ @roml = bank(@banks, @bank)
125
+ end
126
+ end
127
+
128
+ # Two banks of ROM, one behind each window: roml<N> and romh<N>, both
129
+ # selected by one bank register, under bank.
130
+ module SplitBanks
131
+ private
132
+
133
+ def save_mapper_fields(out)
134
+ out.int("bank", @bank)
135
+ end
136
+
137
+ def load_mapper_fields(input)
138
+ latch = latch_or_windows(input, "bank", @bank, bank_latch_mask)
139
+ if latch.nil?
140
+ @bank = number_showing(@roml_banks, @roml)
141
+ else
142
+ @bank = latch
143
+ @roml = bank(@roml_banks, @bank)
144
+ @romh = bank(@romh_banks, @bank)
145
+ end
146
+ end
147
+
148
+ def window_labels
149
+ [*Array.new(@roml_banks.length) { |number| "roml#{number}" },
150
+ *Array.new(@romh_banks.length) { |number| "romh#{number}" }, "empty"]
151
+ end
152
+ end
153
+
154
+ class Ocean
155
+ include BankLatch
156
+
157
+ private
158
+
159
+ def bank_latch_mask = @bank_mask
160
+
161
+ def show_bank = select_bank(@bank)
162
+ end
163
+
164
+ class FunPlay
165
+ include BankLatch
166
+
167
+ private
168
+
169
+ def bank_latch_mask = 0x0f
170
+ end
171
+
172
+ class GameSystem
173
+ include BankLatch
174
+
175
+ private
176
+
177
+ def bank_latch_mask = 0x3f
178
+ end
179
+
180
+ class Dinamic
181
+ include BankLatch
182
+
183
+ private
184
+
185
+ def bank_latch_mask = 0x0f
186
+ end
187
+
188
+ class MagicDesk
189
+ include BankLatch
190
+
191
+ private
192
+
193
+ def bank_latch_mask = @bank_mask
194
+ end
195
+
196
+ class SuperGames
197
+ include SplitBanks
198
+
199
+ private
200
+
201
+ def bank_latch_mask = 0x03
202
+
203
+ def save_mapper_fields(out)
204
+ super
205
+ out.bool("locked", @locked)
206
+ end
207
+
208
+ def load_mapper_fields(input)
209
+ super
210
+ @locked = input.bool("locked", @locked)
211
+ end
212
+ end
213
+
214
+ class Comal80
215
+ include SplitBanks
216
+
217
+ private
218
+
219
+ def bank_latch_mask = 0x07
220
+ end
221
+
222
+ class FinalCartridge3
223
+ include SplitBanks
224
+
225
+ private
226
+
227
+ def bank_latch_mask = @bank_mask
228
+
229
+ def save_mapper_fields(out)
230
+ super
231
+ out.bool("register_visible", @register_visible).bool("phi1_ultimax", @phi1_ultimax)
232
+ end
233
+
234
+ def load_mapper_fields(input)
235
+ super
236
+ @io_bank = @roml
237
+ @register_visible = input.bool("register_visible", @register_visible)
238
+ @phi1_ultimax = input.bool("phi1_ultimax", @phi1_ultimax)
239
+ end
240
+ end
241
+
242
+ class RGCD
243
+ include BankLatch
244
+
245
+ private
246
+
247
+ def bank_latch_mask = @bank_mask
248
+
249
+ def save_mapper_fields(out)
250
+ super
251
+ out.bool("disabled", @disabled)
252
+ end
253
+
254
+ def load_mapper_fields(input)
255
+ super
256
+ @disabled = input.bool("disabled", @disabled)
257
+ end
258
+ end
259
+
260
+ class Zaxxon
261
+ class High
262
+ def save_fields(out)
263
+ out.int("high_bank", @number)
264
+ end
265
+
266
+ def load_fields(input)
267
+ select(input.int("high_bank", 0).clamp(0, @banks.length))
268
+ end
269
+ end
270
+
271
+ private
272
+
273
+ def save_mapper_fields(out) = @romh.save_fields(out)
274
+
275
+ def load_mapper_fields(input) = @romh.load_fields(input)
276
+ end
277
+
278
+ class EpyxFastload
279
+ private
280
+
281
+ # How long ago the capacitor was discharged, on the machine's clock.
282
+ def save_mapper_fields(out)
283
+ out.int("discharged_ago", now_cycles - @discharged_at)
284
+ end
285
+
286
+ def load_mapper_fields(input)
287
+ @discharged_at = now_cycles - input.int("discharged_ago", now_cycles - @discharged_at)
288
+ end
289
+
290
+ def now_cycles
291
+ clock = @clock
292
+ clock ? clock.call : 0
293
+ end
294
+ end
295
+
296
+ class ActionReplay
297
+ private
298
+
299
+ def window_labels
300
+ ["through", "isolated", "contended", *Array.new(@rom_banks.length) { |number| "rom#{number}" }, "empty"]
301
+ end
302
+
303
+ def save_mapper_fields(out)
304
+ out.int("control", @control).bool("active", @active).bytes("ram", @ram_data)
305
+ end
306
+
307
+ def load_mapper_fields(input)
308
+ latch = latch_or_windows(input, "control", @control, 0xff)
309
+ if latch.nil?
310
+ load_legacy_fields(input)
311
+ else
312
+ @control = latch
313
+ @rom = bank(@rom_banks, (@control >> 3) & 0x03)
314
+ configure(@control)
315
+ end
316
+ @active = input.bool("active", @active)
317
+ input.bytes_into("ram", @ram_data)
318
+ end
319
+
320
+ # The ROM bank by name, and whether I/O 2 shows the RAM, from which
321
+ # the register is worked out.
322
+ def load_legacy_fields(input)
323
+ labels = window_labels
324
+ @rom = window_named(input.choice("rom", labels, "rom0"), windows, labels)
325
+ @io2_ram = input.bool("io2_ram", @io2_ram)
326
+ number = number_showing(@rom_banks, @rom) << 3
327
+ @control = if @roml.equal?(@contended) || @romh.equal?(@isolated)
328
+ 0x22 | number
329
+ else
330
+ mode = @exrom.zero? ? 1 - @game : 3 - @game
331
+ mode | (@io2_ram ? 0x20 : 0) | number
332
+ end
333
+ end
334
+ end
335
+
336
+ class KCSPower
337
+ private
338
+
339
+ def save_mapper_fields(out) = out.bytes("io_ram", @io_ram)
340
+
341
+ def load_mapper_fields(input) = input.bytes_into("io_ram", @io_ram)
342
+ end
343
+
344
+ class Pagefox
345
+ private
346
+
347
+ def window_labels
348
+ [*Array.new(@roml_banks.length) { |number| "roml#{number}" },
349
+ *Array.new(@romh_banks.length) { |number| "romh#{number}" },
350
+ *Array.new(@ram_banks.length) { |number| "ram#{number}" }, "open_bus", "empty"]
351
+ end
352
+
353
+ def save_mapper_fields(out)
354
+ out.int("control", @control)
355
+ @ram_banks.each_with_index { |ram, number| out.bytes("ram#{number}.data", ram.data) }
356
+ end
357
+
358
+ def load_mapper_fields(input)
359
+ latch = latch_or_windows(input, "control", @control, 0xff)
360
+ if latch.nil?
361
+ selected = (0...8).find { |select| window(select >> 1, select & 0x01) == [@roml, @romh] } || 0
362
+ @control = (selected << 1) | (@exrom == 1 && @game == 1 ? 0x10 : 0)
363
+ else
364
+ @control = latch
365
+ @roml, @romh = window((@control >> 2) & 0x03, (@control >> 1) & 0x01)
366
+ end
367
+ @ram_banks.each_with_index { |ram, number| input.bytes_into("ram#{number}.data", ram.data) }
368
+ end
369
+ end
370
+
371
+ class GeoRAM
372
+ private
373
+
374
+ def save_setup_fields(out) = out.int("geo_ram_kb", @size)
375
+
376
+ def save_mapper_fields(out)
377
+ out.bytes("ram", @ram_data).int("block", @block).int("page", @page).int("window", @window)
378
+ end
379
+
380
+ def load_mapper_fields(input)
381
+ input.bytes_into("ram", @ram_data)
382
+ @block = input.int("block", @block)
383
+ @page = input.int("page", @page)
384
+ @window = input.int("window", @window)
385
+ end
386
+ end
387
+
388
+ class Flash
389
+ # The array, the command state by name, how far through a command
390
+ # sequence, the toggle bit, a timed operation's end in cycles from
391
+ # now, and the windows handed out so far, under `prefix`.
392
+ module KeyedState
393
+ STATE_NAMES = STATES.map(&:to_s).freeze
394
+
395
+ def save_fields(out, prefix)
396
+ out.bytes("#{prefix}data", @data).text("#{prefix}state", @state.to_s)
397
+ out.text("#{prefix}base_state", @base_state.to_s).int("#{prefix}cycle", @cycle)
398
+ out.int("#{prefix}toggle", @toggle)
399
+ done_at = @done_at
400
+ out.int("#{prefix}done_in", done_at - @clock.call) unless done_at.nil?
401
+ out.optional_int("#{prefix}programmed", @programmed).optional_int("#{prefix}remaining", @remaining)
402
+ sectors = @erasing_sectors
403
+ out.ints("#{prefix}erasing_sectors", sectors) if sectors
404
+ out.ints("#{prefix}windows", @windows.keys).ints("#{prefix}status_windows", @status_windows.keys)
405
+ end
406
+
407
+ def load_fields(input, prefix)
408
+ input.bytes_into("#{prefix}data", @data)
409
+ @state = state_named(input.choice("#{prefix}state", STATE_NAMES, "read"))
410
+ @base_state = state_named(input.choice("#{prefix}base_state", STATE_NAMES, "read"))
411
+ @cycle = input.int("#{prefix}cycle", @cycle)
412
+ @toggle = input.int("#{prefix}toggle", @toggle)
413
+ done_in = input.optional_int("#{prefix}done_in")
414
+ @done_at = done_in.nil? ? nil : @clock.call + done_in
415
+ @programmed = input.optional_int("#{prefix}programmed")
416
+ @remaining = input.optional_int("#{prefix}remaining")
417
+ sectors = input.ints("#{prefix}erasing_sectors", [])
418
+ @erasing_sectors = sectors.empty? ? nil : sectors.dup
419
+ rebuild_windows(input.ints("#{prefix}windows", []), input.ints("#{prefix}status_windows", []))
420
+ end
421
+
422
+ private
423
+
424
+ def state_named(name)
425
+ STATES.find { |state| state.to_s == name } || :read
426
+ end
427
+ end
428
+
429
+ include KeyedState
430
+ end
431
+
432
+ class GMod2
433
+ private
434
+
435
+ def save_mapper_fields(out)
436
+ out.int("bank", @bank).bool("flash_writes", @flash_writes)
437
+ @flash.save_fields(out, "flash.")
438
+ end
439
+
440
+ def load_mapper_fields(input)
441
+ @bank = input.int("bank", @bank)
442
+ @flash_writes = input.bool("flash_writes", @flash_writes)
443
+ @flash.load_fields(input, "flash.")
444
+ @roml = @flash.window(@bank * BANK_SIZE)
445
+ end
446
+ end
447
+
448
+ class EasyFlash
449
+ private
450
+
451
+ def save_mapper_fields(out)
452
+ out.int("bank", @bank).int("control", @control).bytes("io_ram", @io_ram)
453
+ @low_flash.save_fields(out, "flash_low.")
454
+ @high_flash.save_fields(out, "flash_high.")
455
+ end
456
+
457
+ def load_mapper_fields(input)
458
+ @bank = input.int("bank", @bank)
459
+ @control = input.byte("control", @control)
460
+ input.bytes_into("io_ram", @io_ram)
461
+ @low_flash.load_fields(input, "flash_low.")
462
+ @high_flash.load_fields(input, "flash_high.")
463
+ map_bank
464
+ end
465
+ end
466
+
467
+ class RetroReplay
468
+ CONFIG_NAMES = CONTROL_MODES.map(&:to_s).freeze
469
+
470
+ # The flash and bank jumpers, as the board has them set.
471
+ def self.from_jumper_fields(crt, input)
472
+ new(crt, flash_jumper: input.bool("jumper.flash", false), bank_jumper: input.bool("jumper.bank", false))
473
+ end
474
+
475
+ private
476
+
477
+ def save_jumper_fields(out)
478
+ out.bool("jumper.flash", @flash_jumper).bool("jumper.bank", @bank_jumper)
479
+ end
480
+
481
+ def save_mapper_fields(out)
482
+ out.bool("active", @active).bool("frozen", @frozen).bool("phi1_ultimax", @phi1_ultimax)
483
+ out.bool("ram_enabled", @ram_enabled).bool("ram_at_a000", @ram_at_a000).bool("allow_bank", @allow_bank)
484
+ out.bool("no_freeze", @no_freeze).bool("reu_mapping", @reu_mapping).bool("write_once", @write_once)
485
+ out.int("bank", @bank).text("config", @config.to_s)
486
+ @ram_data.each_with_index { |data, number| out.bytes("ram#{number}", data) }
487
+ @flash.save_fields(out, "flash.")
488
+ end
489
+
490
+ def load_mapper_fields(input)
491
+ load_flag_fields(input)
492
+ @bank = input.int("bank", @bank)
493
+ config = input.choice("config", CONFIG_NAMES, "rom8k")
494
+ @config = CONTROL_MODES.find { |mode| mode.to_s == config } || :rom8k
495
+ @ram_data.each_with_index { |data, number| input.bytes_into("ram#{number}", data) }
496
+ @flash.load_fields(input, "flash.")
497
+ map_windows
498
+ end
499
+
500
+ def load_flag_fields(input)
501
+ @active = input.bool("active", @active)
502
+ @frozen = input.bool("frozen", @frozen)
503
+ @phi1_ultimax = input.bool("phi1_ultimax", @phi1_ultimax)
504
+ @ram_enabled = input.bool("ram_enabled", @ram_enabled)
505
+ @ram_at_a000 = input.bool("ram_at_a000", @ram_at_a000)
506
+ @allow_bank = input.bool("allow_bank", @allow_bank)
507
+ @no_freeze = input.bool("no_freeze", @no_freeze)
508
+ @reu_mapping = input.bool("reu_mapping", @reu_mapping)
509
+ @write_once = input.bool("write_once", @write_once)
510
+ end
511
+ end
512
+ end
513
+ end
514
+ end
@@ -11,7 +11,8 @@ module Badline
11
11
  return if addr > 0xdeff
12
12
 
13
13
  @exrom = value.anybits?(0x80) ? 1 : 0
14
- @roml = bank(@banks, value & @bank_mask)
14
+ @bank = value & @bank_mask
15
+ @roml = bank(@banks, @bank)
15
16
  changed!
16
17
  end
17
18
 
@@ -22,6 +22,7 @@ module Badline
22
22
 
23
23
  # In 16K mode the selected bank shows through ROMH as well.
24
24
  def select_bank(number)
25
+ @bank = number
25
26
  @roml = bank(@banks, number)
26
27
  @romh = @roml if game.zero?
27
28
  end
@@ -30,19 +30,10 @@ 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)
36
+ @control = value
46
37
  half = (value >> 1) & 0x01
47
38
  chip = (value >> 2) & 0x03
48
39
  @roml, @romh = window(chip, half)
@@ -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,23 +29,14 @@ 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)
45
35
  @disabled = true
46
36
  self.mode = :off
47
37
  else
48
- @roml = bank(@banks, value & @bank_mask)
38
+ @bank = value & @bank_mask
39
+ @roml = bank(@banks, @bank)
49
40
  end
50
41
  end
51
42
 
@@ -21,21 +21,12 @@ 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)
37
- @roml = bank(@roml_banks, value & 0x03)
38
- @romh = bank(@romh_banks, value & 0x03)
27
+ @bank = value & 0x03
28
+ @roml = bank(@roml_banks, @bank)
29
+ @romh = bank(@romh_banks, @bank)
39
30
  self.mode = value.anybits?(0x04) ? :off : :rom16k
40
31
  @locked = value.anybits?(0x08)
41
32
  end
@@ -26,20 +26,13 @@ module Badline
26
26
  end
27
27
 
28
28
  def select(number)
29
+ @number = number
29
30
  @bank = @banks[number] || EMPTY_BANK
30
31
  end
31
32
 
32
33
  def peek(addr)
33
34
  @bank.peek(addr)
34
35
  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
36
  end
44
37
 
45
38
  def reset
@@ -48,14 +41,6 @@ module Badline
48
41
 
49
42
  private
50
43
 
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
44
  def install_chips(chips)
60
45
  roml, romh = banks_from(chips)
61
46
  @romh = High.new(romh)