badline 0.12.1 → 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 (565) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -1
  3. data/CLAUDE.md +86 -38
  4. data/CONTRIBUTING.md +4 -3
  5. data/README.md +9 -6
  6. data/doc/library.md +9 -9
  7. data/doc/media.md +2 -1
  8. data/doc/pinned-behaviour.md +134 -105
  9. data/doc/snapshot-format.md +1047 -0
  10. data/doc/snapshots.md +73 -43
  11. data/lib/badline/audio/bare_player.rb +53 -20
  12. data/lib/badline/audio/cli.rb +6 -6
  13. data/lib/badline/audio/jukebox.rb +2 -2
  14. data/lib/badline/audio/machine_player.rb +16 -10
  15. data/lib/badline/{media → audio}/queue.rb +1 -1
  16. data/lib/badline/audio/queued_tune.rb +6 -6
  17. data/lib/badline/audio/renderer.rb +33 -22
  18. data/lib/badline/audio/stereo.rb +1 -1
  19. data/lib/badline/c128/banks.rb +6 -3
  20. data/lib/badline/c128/bus/keyed_state.rb +45 -0
  21. data/lib/badline/c128/bus.rb +35 -20
  22. data/lib/badline/c128/color_lines.rb +2 -2
  23. data/lib/badline/c128/cpu.rb +1 -1
  24. data/lib/badline/c128/drives.rb +14 -34
  25. data/lib/badline/c128/fast_serial.rb +5 -5
  26. data/lib/badline/c128/keyed_modules.rb +117 -0
  27. data/lib/badline/c128/keyed_state.rb +91 -0
  28. data/lib/badline/c128/keys.rb +9 -0
  29. data/lib/badline/c128/mmu.rb +9 -8
  30. data/lib/badline/c128/mmu_pages.rb +21 -1
  31. data/lib/badline/c128/modes.rb +21 -11
  32. data/lib/badline/c128/z80.rb +2 -2
  33. data/lib/badline/c128/z80_turns.rb +1 -1
  34. data/lib/badline/c128.rb +21 -16
  35. data/lib/badline/c64/attachments.rb +42 -0
  36. data/lib/badline/c64/banked_ram/banking.rb +32 -0
  37. data/lib/badline/c64/banked_ram/plus256k.rb +64 -0
  38. data/lib/badline/c64/banked_ram/plus60k.rb +47 -0
  39. data/lib/badline/c64/banked_ram/unexpanded.rb +30 -0
  40. data/lib/badline/c64/banked_ram.rb +23 -0
  41. data/lib/badline/c64/bus/debug_register.rb +32 -0
  42. data/lib/badline/c64/bus/extra_sids.rb +32 -0
  43. data/lib/badline/c64/bus/fittings.rb +40 -0
  44. data/lib/badline/c64/bus/keyed_state.rb +41 -0
  45. data/lib/badline/c64/bus/pla.rb +149 -0
  46. data/lib/badline/c64/bus/roms.rb +41 -0
  47. data/lib/badline/c64/bus/sid_slots.rb +44 -0
  48. data/lib/badline/c64/bus.rb +219 -0
  49. data/lib/badline/c64/cartridge/action_replay.rb +115 -0
  50. data/lib/badline/c64/cartridge/atomic_power.rb +26 -0
  51. data/lib/badline/c64/cartridge/bank.rb +83 -0
  52. data/lib/badline/c64/cartridge/comal80.rb +52 -0
  53. data/lib/badline/c64/cartridge/dinamic.rb +36 -0
  54. data/lib/badline/c64/cartridge/easy_flash.rb +132 -0
  55. data/lib/badline/c64/cartridge/epyx_fastload.rb +73 -0
  56. data/lib/badline/c64/cartridge/final_cartridge3.rb +79 -0
  57. data/lib/badline/c64/cartridge/flash.rb +279 -0
  58. data/lib/badline/c64/cartridge/freezer.rb +35 -0
  59. data/lib/badline/c64/cartridge/fun_play.rb +40 -0
  60. data/lib/badline/c64/cartridge/g_mod2.rb +64 -0
  61. data/lib/badline/c64/cartridge/game_system.rb +42 -0
  62. data/lib/badline/c64/cartridge/geo_ram.rb +65 -0
  63. data/lib/badline/c64/cartridge/kcs_power.rb +69 -0
  64. data/lib/badline/c64/cartridge/keyed_state.rb +393 -0
  65. data/lib/badline/c64/cartridge/mach5.rb +34 -0
  66. data/lib/badline/c64/cartridge/magic_desk.rb +32 -0
  67. data/lib/badline/c64/cartridge/ocean.rb +37 -0
  68. data/lib/badline/c64/cartridge/pagefox.rb +60 -0
  69. data/lib/badline/c64/cartridge/retro_replay.rb +278 -0
  70. data/lib/badline/c64/cartridge/rex_utility.rb +31 -0
  71. data/lib/badline/c64/cartridge/rgcd.rb +50 -0
  72. data/lib/badline/c64/cartridge/simons_basic.rb +37 -0
  73. data/lib/badline/c64/cartridge/standard.rb +17 -0
  74. data/lib/badline/c64/cartridge/super_games.rb +40 -0
  75. data/lib/badline/c64/cartridge/westermann.rb +32 -0
  76. data/lib/badline/c64/cartridge/zaxxon.rb +52 -0
  77. data/lib/badline/c64/cartridge.rb +232 -0
  78. data/lib/badline/c64/color_memory.rb +27 -0
  79. data/lib/badline/{computer → c64}/common.rb +2 -2
  80. data/lib/badline/{computer → c64}/kernal_traps.rb +39 -36
  81. data/lib/badline/c64/keyed_modules.rb +111 -0
  82. data/lib/badline/c64/keyed_state.rb +68 -0
  83. data/lib/badline/c64/reu/dma.rb +271 -0
  84. data/lib/badline/c64/reu/keyed_state.rb +140 -0
  85. data/lib/badline/c64/reu/ram.rb +94 -0
  86. data/lib/badline/c64/reu/trigger.rb +34 -0
  87. data/lib/badline/c64/reu.rb +289 -0
  88. data/lib/badline/c64/true_drives.rb +120 -0
  89. data/lib/badline/{computer.rb → c64.rb} +24 -15
  90. data/lib/badline/chips/cia/fast_forward.rb +62 -0
  91. data/lib/badline/chips/cia/interrupt_register.rb +128 -0
  92. data/lib/badline/chips/cia/keyed_parts.rb +213 -0
  93. data/lib/badline/chips/cia/serial.rb +246 -0
  94. data/lib/badline/chips/cia/time_of_day.rb +210 -0
  95. data/lib/badline/chips/cia/timer.rb +188 -0
  96. data/lib/badline/chips/cia.rb +281 -0
  97. data/lib/badline/chips/cpu/addressing.rb +133 -0
  98. data/lib/badline/chips/cpu/boundary.rb +56 -0
  99. data/lib/badline/chips/cpu/core.rb +183 -0
  100. data/lib/badline/chips/cpu/instruction.rb +338 -0
  101. data/lib/badline/chips/cpu/instruction_set/arithmetic.rb +118 -0
  102. data/lib/badline/chips/cpu/instruction_set/bitwise.rb +130 -0
  103. data/lib/badline/chips/cpu/instruction_set/branch.rb +83 -0
  104. data/lib/badline/chips/cpu/instruction_set/flag.rb +67 -0
  105. data/lib/badline/chips/cpu/instruction_set/illegal.rb +274 -0
  106. data/lib/badline/chips/cpu/instruction_set/inc_dec.rb +73 -0
  107. data/lib/badline/chips/cpu/instruction_set/stack.rb +56 -0
  108. data/lib/badline/chips/cpu/instruction_set/transfer.rb +141 -0
  109. data/lib/badline/chips/cpu/instruction_set.rb +59 -0
  110. data/lib/badline/chips/cpu/interrupts.rb +108 -0
  111. data/lib/badline/chips/cpu/keyed_state.rb +177 -0
  112. data/lib/badline/chips/cpu/microcode.rb +195 -0
  113. data/lib/badline/chips/cpu/operations.rb +90 -0
  114. data/lib/badline/chips/cpu/port.rb +133 -0
  115. data/lib/badline/chips/cpu/stack_operations.rb +84 -0
  116. data/lib/badline/chips/cpu/traps.rb +23 -0
  117. data/lib/badline/chips/cpu.rb +17 -0
  118. data/lib/badline/chips/sid/catch_up.rb +122 -0
  119. data/lib/badline/chips/sid/decimator.rb +68 -0
  120. data/lib/badline/chips/sid/envelope.rb +236 -0
  121. data/lib/badline/chips/sid/filter.rb +227 -0
  122. data/lib/badline/chips/sid/keyed_parts.rb +286 -0
  123. data/lib/badline/chips/sid/voice.rb +69 -0
  124. data/lib/badline/chips/sid/waveform/combined.rb +128 -0
  125. data/lib/badline/chips/sid/waveform/fast_forward.rb +107 -0
  126. data/lib/badline/chips/sid/waveform/noise_writeback.rb +124 -0
  127. data/lib/badline/chips/sid/waveform.rb +305 -0
  128. data/lib/badline/chips/sid.rb +264 -0
  129. data/lib/badline/chips/ted/beam.rb +119 -0
  130. data/lib/badline/chips/ted/timers.rb +66 -0
  131. data/lib/badline/chips/ted.rb +143 -0
  132. data/lib/badline/chips/tia.rb +49 -0
  133. data/lib/badline/chips/vdc/keyed_parts.rb +105 -0
  134. data/lib/badline/{c128 → chips}/vdc/memory.rb +1 -1
  135. data/lib/badline/{c128 → chips}/vdc/painter.rb +1 -13
  136. data/lib/badline/{c128 → chips}/vdc/raster.rb +1 -17
  137. data/lib/badline/{c128 → chips}/vdc/window.rb +1 -10
  138. data/lib/badline/{c128 → chips}/vdc.rb +6 -31
  139. data/lib/badline/chips/via/control_lines.rb +109 -0
  140. data/lib/badline/chips/via/fast_forward.rb +55 -0
  141. data/lib/badline/chips/via/interrupt_register.rb +60 -0
  142. data/lib/badline/chips/via/keyed_parts.rb +163 -0
  143. data/lib/badline/chips/via/shift_register.rb +168 -0
  144. data/lib/badline/chips/via/timer1.rb +120 -0
  145. data/lib/badline/chips/via/timer2.rb +117 -0
  146. data/lib/badline/chips/via.rb +267 -0
  147. data/lib/badline/chips/vic/bank.rb +135 -0
  148. data/lib/badline/chips/vic/border_mask.rb +78 -0
  149. data/lib/badline/chips/vic/collisions.rb +106 -0
  150. data/lib/badline/chips/vic/color_patches.rb +38 -0
  151. data/lib/badline/chips/vic/display_state.rb +168 -0
  152. data/lib/badline/chips/vic/graphics_mode.rb +166 -0
  153. data/lib/badline/chips/vic/graphics_shifter.rb +183 -0
  154. data/lib/badline/chips/vic/keyed_parts.rb +228 -0
  155. data/lib/badline/chips/vic/keyed_sprites.rb +225 -0
  156. data/lib/badline/chips/vic/keyed_state.rb +102 -0
  157. data/lib/badline/chips/vic/register_log.rb +65 -0
  158. data/lib/badline/chips/vic/registers.rb +188 -0
  159. data/lib/badline/chips/vic/sequencer.rb +275 -0
  160. data/lib/badline/chips/vic/sequencer_output.rb +118 -0
  161. data/lib/badline/chips/vic/sprite/internal_bus.rb +59 -0
  162. data/lib/badline/chips/vic/sprite/shifter.rb +148 -0
  163. data/lib/badline/chips/vic/sprite/timing.rb +58 -0
  164. data/lib/badline/chips/vic/sprite.rb +337 -0
  165. data/lib/badline/chips/vic/sprites.rb +281 -0
  166. data/lib/badline/chips/vic.rb +787 -0
  167. data/lib/badline/chips/vic_i/keyed_parts.rb +142 -0
  168. data/lib/badline/{vic20/vic → chips/vic_i}/painter.rb +2 -30
  169. data/lib/badline/chips/vic_i/sound/output.rb +85 -0
  170. data/lib/badline/chips/vic_i/sound.rb +227 -0
  171. data/lib/badline/{vic20/vic → chips/vic_i}/text_window.rb +2 -2
  172. data/lib/badline/{vic20/vic.rb → chips/vic_i.rb} +6 -6
  173. data/lib/badline/chips/z80/arithmetic.rb +119 -0
  174. data/lib/badline/chips/z80/bit_page.rb +57 -0
  175. data/lib/badline/chips/z80/block_io.rb +60 -0
  176. data/lib/badline/chips/z80/block_transfers.rb +61 -0
  177. data/lib/badline/chips/z80/bus_cycles.rb +113 -0
  178. data/lib/badline/chips/z80/core.rb +115 -0
  179. data/lib/badline/chips/z80/exchanges.rb +52 -0
  180. data/lib/badline/chips/z80/extended_page.rb +91 -0
  181. data/lib/badline/chips/z80/flags.rb +39 -0
  182. data/lib/badline/chips/z80/interrupt_acceptance.rb +48 -0
  183. data/lib/badline/chips/z80/jumps.rb +84 -0
  184. data/lib/badline/chips/z80/keyed_state.rb +73 -0
  185. data/lib/badline/chips/z80/loads.rb +100 -0
  186. data/lib/badline/chips/z80/main_page.rb +136 -0
  187. data/lib/badline/chips/z80/registers.rb +122 -0
  188. data/lib/badline/chips/z80/shifts.rb +66 -0
  189. data/lib/badline/chips/z80.rb +13 -0
  190. data/lib/badline/chips.rb +10 -0
  191. data/lib/badline/core.rb +13 -32
  192. data/lib/badline/drives/datasette/keyed_state.rb +38 -0
  193. data/lib/badline/drives/datasette.rb +173 -0
  194. data/lib/badline/drives/engine/core.rb +152 -0
  195. data/lib/badline/drives/engine/fast_serial.rb +75 -0
  196. data/lib/badline/drives/engine/idle.rb +127 -0
  197. data/lib/badline/drives/engine/idle_cpu.rb +30 -0
  198. data/lib/badline/drives/engine/orbit.rb +81 -0
  199. data/lib/badline/drives/engine/serial_lines.rb +72 -0
  200. data/lib/badline/drives/engine/sleep.rb +114 -0
  201. data/lib/badline/drives/engine/vias.rb +86 -0
  202. data/lib/badline/drives/engine/watch.rb +152 -0
  203. data/lib/badline/drives/engine.rb +11 -0
  204. data/lib/badline/drives/model1541/bus.rb +75 -0
  205. data/lib/badline/drives/model1541/byte_ready.rb +50 -0
  206. data/lib/badline/drives/model1541/cpu.rb +13 -0
  207. data/lib/badline/drives/model1541/disk.rb +318 -0
  208. data/lib/badline/drives/model1541/disk_via.rb +38 -0
  209. data/lib/badline/drives/model1541/gcr.rb +59 -0
  210. data/lib/badline/drives/model1541/keyed_state.rb +251 -0
  211. data/lib/badline/drives/model1541/mechanism.rb +313 -0
  212. data/lib/badline/drives/model1541/rotation.rb +123 -0
  213. data/lib/badline/drives/model1541/sector_reader.rb +78 -0
  214. data/lib/badline/drives/model1541/serial_port.rb +20 -0
  215. data/lib/badline/drives/model1541/track.rb +100 -0
  216. data/lib/badline/drives/model1541.rb +80 -0
  217. data/lib/badline/drives/model1571/bus.rb +100 -0
  218. data/lib/badline/drives/model1571/cpu.rb +13 -0
  219. data/lib/badline/drives/model1571/keyed_state.rb +108 -0
  220. data/lib/badline/drives/model1571/serial_port.rb +39 -0
  221. data/lib/badline/drives/model1571/serial_via.rb +27 -0
  222. data/lib/badline/drives/model1571/wd1770.rb +108 -0
  223. data/lib/badline/drives/model1571.rb +141 -0
  224. data/lib/badline/drives/model1581/bus.rb +126 -0
  225. data/lib/badline/drives/model1581/cpu.rb +13 -0
  226. data/lib/badline/drives/model1581/disk.rb +133 -0
  227. data/lib/badline/drives/model1581/keyed_state.rb +280 -0
  228. data/lib/badline/drives/model1581/mechanism.rb +98 -0
  229. data/lib/badline/drives/model1581/serial_port.rb +54 -0
  230. data/lib/badline/drives/model1581/slot.rb +61 -0
  231. data/lib/badline/drives/model1581/track.rb +102 -0
  232. data/lib/badline/drives/model1581/track_bytes.rb +77 -0
  233. data/lib/badline/drives/model1581/wd1772/registers.rb +91 -0
  234. data/lib/badline/drives/model1581/wd1772/sector_writes.rb +75 -0
  235. data/lib/badline/drives/model1581/wd1772/sectors.rb +152 -0
  236. data/lib/badline/drives/model1581/wd1772/stepping.rb +111 -0
  237. data/lib/badline/drives/model1581/wd1772/tracks.rb +127 -0
  238. data/lib/badline/drives/model1581/wd1772/turn.rb +75 -0
  239. data/lib/badline/drives/model1581/wd1772.rb +216 -0
  240. data/lib/badline/drives/model1581.rb +206 -0
  241. data/lib/badline/drives/serial_bus.rb +161 -0
  242. data/lib/badline/drives.rb +8 -0
  243. data/lib/badline/frontend/app.rb +3 -3
  244. data/lib/badline/frontend/boot.rb +4 -4
  245. data/lib/badline/frontend/controls.rb +19 -3
  246. data/lib/badline/frontend/drive_led.rb +44 -21
  247. data/lib/badline/frontend/machine_window.rb +3 -1
  248. data/lib/badline/frontend/menu/menu_dialogs.rb +2 -2
  249. data/lib/badline/frontend/player/filter_view.rb +1 -1
  250. data/lib/badline/frontend/player/player_controller.rb +2 -2
  251. data/lib/badline/frontend/player/player_screen.rb +1 -0
  252. data/lib/badline/frontend/player/theme.rb +7 -7
  253. data/lib/badline/frontend/snapshots.rb +12 -1
  254. data/lib/badline/frontend/timeline.rb +30 -12
  255. data/lib/badline/input/control_ports.rb +78 -0
  256. data/lib/badline/input/joystick.rb +27 -0
  257. data/lib/badline/input/keyboard.rb +114 -0
  258. data/lib/badline/input/matrix_typist.rb +98 -0
  259. data/lib/badline/input.rb +4 -0
  260. data/lib/badline/kernal_trap/chrout.rb +45 -0
  261. data/lib/badline/kernal_trap/{drive → dos}/block_commands.rb +1 -1
  262. data/lib/badline/kernal_trap/dos/channel.rb +156 -0
  263. data/lib/badline/kernal_trap/{drive → dos}/channels.rb +1 -26
  264. data/lib/badline/kernal_trap/{drive → dos}/memory.rb +1 -10
  265. data/lib/badline/kernal_trap/{drive → dos}/parameters.rb +1 -1
  266. data/lib/badline/kernal_trap/{drive → dos}/status.rb +1 -9
  267. data/lib/badline/kernal_trap/{drive → dos}/write_file.rb +1 -19
  268. data/lib/badline/kernal_trap/{drive → dos}/writes.rb +1 -1
  269. data/lib/badline/kernal_trap/{drive.rb → dos.rb} +9 -27
  270. data/lib/badline/kernal_trap/file_routine.rb +4 -4
  271. data/lib/badline/kernal_trap/keyed_state.rb +150 -0
  272. data/lib/badline/kernal_trap/layout.rb +75 -11
  273. data/lib/badline/kernal_trap/load.rb +19 -21
  274. data/lib/badline/kernal_trap/routine.rb +12 -0
  275. data/lib/badline/kernal_trap/save.rb +12 -22
  276. data/lib/badline/kernal_trap/serial.rb +8 -24
  277. data/lib/badline/kernal_trap.rb +3 -2
  278. data/lib/badline/machine/clocking.rb +1 -1
  279. data/lib/badline/machine/keyboard_buffer.rb +49 -0
  280. data/lib/badline/machine/settling.rb +29 -0
  281. data/lib/badline/machine.rb +10 -7
  282. data/lib/badline/media/boot_disk.rb +14 -2
  283. data/lib/badline/media/extensions.rb +10 -0
  284. data/lib/badline/media/true_drive.rb +1 -1
  285. data/lib/badline/media/tune.rb +2 -2
  286. data/lib/badline/media.rb +3 -3
  287. data/lib/badline/memory.rb +3 -6
  288. data/lib/badline/model.rb +1 -1
  289. data/lib/badline/options/family.rb +5 -2
  290. data/lib/badline/options/help.rb +42 -16
  291. data/lib/badline/options/table.rb +2 -0
  292. data/lib/badline/options/validation.rb +2 -0
  293. data/lib/badline/options.rb +6 -1
  294. data/lib/badline/plus4/acia.rb +45 -0
  295. data/lib/badline/plus4/bus.rb +312 -0
  296. data/lib/badline/plus4/cpu.rb +10 -0
  297. data/lib/badline/plus4/cpu_port.rb +93 -0
  298. data/lib/badline/plus4/keyboard_port.rb +51 -0
  299. data/lib/badline/plus4/model.rb +38 -0
  300. data/lib/badline/plus4/user_port.rb +31 -0
  301. data/lib/badline/plus4.rb +164 -0
  302. data/lib/badline/roms/README +44 -6
  303. data/lib/badline/roms/plus4/basic.rom +0 -0
  304. data/lib/badline/roms/plus4/function-high.rom +0 -0
  305. data/lib/badline/roms/plus4/function-low.rom +0 -0
  306. data/lib/badline/roms/plus4/kernal-ntsc.rom +0 -0
  307. data/lib/badline/roms/plus4/kernal-pal.rom +0 -0
  308. data/lib/badline/sdl.rb +1 -0
  309. data/lib/badline/{storage/sid_file.rb → sid_tune/file.rb} +3 -3
  310. data/lib/badline/{storage → sid_tune}/hvsc.rb +7 -7
  311. data/lib/badline/{storage → sid_tune}/song_lengths.rb +2 -2
  312. data/lib/badline/{storage → sid_tune}/stil/credit.rb +1 -1
  313. data/lib/badline/{storage → sid_tune}/stil.rb +3 -3
  314. data/lib/badline/sid_tune.rb +6 -0
  315. data/lib/badline/snapshot/c128_setup.rb +32 -0
  316. data/lib/badline/snapshot/c64_setup.rb +83 -0
  317. data/lib/badline/snapshot/container.rb +4 -0
  318. data/lib/badline/snapshot/field_doc/markdown.rb +112 -0
  319. data/lib/badline/snapshot/field_doc.rb +109 -0
  320. data/lib/badline/snapshot/image.rb +132 -46
  321. data/lib/badline/snapshot/keyed/cursor.rb +40 -0
  322. data/lib/badline/snapshot/keyed/modules.rb +66 -0
  323. data/lib/badline/snapshot/keyed/reader.rb +188 -0
  324. data/lib/badline/snapshot/keyed/recorder.rb +88 -0
  325. data/lib/badline/snapshot/keyed/report.rb +26 -0
  326. data/lib/badline/snapshot/keyed/writer.rb +55 -0
  327. data/lib/badline/snapshot/keyed.rb +64 -0
  328. data/lib/badline/snapshot/machine_state.rb +17 -64
  329. data/lib/badline/snapshot/storage_setup.rb +63 -0
  330. data/lib/badline/snapshot/vic20_setup.rb +28 -0
  331. data/lib/badline/snapshot/vice/c64_mem.rb +1 -1
  332. data/lib/badline/snapshot/vice/restore.rb +1 -1
  333. data/lib/badline/snapshot/vice/reu1764.rb +3 -3
  334. data/lib/badline/snapshot/vice/reu_fields.rb +40 -0
  335. data/lib/badline/snapshot/vice.rb +13 -35
  336. data/lib/badline/snapshot.rb +44 -22
  337. data/lib/badline/storage/crt_file.rb +3 -1
  338. data/lib/badline/storage/d64_image.rb +0 -2
  339. data/lib/badline/storage/d71_image.rb +0 -2
  340. data/lib/badline/storage/d81_image.rb +0 -2
  341. data/lib/badline/storage/disk_image.rb +3 -6
  342. data/lib/badline/storage/g64_image.rb +10 -5
  343. data/lib/badline/storage/host_directory.rb +0 -5
  344. data/lib/badline/storage/t64.rb +2 -8
  345. data/lib/badline/storage/tap.rb +43 -15
  346. data/lib/badline/storage/unavailable.rb +1 -1
  347. data/lib/badline/storage.rb +0 -37
  348. data/lib/badline/support/c128_checkpoint.rb +39 -0
  349. data/lib/badline/support/checkpoint.rb +108 -0
  350. data/lib/badline/support/vic20_checkpoint.rb +38 -0
  351. data/lib/badline/support.rb +5 -0
  352. data/lib/badline/version.rb +1 -1
  353. data/lib/badline/vic20/attachments.rb +8 -5
  354. data/lib/badline/vic20/bus/keyed_state.rb +52 -0
  355. data/lib/badline/vic20/bus.rb +2 -25
  356. data/lib/badline/vic20/cpu.rb +1 -1
  357. data/lib/badline/vic20/keyed_modules.rb +73 -0
  358. data/lib/badline/vic20/keyed_state.rb +45 -0
  359. data/lib/badline/vic20/port_wiring.rb +4 -4
  360. data/lib/badline/vic20/user_via_ports.rb +2 -2
  361. data/lib/badline/vic20.rb +22 -17
  362. data/lib/badline.rb +1 -0
  363. metadata +271 -228
  364. data/lib/badline/address_bus/extra_sids.rb +0 -30
  365. data/lib/badline/address_bus/fittings.rb +0 -38
  366. data/lib/badline/address_bus/pla.rb +0 -147
  367. data/lib/badline/address_bus/roms.rb +0 -39
  368. data/lib/badline/address_bus/saved_state.rb +0 -66
  369. data/lib/badline/address_bus/sid_slots.rb +0 -42
  370. data/lib/badline/address_bus.rb +0 -216
  371. data/lib/badline/c128/bus_state.rb +0 -53
  372. data/lib/badline/c128/saved_state.rb +0 -143
  373. data/lib/badline/cartridge/action_replay.rb +0 -126
  374. data/lib/badline/cartridge/atomic_power.rb +0 -24
  375. data/lib/badline/cartridge/bank.rb +0 -81
  376. data/lib/badline/cartridge/comal80.rb +0 -50
  377. data/lib/badline/cartridge/dinamic.rb +0 -34
  378. data/lib/badline/cartridge/easy_flash.rb +0 -145
  379. data/lib/badline/cartridge/epyx_fastload.rb +0 -79
  380. data/lib/badline/cartridge/final_cartridge3.rb +0 -89
  381. data/lib/badline/cartridge/flash.rb +0 -306
  382. data/lib/badline/cartridge/freezer.rb +0 -33
  383. data/lib/badline/cartridge/fun_play.rb +0 -38
  384. data/lib/badline/cartridge/g_mod2.rb +0 -74
  385. data/lib/badline/cartridge/game_system.rb +0 -40
  386. data/lib/badline/cartridge/geo_ram.rb +0 -79
  387. data/lib/badline/cartridge/kcs_power.rb +0 -75
  388. data/lib/badline/cartridge/mach5.rb +0 -32
  389. data/lib/badline/cartridge/magic_desk.rb +0 -30
  390. data/lib/badline/cartridge/ocean.rb +0 -35
  391. data/lib/badline/cartridge/pagefox.rb +0 -68
  392. data/lib/badline/cartridge/retro_replay.rb +0 -320
  393. data/lib/badline/cartridge/rex_utility.rb +0 -29
  394. data/lib/badline/cartridge/rgcd.rb +0 -58
  395. data/lib/badline/cartridge/simons_basic.rb +0 -35
  396. data/lib/badline/cartridge/standard.rb +0 -15
  397. data/lib/badline/cartridge/super_games.rb +0 -48
  398. data/lib/badline/cartridge/westermann.rb +0 -30
  399. data/lib/badline/cartridge/zaxxon.rb +0 -66
  400. data/lib/badline/cartridge.rb +0 -303
  401. data/lib/badline/checkpoint.rb +0 -98
  402. data/lib/badline/chrout_trap.rb +0 -43
  403. data/lib/badline/cia/fast_forward.rb +0 -60
  404. data/lib/badline/cia/interrupt_register.rb +0 -143
  405. data/lib/badline/cia/serial.rb +0 -275
  406. data/lib/badline/cia/state.rb +0 -47
  407. data/lib/badline/cia/timer.rb +0 -206
  408. data/lib/badline/cia.rb +0 -279
  409. data/lib/badline/color_memory.rb +0 -25
  410. data/lib/badline/computer/attachments.rb +0 -88
  411. data/lib/badline/computer/saved_state.rb +0 -117
  412. data/lib/badline/computer/true_drives.rb +0 -69
  413. data/lib/badline/control_ports.rb +0 -76
  414. data/lib/badline/cpu/addressing.rb +0 -131
  415. data/lib/badline/cpu/boundary.rb +0 -54
  416. data/lib/badline/cpu/core.rb +0 -165
  417. data/lib/badline/cpu/microcode.rb +0 -177
  418. data/lib/badline/cpu/operations.rb +0 -88
  419. data/lib/badline/cpu/saved_state.rb +0 -115
  420. data/lib/badline/cpu/stack_operations.rb +0 -82
  421. data/lib/badline/cpu.rb +0 -10
  422. data/lib/badline/cpu_port.rb +0 -112
  423. data/lib/badline/datasette.rb +0 -161
  424. data/lib/badline/debug_register.rb +0 -28
  425. data/lib/badline/drive/core.rb +0 -147
  426. data/lib/badline/drive/fast_serial.rb +0 -73
  427. data/lib/badline/drive/idle.rb +0 -125
  428. data/lib/badline/drive/idle_cpu.rb +0 -28
  429. data/lib/badline/drive/orbit.rb +0 -79
  430. data/lib/badline/drive/serial_lines.rb +0 -73
  431. data/lib/badline/drive/sleep.rb +0 -112
  432. data/lib/badline/drive/vias.rb +0 -84
  433. data/lib/badline/drive/watch.rb +0 -150
  434. data/lib/badline/drive.rb +0 -11
  435. data/lib/badline/drive1541/bus.rb +0 -86
  436. data/lib/badline/drive1541/byte_ready.rb +0 -48
  437. data/lib/badline/drive1541/cpu.rb +0 -11
  438. data/lib/badline/drive1541/disk.rb +0 -315
  439. data/lib/badline/drive1541/disk_state.rb +0 -86
  440. data/lib/badline/drive1541/disk_via.rb +0 -36
  441. data/lib/badline/drive1541/gcr.rb +0 -57
  442. data/lib/badline/drive1541/mechanism.rb +0 -313
  443. data/lib/badline/drive1541/mechanism_state.rb +0 -68
  444. data/lib/badline/drive1541/rotation.rb +0 -121
  445. data/lib/badline/drive1541/sector_reader.rb +0 -76
  446. data/lib/badline/drive1541/serial_port.rb +0 -18
  447. data/lib/badline/drive1541/track.rb +0 -98
  448. data/lib/badline/drive1541.rb +0 -111
  449. data/lib/badline/drive1571/bus.rb +0 -111
  450. data/lib/badline/drive1571/cpu.rb +0 -11
  451. data/lib/badline/drive1571/serial_port.rb +0 -37
  452. data/lib/badline/drive1571/serial_via.rb +0 -25
  453. data/lib/badline/drive1571/wd1770.rb +0 -120
  454. data/lib/badline/drive1571.rb +0 -175
  455. data/lib/badline/drive1581/bus.rb +0 -137
  456. data/lib/badline/drive1581/cpu.rb +0 -11
  457. data/lib/badline/drive1581/disk.rb +0 -135
  458. data/lib/badline/drive1581/disk_state.rb +0 -87
  459. data/lib/badline/drive1581/mechanism.rb +0 -117
  460. data/lib/badline/drive1581/serial_port.rb +0 -52
  461. data/lib/badline/drive1581/slot.rb +0 -78
  462. data/lib/badline/drive1581/track.rb +0 -100
  463. data/lib/badline/drive1581/track_bytes.rb +0 -75
  464. data/lib/badline/drive1581/wd1772/registers.rb +0 -89
  465. data/lib/badline/drive1581/wd1772/sector_writes.rb +0 -73
  466. data/lib/badline/drive1581/wd1772/sectors.rb +0 -150
  467. data/lib/badline/drive1581/wd1772/state.rb +0 -66
  468. data/lib/badline/drive1581/wd1772/stepping.rb +0 -109
  469. data/lib/badline/drive1581/wd1772/tracks.rb +0 -125
  470. data/lib/badline/drive1581/wd1772/turn.rb +0 -73
  471. data/lib/badline/drive1581/wd1772.rb +0 -216
  472. data/lib/badline/drive1581.rb +0 -233
  473. data/lib/badline/iec_bus.rb +0 -159
  474. data/lib/badline/instruction.rb +0 -334
  475. data/lib/badline/instruction_set/arithmetic.rb +0 -114
  476. data/lib/badline/instruction_set/bitwise.rb +0 -126
  477. data/lib/badline/instruction_set/branch.rb +0 -79
  478. data/lib/badline/instruction_set/flag.rb +0 -63
  479. data/lib/badline/instruction_set/illegal.rb +0 -270
  480. data/lib/badline/instruction_set/inc_dec.rb +0 -69
  481. data/lib/badline/instruction_set/stack.rb +0 -52
  482. data/lib/badline/instruction_set/transfer.rb +0 -137
  483. data/lib/badline/instruction_set.rb +0 -55
  484. data/lib/badline/interrupts.rb +0 -104
  485. data/lib/badline/joystick.rb +0 -25
  486. data/lib/badline/kernal_trap/channel.rb +0 -181
  487. data/lib/badline/keyboard.rb +0 -112
  488. data/lib/badline/keyboard_buffer.rb +0 -33
  489. data/lib/badline/ram_expansion/banking.rb +0 -30
  490. data/lib/badline/ram_expansion/plus256k.rb +0 -54
  491. data/lib/badline/ram_expansion/plus60k.rb +0 -43
  492. data/lib/badline/ram_expansion/unexpanded.rb +0 -28
  493. data/lib/badline/ram_expansion.rb +0 -21
  494. data/lib/badline/reu/dma/saved_state.rb +0 -50
  495. data/lib/badline/reu/dma.rb +0 -273
  496. data/lib/badline/reu/ram.rb +0 -107
  497. data/lib/badline/reu/saved_state.rb +0 -80
  498. data/lib/badline/reu/trigger.rb +0 -32
  499. data/lib/badline/reu.rb +0 -289
  500. data/lib/badline/sid/catch_up.rb +0 -120
  501. data/lib/badline/sid/decimator.rb +0 -64
  502. data/lib/badline/sid/envelope/state.rb +0 -68
  503. data/lib/badline/sid/envelope.rb +0 -239
  504. data/lib/badline/sid/filter.rb +0 -263
  505. data/lib/badline/sid/voice.rb +0 -77
  506. data/lib/badline/sid/waveform/combined.rb +0 -126
  507. data/lib/badline/sid/waveform/fast_forward.rb +0 -105
  508. data/lib/badline/sid/waveform/noise_writeback.rb +0 -122
  509. data/lib/badline/sid/waveform/state.rb +0 -66
  510. data/lib/badline/sid/waveform.rb +0 -305
  511. data/lib/badline/sid.rb +0 -303
  512. data/lib/badline/snapshot/setup.rb +0 -75
  513. data/lib/badline/snapshot/state.rb +0 -189
  514. data/lib/badline/time_of_day.rb +0 -228
  515. data/lib/badline/traps.rb +0 -19
  516. data/lib/badline/via/control_lines.rb +0 -122
  517. data/lib/badline/via/fast_forward.rb +0 -53
  518. data/lib/badline/via/interrupt_register.rb +0 -67
  519. data/lib/badline/via/shift_register.rb +0 -180
  520. data/lib/badline/via/state.rb +0 -42
  521. data/lib/badline/via/timer1.rb +0 -131
  522. data/lib/badline/via/timer2.rb +0 -133
  523. data/lib/badline/via.rb +0 -266
  524. data/lib/badline/vic/bank.rb +0 -133
  525. data/lib/badline/vic/border_mask.rb +0 -86
  526. data/lib/badline/vic/collisions.rb +0 -115
  527. data/lib/badline/vic/color_patches.rb +0 -47
  528. data/lib/badline/vic/display_state.rb +0 -185
  529. data/lib/badline/vic/graphics_mode.rb +0 -164
  530. data/lib/badline/vic/graphics_shifter.rb +0 -207
  531. data/lib/badline/vic/register_log.rb +0 -81
  532. data/lib/badline/vic/registers.rb +0 -195
  533. data/lib/badline/vic/sequencer.rb +0 -275
  534. data/lib/badline/vic/sequencer_output.rb +0 -116
  535. data/lib/badline/vic/sequencer_state.rb +0 -89
  536. data/lib/badline/vic/sprite/internal_bus.rb +0 -68
  537. data/lib/badline/vic/sprite/saved_state.rb +0 -64
  538. data/lib/badline/vic/sprite/shifter.rb +0 -146
  539. data/lib/badline/vic/sprite/timing.rb +0 -56
  540. data/lib/badline/vic/sprite.rb +0 -337
  541. data/lib/badline/vic/sprites.rb +0 -282
  542. data/lib/badline/vic/sprites_state.rb +0 -57
  543. data/lib/badline/vic.rb +0 -853
  544. data/lib/badline/vic20/saved_state.rb +0 -124
  545. data/lib/badline/vic20/sound/output.rb +0 -83
  546. data/lib/badline/vic20/sound.rb +0 -233
  547. data/lib/badline/vic20/vic/saved_state.rb +0 -55
  548. data/lib/badline/z80/arithmetic.rb +0 -117
  549. data/lib/badline/z80/bit_page.rb +0 -55
  550. data/lib/badline/z80/block_io.rb +0 -58
  551. data/lib/badline/z80/block_transfers.rb +0 -59
  552. data/lib/badline/z80/bus_cycles.rb +0 -111
  553. data/lib/badline/z80/core.rb +0 -113
  554. data/lib/badline/z80/exchanges.rb +0 -50
  555. data/lib/badline/z80/extended_page.rb +0 -89
  556. data/lib/badline/z80/flags.rb +0 -37
  557. data/lib/badline/z80/interrupt_acceptance.rb +0 -46
  558. data/lib/badline/z80/jumps.rb +0 -82
  559. data/lib/badline/z80/loads.rb +0 -98
  560. data/lib/badline/z80/main_page.rb +0 -134
  561. data/lib/badline/z80/registers.rb +0 -120
  562. data/lib/badline/z80/saved_state.rb +0 -69
  563. data/lib/badline/z80/shifts.rb +0 -64
  564. data/lib/badline/z80.rb +0 -11
  565. /data/lib/badline/snapshot/{fields.rb → vice/fields.rb} +0 -0
