badline 0.5.1 → 0.6.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 (129) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +58 -0
  4. data/exe/badline-ruby +2 -1
  5. data/lib/badline/address_bus/saved_state.rb +72 -0
  6. data/lib/badline/address_bus.rb +1 -0
  7. data/lib/badline/audio/stream.rb +6 -0
  8. data/lib/badline/cartridge/action_replay.rb +15 -0
  9. data/lib/badline/cartridge/comal80.rb +8 -0
  10. data/lib/badline/cartridge/dinamic.rb +8 -0
  11. data/lib/badline/cartridge/easy_flash.rb +20 -1
  12. data/lib/badline/cartridge/epyx_fastload.rb +8 -0
  13. data/lib/badline/cartridge/final_cartridge3.rb +12 -0
  14. data/lib/badline/cartridge/flash.rb +41 -0
  15. data/lib/badline/cartridge/fun_play.rb +8 -0
  16. data/lib/badline/cartridge/g_mod2.rb +12 -0
  17. data/lib/badline/cartridge/game_system.rb +8 -0
  18. data/lib/badline/cartridge/geo_ram.rb +16 -0
  19. data/lib/badline/cartridge/magic_desk.rb +8 -0
  20. data/lib/badline/cartridge/ocean.rb +8 -0
  21. data/lib/badline/cartridge/pagefox.rb +12 -0
  22. data/lib/badline/cartridge/retro_replay.rb +44 -1
  23. data/lib/badline/cartridge/rgcd.rb +10 -0
  24. data/lib/badline/cartridge/super_games.rb +10 -0
  25. data/lib/badline/cartridge/zaxxon.rb +16 -0
  26. data/lib/badline/cartridge.rb +72 -0
  27. data/lib/badline/cia/interrupt_register.rb +17 -0
  28. data/lib/badline/cia/serial.rb +44 -0
  29. data/lib/badline/cia/state.rb +47 -0
  30. data/lib/badline/cia/timer.rb +33 -0
  31. data/lib/badline/cia.rb +8 -0
  32. data/lib/badline/computer/attachments.rb +132 -0
  33. data/lib/badline/computer/saved_state.rb +116 -0
  34. data/lib/badline/computer.rb +7 -30
  35. data/lib/badline/cpu/microcode.rb +8 -0
  36. data/lib/badline/cpu/saved_state.rb +115 -0
  37. data/lib/badline/cpu.rb +2 -0
  38. data/lib/badline/datasette.rb +27 -0
  39. data/lib/badline/drive1541/bus.rb +13 -0
  40. data/lib/badline/drive1541/disk.rb +7 -2
  41. data/lib/badline/drive1541/disk_state.rb +80 -0
  42. data/lib/badline/drive1541/mechanism.rb +2 -0
  43. data/lib/badline/drive1541/mechanism_state.rb +67 -0
  44. data/lib/badline/drive1541/rotation.rb +10 -0
  45. data/lib/badline/drive1541/serial_port.rb +10 -0
  46. data/lib/badline/drive1541.rb +34 -0
  47. data/lib/badline/gui/application.rb +41 -14
  48. data/lib/badline/gui/gamepads.rb +7 -0
  49. data/lib/badline/gui/snapshots.rb +67 -0
  50. data/lib/badline/gui.rb +1 -0
  51. data/lib/badline/kernal_trap/channel.rb +27 -0
  52. data/lib/badline/kernal_trap/drive/channels.rb +25 -0
  53. data/lib/badline/kernal_trap/drive/memory.rb +9 -0
  54. data/lib/badline/kernal_trap/drive/status.rb +8 -0
  55. data/lib/badline/kernal_trap/drive/write_file.rb +21 -0
  56. data/lib/badline/kernal_trap/drive.rb +19 -0
  57. data/lib/badline/kernal_trap/save.rb +8 -0
  58. data/lib/badline/kernal_trap/serial.rb +16 -0
  59. data/lib/badline/memory.rb +8 -0
  60. data/lib/badline/options.rb +3 -2
  61. data/lib/badline/ram_expansion/plus256k.rb +16 -0
  62. data/lib/badline/ram_expansion/plus60k.rb +12 -0
  63. data/lib/badline/ram_expansion/unexpanded.rb +6 -0
  64. data/lib/badline/reu/dma.rb +36 -0
  65. data/lib/badline/reu/ram.rb +15 -0
  66. data/lib/badline/reu/saved_state.rb +52 -0
  67. data/lib/badline/reu.rb +4 -0
  68. data/lib/badline/sdl/events.rb +2 -0
  69. data/lib/badline/sid/envelope/state.rb +68 -0
  70. data/lib/badline/sid/envelope.rb +7 -0
  71. data/lib/badline/sid/filter.rb +38 -0
  72. data/lib/badline/sid/voice.rb +10 -0
  73. data/lib/badline/sid/waveform/state.rb +66 -0
  74. data/lib/badline/sid/waveform.rb +2 -0
  75. data/lib/badline/sid.rb +34 -0
  76. data/lib/badline/snapshot/container.rb +117 -0
  77. data/lib/badline/snapshot/fields.rb +54 -0
  78. data/lib/badline/snapshot/image.rb +70 -0
  79. data/lib/badline/snapshot/machine_state.rb +77 -0
  80. data/lib/badline/snapshot/setup.rb +58 -0
  81. data/lib/badline/snapshot/state.rb +178 -0
  82. data/lib/badline/snapshot/vice/c64_mem.rb +53 -0
  83. data/lib/badline/snapshot/vice/cias.rb +89 -0
  84. data/lib/badline/snapshot/vice/cias_import.rb +70 -0
  85. data/lib/badline/snapshot/vice/main_cpu.rb +76 -0
  86. data/lib/badline/snapshot/vice/peripherals.rb +47 -0
  87. data/lib/badline/snapshot/vice/restore.rb +125 -0
  88. data/lib/badline/snapshot/vice/sid_extended.rb +96 -0
  89. data/lib/badline/snapshot/vice/sid_registers.rb +46 -0
  90. data/lib/badline/snapshot/vice/vicii.rb +119 -0
  91. data/lib/badline/snapshot/vice/vicii_import.rb +131 -0
  92. data/lib/badline/snapshot/vice.rb +74 -0
  93. data/lib/badline/snapshot.rb +52 -0
  94. data/lib/badline/storage/crt_file.rb +9 -3
  95. data/lib/badline/storage/d64_image.rb +2 -0
  96. data/lib/badline/storage/d71_image.rb +2 -0
  97. data/lib/badline/storage/d81_image.rb +2 -0
  98. data/lib/badline/storage/disk_image.rb +11 -2
  99. data/lib/badline/storage/host_directory.rb +5 -0
  100. data/lib/badline/storage/t64.rb +6 -0
  101. data/lib/badline/storage/tap.rb +10 -1
  102. data/lib/badline/storage/unavailable.rb +20 -0
  103. data/lib/badline/storage.rb +34 -0
  104. data/lib/badline/time_of_day.rb +56 -0
  105. data/lib/badline/version.rb +1 -1
  106. data/lib/badline/via/control_lines.rb +15 -0
  107. data/lib/badline/via/interrupt_register.rb +9 -0
  108. data/lib/badline/via/shift_register.rb +13 -0
  109. data/lib/badline/via/state.rb +42 -0
  110. data/lib/badline/via/timer1.rb +13 -0
  111. data/lib/badline/via/timer2.rb +18 -0
  112. data/lib/badline/via.rb +2 -0
  113. data/lib/badline/vic/border_mask.rb +10 -0
  114. data/lib/badline/vic/collisions.rb +11 -0
  115. data/lib/badline/vic/color_patches.rb +11 -0
  116. data/lib/badline/vic/display_state.rb +29 -1
  117. data/lib/badline/vic/graphics_shifter.rb +26 -0
  118. data/lib/badline/vic/register_log.rb +18 -0
  119. data/lib/badline/vic/registers.rb +13 -2
  120. data/lib/badline/vic/sequencer.rb +12 -0
  121. data/lib/badline/vic/sequencer_state.rb +89 -0
  122. data/lib/badline/vic/sprite/internal_bus.rb +11 -0
  123. data/lib/badline/vic/sprite/saved_state.rb +64 -0
  124. data/lib/badline/vic/sprite.rb +2 -0
  125. data/lib/badline/vic/sprites.rb +3 -0
  126. data/lib/badline/vic/sprites_state.rb +57 -0
  127. data/lib/badline/vic.rb +94 -10
  128. data/lib/badline.rb +1 -0
  129. metadata +35 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9175400f11b212812092f887bea13e0880417fb151011fdbc568c4789050dcf1