@@ -2,37 +2,109 @@
2
2
 
3
3
  module Badline
4
4
  module Snapshot
5
- # What a restore applied and what it left out: module names, and a line
6
- # for each thing left out, saying why.
7
- Report = Data.define(:applied, :ignored)
5
+ # What a restore applied and what it left out: module names and a line
6
+ # for each thing left out or changed, saying why. For a badline
7
+ # snapshot, `modules` holds each BL-* module's Keyed::Report, which
8
+ # give the fields per module this badline doesn't know, the modules it
9
+ # expected and didn't find, and the values it read other than as
10
+ # written, and `written_by` the badline version that saved it.
11
+ class Report
12
+ attr_reader :applied, :ignored, :written_by
13
+
14
+ def initialize(applied:, ignored:, modules: [], written_by: "")
15
+ @applied = applied
16
+ @ignored = ignored
17
+ @modules = modules
18
+ @written_by = written_by
19
+ end
20
+
21
+ # The keys nothing read, by module, for the modules that had any.
22
+ def unknown
23
+ @modules.reject { |report| report.unknown.empty? }.to_h { |report| [report.name, report.unknown] }
24
+ end
25
+
26
+ # The modules the snapshot didn't have.
27
+ def missing = @modules.select(&:missing).map(&:name)
28
+
29
+ # A line for each value read other than as written.
30
+ def adjusted = @modules.flat_map { |report| report.adjusted.map { |line| "#{report.name}: #{line}" } }
31
+ end
8
32
 
9
- # A snapshot read from a file.
33
+ # A snapshot: the container and its modules, read from a file or taken
34
+ # of a machine in memory (.of).
10
35
  class Image
36
+ MACHINE = "BL-MACHINE"
37
+ FAMILIES = { "c64" => "C64", "vic20" => "VIC-20", "c128" => "C128" }.freeze
38
+
11
39
  attr_reader :container
12
40
 
13
- def initialize(container)
41
+ # `name` is what a refusal calls the snapshot, such as its file's name.
42
+ def initialize(container, name: "the snapshot")
14
43
  @container = container
44
+ @name = name
45
+ end
46
+
47
+ # The machine's snapshot, saved as a file saves it: the machine runs
48
+ # on to the end of the instruction its CPU is in, and of an REU
49
+ # transfer under way (Machine::Settling), then writes its BL-*
50
+ # modules.
51
+ def self.of(computer)
52
+ computer.settle_for_snapshot!
53
+ new(Container.new(computer.save_modules, machine: machine_name(computer)))
54
+ end
55
+
56
+ # The snapshot with VICE's modules ahead of the BL-* ones for a C64,
57
+ # so x64sc opens it too.
58
+ def self.exported(computer)
59
+ computer.settle_for_snapshot!
60
+ sections = computer.save_modules
61
+ sections = Vice.export(computer) + sections if computer.family == :c64
62
+ new(Container.new(sections, machine: machine_name(computer)))
63
+ end
64
+
65
+ # The machine as VICE's emulators name it.
66
+ def self.machine_name(computer)
67
+ case computer.family
68
+ when :vic20 then Container::VIC20
69
+ when :c128 then Container::C128
70
+ else Container::MACHINE
71
+ end
72
+ end
73
+
74
+ # How the machine was built, from the family BL-MACHINE, read by
75
+ # `machine`, names.
76
+ def self.setup(machine)
77
+ case machine.choice("family", FAMILIES.keys, "c64")
78
+ when "vic20" then Vic20Setup.read_fields(machine)
79
+ when "c128" then C128Setup.read_fields(machine)
80
+ else C64Setup.read_fields(machine)
81
+ end
15
82
  end