4
- data.tar.gz: dbd07633e45bfcc06fd6b4ab1b680389276ff81340cc6c72d26bd4e7fdf6baad
3
+ metadata.gz: b238dd5194dd5fc7783779eced6e4b718b73890e81d78566d12c26c08a379597
4
+ data.tar.gz: 4a47225b0d7e2a055674ce9f4f61cd2bc1064413a2c4c8b6bf228ed99f4dde07
5
5
  SHA512:
6
- metadata.gz: d00a54fba0e530ff578a35b3b5953c0f011b6ea32a2b5ac3f8ccaf19dc8c8f3e26bb81dedfac4c53b026b0e2d0a8e3a35c9f212369624995765c0c8a99678542
7
- data.tar.gz: '0918a31ea33114c1f46d76ec3c0e9978a5e922bd7b39f6abe9494ee03423fad48f4aecbf8c4a2e0af4414ee19b2ce2d33c6cb21774a473138db15a588c149f2b'
6
+ metadata.gz: c636911838f2d65ae88c6b4e940085790c54573c2cc6a9d97450b68f2319eb374f3330e75243487bd7538b56d5cb695f2fe085739ead20db06eb3f24b9169c45
7
+ data.tar.gz: c48b8ecfe80b51a178a757246adba3ea267901db096ccb74c28dbc6482a9ff1f71193c26b4829e98744bcd6847f5fe3f073ff01275604d8383dd36dd91f50f69
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.0](https://github.com/elektronaut/badline/compare/v0.5.1...v0.6.0) (2026-09-29)
4
+
5
+
6
+ ### Features
7
+
8
+ * save and restore the machine in VICE .vsf snapshots ([07c0a26](https://github.com/elektronaut/badline/commit/07c0a26d7e73595d5cd4a5bfa90b633ee01195be))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * forget the drive's orbits after restoring it ([0238d81](https://github.com/elektronaut/badline/commit/0238d81ab12a0b0ff1e841661e10004ab3683cc1))
14
+
3
15
  ## [0.5.1](https://github.com/elektronaut/badline/compare/v0.5.0...v0.5.1) (2026-09-29)
4
16
 
5
17
 
data/README.md CHANGED
@@ -57,6 +57,7 @@ badline game.d64 # Mount a disk image as device 8 and load it
57
57
  badline game.tap # Insert a tape and load it
58
58
  badline game.crt # Attach a cartridge
59
59
  badline tune.sid # Play a SID tune
60
+ badline game.vsf # Restore a snapshot, from badline or VICE
60
61
  badline ~/c64 # Mount a directory as device 8
61
62
  ```
62
63
 
@@ -107,6 +108,7 @@ slows the emulation down.
107
108
  | `.tap` | Inserted in the datasette with PLAY pressed, then `LOAD` and `RUN`. It loads at the speed of a real tape |
108
109
  | `.crt` | The hardware types listed under [Cartridges](#whats-emulated). Other types are rejected |
109
110
  | `.sid` | PSID and RSID tunes, started through a small driver after boot |
111
+ | `.vsf` | A snapshot of a running machine, badline's own or one VICE's x64sc saved. See [Snapshots](#snapshots) |
110
112
  | A directory | Mounted read-write as device 8. It serves the `.prg` and `.p00` files in it and the contents of any `.t64`, and `SAVE` writes a new `.prg` |
111
113
 
112
114
  Apart from a `.g64`, which only a true drive can read, there is no 1541
@@ -139,6 +141,62 @@ and takes out a disk mounted through the traps, so `LOAD` and `SAVE`
139
141
  reach the 1541 too. With the 1541 in device 8, a `.d64` goes in its
140
142
  drive as well.
141
143
 
144
+ ## Snapshots
145
+
146
+ In the window, `F11` saves the whole machine to a new
147
+ `badline-<date>-<time>.vsf` in the working directory, and `F12` goes
148
+ back to the snapshot last saved or opened, in a new machine built as the
149
+ saved one was. Both executables do this, and both open a `.vsf` given as
150
+ the media. A snapshot that fails to open leaves the machine running as
151
+ it was. From Ruby, `computer.save_snapshot(path)` saves,
152
+ `computer.restore_snapshot(path)` takes a machine back to a snapshot,
153
+ and `Badline::Snapshot.load(path)` builds a new machine as the saved one
154
+ was built and restores it. `computer.snapshot` and
155
+ `computer.restore(state)` do the same in memory, without a file. A
156
+ restore that fails leaves the machine as it was.
157
+
158
+ A snapshot holds the machine as it was on the cycle it was saved: the
159
+ chips down to the instruction step and the pixel pipeline, the RAM and
160
+ any +60K or +256K expansion, the cartridge with its RAM and flash, a
161
+ GEO-RAM, a true 1541 with its RAM, its VIAs and the disk under the head,
162
+ a disk or directory mounted through the traps with its open channels,
163
+ and the tape with its place on it. A restored machine runs on exactly
164
+ as the saved one would have. A directory, a tape and a true drive's
165
+ disk image open again from their paths when the snapshot is restored,
166
+ the disk with its tracks as the drive last saw them. A disk image mounted through the traps comes back
167
+ with its contents from the snapshot. What the host holds stays the host's: the keyboard, the
168
+ joysticks, the mouse and paddles, sound, and blocks given to `on_init`
169
+ that hadn't run yet, which a restore reports. A snapshot only restores
170
+ in the badline version that wrote it, into a machine with the same
171
+ chip models, RAM expansion and REU. `computer.snapshot` holds an REU's
172
+ RAM, registers and transfer too, but a machine with an REU doesn't save
173
+ to a file yet, as badline doesn't write VICE's REU module.
174
+
175
+ Snapshots use VICE's `.vsf` format. badline writes VICE's modules for
176
+ the CPU, RAM and CPU port, both CIAs, the SID and the VIC-II, plus the
177
+ ones x64sc needs to open the file, with nothing attached to the
178
+ cartridge, tape or user ports and no true drive. A `BADLINE` module,
179
+ which VICE skips, holds the whole machine, and badline restores its own
180
+ snapshots from it.
181
+
182
+ x64sc 3.10 opens badline's snapshots, taken at the end of the
183
+ instruction the CPU was in, as long as its VIC-II model matches
184
+ (`-model c64` for the default machine). It keeps its own drives and
185
+ leaves out the cartridge, the expansions and the tape. Snapshots x64sc
186
+ saves open in badline through the same modules: the CPU at its
187
+ instruction boundary, RAM, the CPU port, the CIAs' registers, timers and
188
+ clocks, the SID's registers and reSID voice state, and the VIC-II's
189
+ registers, beam position, counters and colour RAM. The VIC-II's pixel
190
+ pipeline starts empty. badline reads the modules x64sc 3.7 to 3.10
191
+ write, and VICE's development versions' `MAINC64CPU` and `VIC-IISC`. A module version
192
+ it doesn't know is left out, or fails the restore for the CPU and RAM.
193
+ It reports the modules it leaves out, such as the 1541 drives, the
194
+ cartridge, the datasette and the keyboard, and carries on without them.
195
+ An NTSC snapshot fails, as badline runs PAL only. Restored into a
196
+ running machine, a VICE snapshot fails for a machine built another way,
197
+ such as a C64C's snapshot in a C64, and otherwise takes the cartridge
198
+ out and switches the machine off and on.
199
+
142
200
  ## Playing and rendering SID tunes
143
201
 
144
202
  `badline-ruby --headless` plays a `.sid` tune on the host's audio
data/exe/badline-ruby CHANGED
@@ -55,7 +55,8 @@ begin
55
55
  disk: { read_only: options.read_only? })
56
56
  rescue Badline::Media::TrueDrive::Error, Badline::Storage::SIDFile::FormatError, Badline::Storage::T64::FormatError,
57
57
  Badline::Storage::TAP::FormatError, Badline::Storage::CRTFile::FormatError,
58
- Badline::Storage::G64Image::FormatError, Badline::Cartridge::UnsupportedTypeError => e
58
+ Badline::Storage::G64Image::FormatError, Badline::Cartridge::UnsupportedTypeError,
59
+ Badline::Snapshot::FormatError => e
59
60
  warn "badline-ruby: #{options.media_path}: #{e.message}"
60
61
  exit 1
61
62
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class AddressBus
5
+ # The bus's state for a snapshot: the CPU port, the RAM, colour RAM and
6
+ # a RAM expansion, the cartridge, the datasette and the chips.
7
+ module SavedState
8
+ # The RAM expansion fitted, or an Unexpanded stand-in.
9
+ attr_reader :ram_expansion
10
+
11
+ # Takes the cartridge out of the expansion port, as a restore does for
12
+ # a state without one.
13
+ def detach_cartridge
14
+ @cartridge = nil
15
+ update_overlays!
16
+ end
17
+
18
+ # The 6510 port's direction and output registers, and the charge left
19
+ # on its floating bits.
20
+ def port_state = [@port_ddr, @port_out, @port_floating]
21
+
22
+ # Sets the port as port_state reads it, as a snapshot restores it,
23
+ # leaving the RAM under $00 and $01 alone.
24
+ def restore_port(ddr, out, floating)
25
+ @port_ddr = ddr
26
+ @port_out = out
27
+ @port_floating = floating
28
+ update_port!
29
+ end
30
+
31
+ # The machine's memory and everything on the bus but the keyboard, the
32
+ # joysticks and the pot devices, which the host holds. A cartridge is
33
+ # already in the port, built from its setup (Computer#load_state).
34
+ def save_state(out)
35
+ out.marker("BUS")
36
+ out.int(@port_ddr).int(@port_out).int(@port_floating)
37
+ @ram.save_state(out)
38
+ @color_ram.save_state(out)
39
+ @ram_expansion.save_state(out)
40
+ @cartridge&.save_state(out)
41
+ @datasette.save_state(out)
42
+ @vic.save_state(out)
43
+ @cia1.save_state(out)
44
+ @cia2.save_state(out)
45
+ @sid.save_state(out)
46
+ end
47
+
48
+ def load_state(input)
49
+ input.marker("BUS")
50
+ ddr = input.int
51
+ port_out = input.int
52
+ floating = input.int
53
+ @ram.load_state(input)
54
+ @color_ram.load_state(input)
55
+ @ram_expansion.load_state(input)
56
+ @cartridge&.load_state(input)
57
+ @datasette.load_state(input)
58
+ @port_ddr = ddr
59
+ @port_out = port_out
60
+ @port_floating = floating
61
+ @io_port.value = port_value
62
+ update_overlays!
63
+ @vic.load_state(input)
64
+ @cia1.load_state(input)
65
+ @cia2.load_state(input)
66
+ @sid.load_state(input)
67
+ end
68
+ end
69
+
70
+ include SavedState
71
+ end
72
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "badline/address_bus/saved_state"
3
4
  require "badline/address_bus/ultimax_pages"
4
5
 
5
6
  module Badline
@@ -38,6 +38,12 @@ module Badline
38
38
 
39
39
  def rate = @sink.rate
40
40
 
41
+ # Plays another machine's SID from here on.
42
+ def sid=(sid)
43
+ @sid = sid
44
+ sid.record(rate: rate)
45
+ end
46
+
41
47
  def muted? = @muted
42
48
 
43
49
  def playing? = @started
@@ -64,6 +64,21 @@ module Badline
64
64
 
65
65
  private
66
66
 
67
+ def save_mapper(out)
68
+ save_windows(out, windows)
69
+ out.int(window_index(@rom, windows)).boolean(@active).boolean(@io2_ram).blob(@ram_data)
70
+ end
71
+
72
+ def load_mapper(input)
73
+ load_windows(input, windows)
74
+ @rom = window_at(input.int, windows)
75
+ @active = input.boolean?
76
+ @io2_ram = input.boolean?
77
+ input.blob_into(@ram_data)
78
+ end
79
+
80
+ def windows = [@through, @isolated, @contended, *@rom_banks, EMPTY_BANK]
81
+
67
82
  def io2_peek(addr)
68
83
  @io2_ram ? @ram_data[IO2_PAGE | (addr & 0xff)] : @rom.peek(addr)
69
84
  end
@@ -33,6 +33,14 @@ module Badline
33
33
 
34
34
  private
35
35
 
36
+ def save_mapper(out)
37
+ save_windows(out, [*@roml_banks, *@romh_banks, EMPTY_BANK])
38
+ end
39
+
40
+ def load_mapper(input)
41
+ load_windows(input, [*@roml_banks, *@romh_banks, EMPTY_BANK])
42
+ end
43
+
36
44
  def select(number, mode)
37
45
  @roml = bank(@roml_banks, number)
38
46
  @romh = bank(@romh_banks, number)
@@ -24,6 +24,14 @@ module Badline
24
24
 
25
25
  private
26
26
 
27
+ def save_mapper(out)
28
+ save_windows(out, [*@banks, EMPTY_BANK])
29
+ end
30
+
31
+ def load_mapper(input)
32
+ load_windows(input, [*@banks, EMPTY_BANK])
33
+ end
34
+
27
35
  def install_chips(chips)
28
36
  @banks = banks_from(chips).first
29
37
  self.mode = :rom8k
@@ -78,9 +78,28 @@ module Badline
78
78
  private
79
79
 
80
80
  def select_bank
81
+ map_bank
82
+ changed!
83
+ end
84
+
85
+ def map_bank
81
86
  @roml = @low_flash.window(@bank * BANK_SIZE)
82
87
  @romh = @high_flash.window(@bank * BANK_SIZE)
83
- changed!
88
+ end
89
+
90
+ def save_mapper(out)
91
+ out.int(@bank).int(@control).blob(@io_ram)
92
+ @low_flash.save_state(out)
93
+ @high_flash.save_state(out)
94
+ end
95
+
96
+ def load_mapper(input)
97
+ @bank = input.int
98
+ @control = input.int
99
+ input.blob_into(@io_ram)
100
+ @low_flash.load_state(input)
101
+ @high_flash.load_state(input)
102
+ map_bank
84
103
  end
85
104
 
86
105
  def apply_control
@@ -56,6 +56,14 @@ module Badline
56
56
 
57
57
  private
58
58
 
59
+ def save_mapper(out)
60
+ out.int(@discharged_at)
61
+ end
62
+
63
+ def load_mapper(input)
64
+ @discharged_at = input.int
65
+ end
66
+
59
67
  def discharge!
60
68
  @discharged_at = @clock ? @clock.call : 0
61
69
  end
@@ -62,6 +62,18 @@ module Badline
62
62
 
63
63
  private
64
64
 
65
+ def save_mapper(out)
66
+ save_windows(out, [*@roml_banks, *@romh_banks, EMPTY_BANK])
67
+ out.boolean(@register_visible).boolean(@phi1_ultimax)
68
+ end
69
+
70
+ def load_mapper(input)
71
+ load_windows(input, [*@roml_banks, *@romh_banks, EMPTY_BANK])
72
+ @io_bank = @roml
73
+ @register_visible = input.boolean?
74
+ @phi1_ultimax = input.boolean?
75
+ end
76
+
65
77
  def select(number)
66
78
  @io_bank = @roml = bank(@roml_banks, number)
67
79
  @romh = bank(@romh_banks, number)
@@ -2,6 +2,37 @@
2
2
 
3
3
  module Badline
4
4
  class Cartridge
5
+ # A Flash chip's state, for a snapshot.
6
+ module FlashState
7
+ STATES = %i[read program_setup autoselect programming erase_window erasing suspended chip_erase
8
+ program_error].freeze
9
+
10
+ # The array, the command state, a timed operation's end on the
11
+ # machine's clock, and the windows handed out so far, which load_state
12
+ # builds anew.
13
+ def save_state(out)
14
+ out.blob(@data).int(STATES.index(@state)).int(STATES.index(@base_state)).int(@cycle).int(@toggle)
15
+ out.optional_int(@done_at).optional_int(@programmed).optional_int(@remaining)
16
+ out.boolean(!@erasing_sectors.nil?)
17
+ out.ints(@erasing_sectors) if @erasing_sectors
18
+ out.ints(@windows.keys).ints(@status_windows.keys)
19
+ end
20
+
21
+ def load_state(input)
22
+ input.blob_into(@data)
23
+ @state = STATES.fetch(input.int).to_sym
24
+ @base_state = STATES.fetch(input.int).to_sym
25
+ @cycle = input.int
26
+ @toggle = input.int
27
+ @done_at = input.optional_int
28
+ @programmed = input.optional_int
29
+ @remaining = input.optional_int
30
+ @erasing_sectors = nil
31
+ @erasing_sectors = input.ints if input.boolean?
32
+ rebuild_windows(input.ints, input.ints)
33
+ end
34
+ end
35
+
5
36
  # An AMD 29F0x0 flash chip, driven through the JEDEC command set in the
6
37
  # AMD datasheets. Each command opens with two unlock cycles, $AA to the
7
38
  # first unlock address and $55 to the second, and a third write picks
@@ -66,6 +97,8 @@ module Badline
66
97
  alias [] peek
67
98
  end
68
99
 
100
+ include FlashState
101
+
69
102
  attr_reader :data, :state
70
103
  attr_writer :clock
71
104
 
@@ -95,6 +128,14 @@ module Badline
95
128
  end
96
129
  end
97
130
 
131
+ # Hands out new windows at the bases given, as #window would have.
132
+ def rebuild_windows(bases, status_bases)
133
+ @windows = {}
134
+ @status_windows = {}
135
+ bases.each { |base| @windows[base] = Window.new(self, base) }
136
+ status_bases.each { |base| @status_windows[base] = StatusWindow.new(self, base) }
137
+ end
138
+
98
139
  def array_mode?
99
140
  @state == :read || @state == :program_setup
100
141
  end
@@ -28,6 +28,14 @@ module Badline
28
28
 
29
29
  private
30
30
 
31
+ def save_mapper(out)
32
+ save_windows(out, [*@banks, EMPTY_BANK])
33
+ end
34
+
35
+ def load_mapper(input)
36
+ load_windows(input, [*@banks, EMPTY_BANK])
37
+ end
38
+
31
39
  def install_chips(chips)
32
40
  @banks = []
33
41
  chips.each { |chip| @banks[self.class.bank_number(chip.bank)] = rom_bank(chip.data) }
@@ -47,6 +47,18 @@ module Badline
47
47
  changed!
48
48
  end
49
49
 
50
+ def save_mapper(out)
51
+ out.int(@bank).boolean(@flash_writes)
52
+ @flash.save_state(out)
53
+ end
54
+
55
+ def load_mapper(input)
56
+ @bank = input.int
57
+ @flash_writes = input.boolean?
58
+ @flash.load_state(input)
59
+ @roml = @flash.window(@bank * BANK_SIZE)
60
+ end
61
+
50
62
  def install_chips(chips)
51
63
  data = Array.new(BANKS * BANK_SIZE, 0xff)
52
64
  chips.each do |chip|
@@ -25,6 +25,14 @@ module Badline
25
25
 
26
26
  private
27
27
 
28
+ def save_mapper(out)
29
+ save_windows(out, [*@banks, EMPTY_BANK])
30
+ end
31
+
32
+ def load_mapper(input)
33
+ load_windows(input, [*@banks, EMPTY_BANK])
34
+ end
35
+
28
36
  def select(addr)
29
37
  @roml = bank(@banks, addr & 0x3f)
30
38
  changed!
@@ -52,8 +52,24 @@ module Badline
52
52
  @block = @page = @window = 0
53
53
  end
54
54
 
55
+ # A GEO-RAM is built from its size alone.
56
+ def save_setup(out)
57
+ out.int(GEO_RAM_SETUP).int(@size)
58
+ end
59
+
55
60
  private
56
61
 
62
+ def save_mapper(out)
63
+ out.blob(@ram_data).int(@block).int(@page).int(@window)
64
+ end
65
+
66
+ def load_mapper(input)
67
+ input.blob_into(@ram_data)
68
+ @block = input.int
69
+ @page = input.int
70
+ @window = input.int
71
+ end
72
+
57
73
  def install_chips(_chips)
58
74
  @ram_data = Array.new(@size * 1024, 0)
59
75
  reset
@@ -20,6 +20,14 @@ module Badline
20
20
 
21
21
  private
22
22
 
23
+ def save_mapper(out)
24
+ save_windows(out, [*@banks, EMPTY_BANK])
25
+ end
26
+
27
+ def load_mapper(input)
28
+ load_windows(input, [*@banks, EMPTY_BANK])
29
+ end
30
+
23
31
  def install_chips(chips)
24
32
  @banks = banks_from(chips).first
25
33
  @bank_mask = (bank_mask(@banks) | 0x03) & 0x7f
@@ -19,6 +19,14 @@ module Badline
19
19
 
20
20
  private
21
21
 
22
+ def save_mapper(out)
23
+ save_windows(out, [*@banks, EMPTY_BANK])
24
+ end
25
+
26
+ def load_mapper(input)
27
+ load_windows(input, [*@banks, EMPTY_BANK])
28
+ end
29
+
22
30
  # In 16K mode the selected bank shows through ROMH as well.
23
31
  def select_bank(number)
24
32
  @roml = bank(@banks, number)
@@ -29,6 +29,18 @@ module Badline
29
29
 
30
30
  private
31
31
 
32
+ def save_mapper(out)
33
+ save_windows(out, windows)
34
+ @ram_banks.each { |ram| out.blob(ram.data) }
35
+ end
36
+
37
+ def load_mapper(input)
38
+ load_windows(input, windows)
39
+ @ram_banks.each { |ram| input.blob_into(ram.data) }
40
+ end
41
+
42
+ def windows = [*@roml_banks, *@romh_banks, *@ram_banks, @open_bus || EMPTY_BANK, EMPTY_BANK]
43
+
32
44
  def select(value)
33
45
  half = (value >> 1) & 0x01
34
46
  chip = (value >> 2) & 0x03
@@ -2,6 +2,44 @@
2
2
 
3
3
  module Badline
4
4
  class Cartridge
5
+ # A Retro Replay's registers, RAM and flash, for a snapshot.
6
+ module RetroReplayState
7
+ private
8
+
9
+ def save_jumpers(out)
10
+ out.boolean(true).boolean(@flash_jumper).boolean(@bank_jumper)
11
+ end
12
+
13
+ def save_mapper(out)
14
+ [@active, @frozen, @phi1_ultimax, @ram_enabled, @ram_at_a000, @allow_bank, @no_freeze, @reu_mapping,
15
+ @write_once].each { |flag| out.boolean(flag) }
16
+ out.int(@bank).int(RetroReplay::CONTROL_MODES.index(@config))
17
+ @ram_data.each { |data| out.blob(data) }
18
+ @flash.save_state(out)
19
+ end
20
+
21
+ def load_mapper(input)
22
+ load_flags(input)
23
+ @bank = input.int
24
+ @config = RetroReplay::CONTROL_MODES.fetch(input.int)
25
+ @ram_data.each { |data| input.blob_into(data) }
26
+ @flash.load_state(input)
27
+ map_windows
28
+ end
29
+
30
+ def load_flags(input)
31
+ @active = input.boolean?
32
+ @frozen = input.boolean?
33
+ @phi1_ultimax = input.boolean?
34
+ @ram_enabled = input.boolean?
35
+ @ram_at_a000 = input.boolean?
36
+ @allow_bank = input.boolean?
37
+ @no_freeze = input.boolean?
38
+ @reu_mapping = input.boolean?
39
+ @write_once = input.boolean?
40
+ end
41
+ end
42
+
5
43
  # Retro Replay: an Am29F010 flash chip of two 64K halves, each eight 8K
6
44
  # ROM banks, and four 8K RAM banks, with the Action Replay's register at
7
45
  # $DE00 and an extended one at $DE01. The Nordic Replay (subtype 1) adds
@@ -44,6 +82,7 @@ module Badline
44
82
  # the half in bit 5.
45
83
  class RetroReplay < Cartridge
46
84
  include Freezer
85
+ include RetroReplayState
47
86
 
48
87
  CONTROL_MODES = %i[rom8k rom16k off ultimax].freeze
49
88
  IO1_PAGE = 0x1e00
@@ -212,11 +251,15 @@ module Badline
212
251
  end
213
252
 
214
253
  def configure
254
+ map_windows
255
+ changed!
256
+ end
257
+
258
+ def map_windows
215
259
  self.mode = @config
216
260
  @rom = flash_window(@bank)
217
261
  @roml = roml_window
218
262
  @romh = romh_window
219
- changed!
220
263
  end
221
264
 
222
265
  def roml_window
@@ -28,6 +28,16 @@ module Badline
28
28
 
29
29
  private
30
30
 
31
+ def save_mapper(out)
32
+ save_windows(out, [*@banks, EMPTY_BANK])
33
+ out.boolean(@disabled)
34
+ end
35
+
36
+ def load_mapper(input)
37
+ load_windows(input, [*@banks, EMPTY_BANK])
38
+ @disabled = input.boolean?
39
+ end
40
+
31
41
  def select(value)
32
42
  value ^= 0x07 if @hucky
33
43
  if value.anybits?(0x08)
@@ -20,6 +20,16 @@ module Badline
20
20
 
21
21
  private
22
22
 
23
+ def save_mapper(out)
24
+ save_windows(out, [*@roml_banks, *@romh_banks, EMPTY_BANK])
25
+ out.boolean(@locked)
26
+ end
27
+
28
+ def load_mapper(input)
29
+ load_windows(input, [*@roml_banks, *@romh_banks, EMPTY_BANK])
30
+ @locked = input.boolean?
31
+ end
32
+
23
33
  def select(value)
24
34
  @roml = bank(@roml_banks, value & 0x03)
25
35
  @romh = bank(@romh_banks, value & 0x03)
@@ -31,6 +31,14 @@ module Badline
31
31
  def peek(addr)
32
32
  @bank.peek(addr)
33
33
  end
34
+
35
+ def save_state(out)
36
+ out.int([*@banks, EMPTY_BANK].index { |bank| bank.equal?(@bank) })
37
+ end
38
+
39
+ def load_state(input)
40
+ @bank = [*@banks, EMPTY_BANK].fetch(input.int)
41
+ end
34
42
  end
35
43
 
36
44
  def reset
@@ -39,6 +47,14 @@ module Badline
39
47
 
40
48
  private
41
49
 
50
+ def save_mapper(out)
51
+ @romh.save_state(out)
52
+ end
53
+
54
+ def load_mapper(input)
55
+ @romh.load_state(input)
56
+ end
57
+
42
58
  def install_chips(chips)
43
59
  roml, romh = banks_from(chips)
44
60
  @romh = High.new(romh)