16
83
 
17
84
  def sections = @container.sections
18
85
 
19
- # Whether badline wrote it, with the whole machine in a BADLINE module.
20
- def badline? = !section.nil?
86
+ def to_s = @container.to_s
87
+
88
+ def ==(other) = other.is_a?(Image) && to_s == other.to_s
21
89
 
22
- # The State the BADLINE module holds.
23
- def state = MachineState.state(section)
90
+ # Whether badline wrote it, with the whole machine in BL-* modules.
91
+ def badline? = !@container[MACHINE].nil?
24
92
 
25
93
  # A new machine, built as the snapshot's was and restored from it. A
26
- # badline snapshot restores from its BADLINE module alone, which
27
- # carries everything its VICE modules do, and a VICE one through the
28
- # modules badline reads. Yields a line for each thing left out, or
29
- # warns it without a block.
30
- def load(&)
31
- return load_vic20(&) if vic20?
32
- return load_c128(&) if c128?
33
-
34
- computer = badline? ? opening { Computer.restored(state) } : Vice.setup(@container).build
35
- tell(badline? ? state_report(computer) : Vice::Restore.apply(@container, computer), &)
94
+ # badline snapshot restores from its BL-* modules alone, and a VICE
95
+ # one through the modules badline reads. xvic's and x128's own
96
+ # modules aren't read. Yields a line for each thing left out, or
97
+ # warns it without a block. `detached` restores it without the host's
98
+ # files (Keyed::Modules).
99
+ def load(detached: false, &)
100
+ refuse_old_layout
101
+ raise FormatError, "badline reads only its own VIC-20 snapshots, not xvic's" if vic20? && !badline?
102
+ raise FormatError, "badline reads only its own C128 snapshots, not x128's" if c128? && !badline?
103
+ return load_vice(&) unless badline?
104
+
105
+ modules = Keyed::Modules.new(sections, detached:)
106
+ computer = opening { Image.setup(modules.machine).build.load_modules(modules) }
107
+ tell(report(modules, computer), &)
36
108
  computer
37
109
  end
38
110
 
@@ -42,57 +114,71 @@ module Badline
42
114
  # Whether the machine is a C128, as the container names it.
43
115
  def c128? = @container.machine == Container::C128
44
116
 
45
- # Restores `computer` from the snapshot, and returns the Report. A
46
- # snapshot that fails leaves the machine as it was.
117
+ # Restores `computer` from the snapshot, and returns the Report. The
118
+ # snapshot goes into a new machine first, so one that fails, or is of
119
+ # a machine built another way, leaves `computer` as it was.
47
120
  def restore(computer, &)
48
- report = badline? ? restore_state(computer) : Vice::Restore.import(@container, computer)
121
+ refuse_old_layout
122
+ report = badline? ? restore_modules(computer) : Vice::Restore.import(@container, computer)
49
123
  tell(report, &)
50
124
  report
51
125
  end
52
126
 
53
127
  private
54
128
 
55
- def section = @container[MachineState::NAME]
56
-
57
- # A VIC-20 restores from its BADLINE module alone. xvic's own modules
58
- # aren't read.
59
- def load_vic20(&)
60
- raise FormatError, "badline reads only its own VIC-20 snapshots, not xvic's" unless badline?
129
+ def load_vice(&)
130
+ computer = Vice.setup(@container).build
131
+ tell(Vice::Restore.apply(@container, computer), &)
132
+ computer
133
+ end
61
134
 
62
- machine = Vic20.restored(state)
63
- tell(state_report(machine), &)
64
- machine
135
+ # The snapshot goes into a new machine first, so one that fails
136
+ # leaves `computer` as it was.
137
+ def restore_modules(computer)
138
+ modules = Keyed::Modules.new(sections)
139
+ family = modules.machine.choice("family", FAMILIES.keys, "c64")
140
+ unless family == computer.family.to_s
141
+ raise FormatError, "the snapshot is of a #{FAMILIES.fetch(family, family)}, " \
142
+ "not a #{FAMILIES.fetch(computer.family.to_s)}"
143
+ end
144
+
145
+ trial = Keyed::Modules.new(sections)
146
+ opening { Image.setup(trial.machine).build.load_modules(trial) }
147
+ opening { computer.load_modules(modules) }
148
+ report(modules, computer)
65
149
  end
66
150
 
67
- # A C128 restores from its BADLINE module alone. x128's own modules
68
- # aren't read.
69
- def load_c128(&)
70
- raise FormatError, "badline reads only its own C128 snapshots, not x128's" unless badline?
151
+ # A snapshot from a badline before the keyed modules, which only the
152
+ # version that saved it reads.
153
+ def refuse_old_layout
154
+ old = @container[MachineState::NAME]
155
+ return if old.nil? || badline?
156
+
157
+ version = MachineState.written_by(old)
158
+ layout = "in the old snapshot layout, which this badline can't read."
159
+ raise FormatError, "#{@name} was saved by an earlier badline #{layout}" if version.empty?
71
160
 
72
- machine = C128.restored(state)
73
- tell(state_report(machine), &)
74
- machine
161
+ raise FormatError, "#{@name} was saved by badline #{version} #{layout} It still opens in badline #{version}."
75
162
  end
76
163
 
77
164
  def tell(report)
78
165
  report.ignored.each { |line| block_given? ? yield(line) : warn("badline: #{line}") }
79
166
  end
80
167
 
81
- def restore_state(computer)
82
- opening { computer.restore(state) }
83
- state_report(computer)
84
- end
85
-
86
168
  def opening
87
169
  yield
88
170
  rescue SystemCallError => e
89
171
  raise FormatError, "a file the snapshot names won't open: #{e.message}"
90
172
  end
91
173
 
92
- def state_report(computer)
174
+ def report(modules, computer)
175
+ written_by = modules.machine.text("written_by", "")
176
+ reports = modules.readers.map(&:report)
177
+ ignored = reports.flat_map(&:lines)
178
+ ignored.concat(modules.unread.map { |name| "#{name}: badline doesn't read this module, left out" })
93
179
  lost = computer.init_handlers_lost
94
- ignored = lost.positive? ? ["#{lost} on_init handler(s) the saved machine had yet to run, left out"] : []
95
- Report.new(applied: [MachineState::NAME], ignored:)
180
+ ignored << "#{lost} on_init handler(s) the saved machine had yet to run, left out" if lost.positive?
181
+ Report.new(applied: reports.reject(&:missing).map(&:name), ignored:, modules: reports, written_by:)
96
182
  end
97
183
  end
98
184
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Keyed
6
+ # Reads BER numbers and byte runs from a module's payload, or from one
7
+ # record's value, failing on anything cut short.
8
+ class Cursor
9
+ def initialize(bytes, name)
10
+ @bytes = bytes
11
+ @name = name
12
+ @pos = 0
13
+ end
14
+
15
+ def done? = @pos >= @bytes.bytesize
16
+
17
+ def remaining = @bytes.bytesize - @pos
18
+
19
+ def ber
20
+ value = 0
21
+ loop do
22
+ raise FormatError, "#{@name} holds a number too large for a snapshot" if value >= (1 << 56)
23
+
24
+ byte = take(1).getbyte(0).to_i
25
+ value = (value << 7) | (byte & 0x7f)
26
+ return value if byte < 0x80
27
+ end
28
+ end
29
+
30
+ def int = Keyed.unzigzag(ber)
31
+
32
+ def take(count)
33
+ raise FormatError, "#{@name} ends early, at byte #{@bytes.bytesize}" if count > remaining
34
+
35
+ @bytes.byteslice(@pos, count).to_s.tap { @pos += count }
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Keyed
6
+ # A snapshot's BL-* modules by name, which a family reads in its own
7
+ # fixed order, whatever order the file has them in. Each module is
8
+ # parsed once, and its Reader kept for the Report.
9
+ #
10
+ # A `detached` load restores a machine that opens no host file and
11
+ # writes none: a disk image comes from the bytes the snapshot holds,
12
+ # write-protected, and a directory device 8 serves is left out.
13
+ class Modules
14
+ attr_reader :readers
15
+
16
+ def initialize(sections, detached: false)
17
+ @sections = {}
18
+ sections.each { |section| @sections[section.name] = section }
19
+ @parsed = {}
20
+ @readers = []
21
+ @detached = detached
22
+ end
23
+
24
+ def detached? = @detached
25
+
26
+ # BL-MACHINE, which every family writes first.
27
+ def machine = required("BL-MACHINE")
28
+
29
+ # The module a family always writes, or a Reader standing in for it,
30
+ # reported missing.
31
+ def required(name)
32
+ reader = @parsed[name]
33
+ return reader if reader
34
+
35
+ section = @sections[name]
36
+ keep(name, section.nil? ? missing(name) : parse(section))
37
+ end
38
+
39
+ # The module, or nil when the snapshot has none, as it has none for a
40
+ # cartridge or a drive that isn't there.
41
+ def optional(name)
42
+ reader = @parsed[name]
43
+ return reader if reader
44
+
45
+ section = @sections[name]
46
+ section.nil? ? nil : keep(name, parse(section))
47
+ end
48
+
49
+ # The BL-* modules no family read, from a later badline.
50
+ def unread = @sections.keys.select { |name| name.start_with?("BL-") && !@parsed.key?(name) }
51
+
52
+ private
53
+
54
+ def parse(section) = Reader.parse(section)
55
+
56
+ def missing(name) = Reader.missing(name)
57
+
58
+ def keep(name, reader)
59
+ @parsed[name] = reader
60
+ @readers << reader
61
+ reader
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Keyed
6
+ # Parses a module's records into one Hash per type, so every value
7
+ # read has a single type in Spinel. Each read takes the current value
8
+ # as its default, which in a machine built for the load is power-on,
9
+ # and returns it when the record is missing or of another type.
10
+ #
11
+ # A Reader with no records stands in for a module the snapshot
12
+ # doesn't have, so every load takes the same path.
13
+ class Reader
14
+ # The tag of each key the module's records hold.
15
+ attr_reader :name, :tags
16
+
17
+ # Whether it stands in for a module the snapshot doesn't have.
18
+ def missing? = @missing
19
+
20
+ # The records in `section`, which must be in this encoding.
21
+ def self.parse(section)
22
+ unless section.major == MAJOR
23
+ raise FormatError, "#{section.name} #{section.version} was written by a newer badline's encoding, " \
24
+ "and this one reads #{MAJOR}.#{MINOR}"
25
+ end
26
+
27
+ new(section.name, Zlib.inflate(section.data))
28
+ rescue Zlib::Error => e
29
+ raise FormatError, "#{section.name} is damaged: #{e.message}"
30
+ end
31
+
32
+ # Stands in for a module the snapshot doesn't have.
33
+ def self.missing(name) = new(name, "".b, missing: true)
34
+
35
+ def initialize(name, payload = "".b, missing: false)
36
+ @name = name
37
+ @missing = missing
38
+ @ints = {}
39
+ @bools = {}
40
+ @bytes = {}
41
+ @lists = {}
42
+ @texts = {}
43
+ @tags = {}
44
+ @read = {}
45
+ @adjusted = []
46
+ @unknown_tags = 0
47
+ parse(Cursor.new(payload, name))
48
+ end
49
+
50
+ def int(key, default) = fetch_int(key, default)
51
+
52
+ def word(key, default) = fetch_masked(key, default, 0xffff)
53
+
54
+ def byte(key, default) = fetch_masked(key, default, 0xff)
55
+
56
+ # An int cut to the bits a latch holds, reported if it was wider.
57
+ def masked(key, default, mask) = fetch_masked(key, default, mask)
58
+
59
+ def bool(key, default) = present?(key, "a bool", @bools.key?(key)) ? @bools.fetch(key) : default
60
+
61
+ # Nil when the record is missing.
62
+ def optional_int(key) = present?(key, "an int", @ints.key?(key)) ? @ints.fetch(key) : nil
63
+
64
+ def ints(key, default) = present?(key, "a list of ints", @lists.key?(key)) ? @lists.fetch(key) : default
65
+
66
+ # A binary String.
67
+ def bytes(key, default) = fetch_bytes(key, default)
68
+
69
+ # Reads a hardware array into `array` in place. A shorter list
70
+ # leaves the rest as it was and a longer one is cut, and both are
71
+ # reported.
72
+ def ints_into(key, array)
73
+ return array unless present?(key, "a list of ints", @lists.key?(key))
74
+
75
+ fill(key, array, @lists.fetch(key))
76
+ end
77
+
78
+ def bytes_into(key, array)
79
+ return array unless present?(key, "bytes", @bytes.key?(key))
80
+
81
+ fill(key, array, @bytes.fetch(key).bytes)
82
+ end
83
+
84
+ def text(key, default) = fetch_text(key, default)
85
+
86
+ # The text, if it is one of `names`, and otherwise `default`,
87
+ # reported.
88
+ def choice(key, names, default)
89
+ value = fetch_text(key, default)
90
+ return value if names.include?(value)
91
+
92
+ @adjusted << "#{key} #{value.inspect} is not one of #{names.join(', ')}, left at #{default}"
93
+ default
94
+ end
95
+
96
+ # Reports something a unit's load left out or changed beyond one
97
+ # field, such as a line batch from another renderer.
98
+ def adjust(line)
99
+ @adjusted << line
100
+ self
101
+ end
102
+
103
+ # The keys of records nothing read.
104
+ def unknown = @tags.keys.reject { |key| @read.key?(key) }
105
+
106
+ def report
107
+ Report.new(name:, missing: @missing, unknown:, adjusted: @adjusted.dup, unknown_tags: @unknown_tags)
108
+ end
109
+
110
+ private
111
+
112
+ def fetch_int(key, default) = present?(key, "an int", @ints.key?(key)) ? @ints.fetch(key) : default
113
+
114
+ def fetch_masked(key, default, mask)
115
+ value = fetch_int(key, default)
116
+ return value if value & mask == value
117
+
118
+ @adjusted << "#{key} #{value} is wider than its latch, masked to #{value & mask}"
119
+ value & mask
120
+ end
121
+
122
+ def fetch_bytes(key, default) = present?(key, "bytes", @bytes.key?(key)) ? @bytes.fetch(key) : default
123
+
124
+ def fetch_text(key, default) = present?(key, "text", @texts.key?(key)) ? @texts.fetch(key) : default
125
+
126
+ # Whether the key has a record of the type read, `there`. A record
127
+ # of another type counts as missing, and is reported.
128
+ def present?(key, type, there)
129
+ @read[key] = true if there || @tags.key?(key)
130
+ @adjusted << "#{key} is not #{type}, left at its default" if !there && @tags.key?(key)
131
+ there
132
+ end
133
+
134
+ def fill(key, array, values)
135
+ unless values.length == array.length
136
+ what = values.length < array.length ? "the rest left as they were" : "the rest cut"
137
+ @adjusted << "#{key} holds #{values.length} values where #{array.length} fit, #{what}"
138
+ end
139
+ [values.length, array.length].min.times { |index| array[index] = values[index] }
140
+ array
141
+ end
142
+
143
+ def parse(cursor)
144
+ until cursor.done?
145
+ key = cursor.take(cursor.ber)
146
+ tag = cursor.take(1).getbyte(0).to_i
147
+ store(key, tag, cursor.take(cursor.ber))
148
+ end
149
+ end
150
+
151
+ def store(key, tag, value)
152
+ unless tag.between?(INT, TEXT)
153
+ @unknown_tags += 1
154
+ return
155
+ end
156
+
157
+ forget(key)
158
+ @tags[key] = tag
159
+ case tag
160
+ when INT then @ints[key] = Cursor.new(value, "#{@name} #{key}").int
161
+ when BOOL then @bools[key] = value.getbyte(0).to_i != 0
162
+ when BYTES then @bytes[key] = value
163
+ when INTS then @lists[key] = list(Cursor.new(value, "#{@name} #{key}"))
164
+ else @texts[key] = value.force_encoding(Encoding::UTF_8)
165
+ end
166
+ end
167
+
168
+ def forget(key)
169
+ return unless @tags.key?(key)
170
+
171
+ @adjusted << "#{key} appears more than once, the last one counts"
172
+ @ints.delete(key)
173
+ @bools.delete(key)
174
+ @bytes.delete(key)
175
+ @lists.delete(key)
176
+ @texts.delete(key)
177
+ end
178
+
179
+ def list(cursor)
180
+ count = cursor.ber
181
+ raise FormatError, "#{@name} ends early, in a list of #{count}" if count > cursor.remaining
182
+
183
+ Array.new(count) { cursor.int }
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Keyed
6
+ # A Reader that notes every read: the module, the key, the type read
7
+ # and the default passed. Loading a machine at power-on through
8
+ # Recorders lists each module's fields with their power-on values
9
+ # (FieldDoc).
10
+ class Recorder < Reader
11
+ Field = Data.define(:module_name, :key, :type, :default)
12
+
13
+ attr_reader :fields
14
+
15
+ def initialize(name, payload = "".b, missing: false)
16
+ super
17
+ @fields = []
18
+ end
19
+
20
+ def int(key, default)
21
+ note(key, "int", default)
22
+ super
23
+ end
24
+
25
+ def word(key, default)
26
+ note(key, "word", default)
27
+ super
28
+ end
29
+
30
+ def byte(key, default)
31
+ note(key, "byte", default)
32
+ super
33
+ end
34
+
35
+ def masked(key, default, mask)
36
+ note(key, "int & 0x#{mask.to_s(16)}", default)
37
+ super
38
+ end
39
+
40
+ def bool(key, default)
41
+ note(key, "bool", default)
42
+ super
43
+ end
44
+
45
+ def optional_int(key)
46
+ note(key, "int, optional", nil)
47
+ super
48
+ end
49
+
50
+ def ints(key, default)
51
+ note(key, "ints", default.dup)
52
+ super
53
+ end
54
+
55
+ def bytes(key, default)
56
+ note(key, "bytes", default.dup)
57
+ super
58
+ end
59
+
60
+ def ints_into(key, array)
61
+ note(key, "ints", array.dup)
62
+ super
63
+ end
64
+
65
+ def bytes_into(key, array)
66
+ note(key, "bytes", array.dup)
67
+ super
68
+ end
69
+
70
+ def text(key, default)
71
+ note(key, "text", default)
72
+ super
73
+ end
74
+
75
+ def choice(key, names, default)
76
+ note(key, "text: #{names.join(' ')}", default)
77
+ super
78
+ end
79
+
80
+ private
81
+
82
+ def note(key, type, default)
83
+ @fields << Field.new(module_name: name, key:, type:, default:)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Keyed
6
+ # What reading a module left out or changed: whether the snapshot
7
+ # had the module at all, the keys nothing read, a line for each value
8
+ # read other than as written, and how many records had a tag this
9
+ # badline doesn't know.
10
+ Report = Data.define(:name, :missing, :unknown, :adjusted, :unknown_tags) do
11
+ # Lines for the user. A missing module and adjusted values always
12
+ # get one, and unknown keys and tags only when `verbose`, since a
13
+ # snapshot from a later badline has them on every load.
14
+ def lines(verbose: false)
15
+ lines = missing ? ["#{name} is missing, left at power-on"] : []
16
+ lines.concat(adjusted.map { |line| "#{name}: #{line}" })
17
+ return lines unless verbose
18
+
19
+ lines << "#{name}: #{unknown.length} unknown field(s): #{unknown.join(', ')}" unless unknown.empty?
20
+ lines << "#{name}: #{unknown_tags} field(s) of an unknown type, skipped" if unknown_tags.positive?
21
+ lines
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end