badline 0.1.0 → 0.2.1

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 (140) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +185 -0
  3. data/CLAUDE.md +217 -0
  4. data/CODE_OF_CONDUCT.md +92 -0
  5. data/CONTRIBUTING.md +51 -0
  6. data/README.md +224 -31
  7. data/doc/pinned-behaviour.md +1117 -0
  8. data/exe/badline +22 -2
  9. data/exe/badline-sid +34 -0
  10. data/lib/badline/address_bus.rb +96 -25
  11. data/lib/badline/audio/aiff.rb +36 -0
  12. data/lib/badline/audio/bare_player.rb +129 -0
  13. data/lib/badline/audio/cli.rb +133 -0
  14. data/lib/badline/audio/console.rb +75 -0
  15. data/lib/badline/audio/jukebox.rb +82 -0
  16. data/lib/badline/audio/machine_player.rb +57 -0
  17. data/lib/badline/audio/options.rb +137 -0
  18. data/lib/badline/audio/pcm_writer.rb +57 -0
  19. data/lib/badline/audio/playback.rb +108 -0
  20. data/lib/badline/audio/renderer.rb +85 -0
  21. data/lib/badline/audio/sdl_sink.rb +102 -0
  22. data/lib/badline/audio/wav.rb +23 -0
  23. data/lib/badline/audio.rb +14 -0
  24. data/lib/badline/cartridge/action_replay.rb +111 -0
  25. data/lib/badline/cartridge/atomic_power.rb +24 -0
  26. data/lib/badline/cartridge/bank.rb +81 -0
  27. data/lib/badline/cartridge/comal80.rb +48 -0
  28. data/lib/badline/cartridge/dinamic.rb +34 -0
  29. data/lib/badline/cartridge/easy_flash.rb +122 -0
  30. data/lib/badline/cartridge/epyx_fastload.rb +71 -0
  31. data/lib/badline/cartridge/final_cartridge3.rb +77 -0
  32. data/lib/badline/cartridge/flash.rb +265 -0
  33. data/lib/badline/cartridge/freezer.rb +31 -0
  34. data/lib/badline/cartridge/fun_play.rb +38 -0
  35. data/lib/badline/cartridge/g_mod2.rb +62 -0
  36. data/lib/badline/cartridge/game_system.rb +40 -0
  37. data/lib/badline/cartridge/kcs_power.rb +72 -0
  38. data/lib/badline/cartridge/mach5.rb +34 -0
  39. data/lib/badline/cartridge/magic_desk.rb +6 -3
  40. data/lib/badline/cartridge/ocean.rb +11 -12
  41. data/lib/badline/cartridge/pagefox.rb +56 -0
  42. data/lib/badline/cartridge/retro_replay.rb +276 -0
  43. data/lib/badline/cartridge/rex_utility.rb +33 -0
  44. data/lib/badline/cartridge/rgcd.rb +48 -0
  45. data/lib/badline/cartridge/simons_basic.rb +40 -0
  46. data/lib/badline/cartridge/standard.rb +3 -19
  47. data/lib/badline/cartridge/super_games.rb +36 -0
  48. data/lib/badline/cartridge/westermann.rb +34 -0
  49. data/lib/badline/cartridge/zaxxon.rb +50 -0
  50. data/lib/badline/cartridge.rb +132 -16
  51. data/lib/badline/cia/interrupt_register.rb +102 -0
  52. data/lib/badline/cia/serial.rb +219 -0
  53. data/lib/badline/cia/timer.rb +43 -31
  54. data/lib/badline/cia.rb +138 -88
  55. data/lib/badline/color_memory.rb +14 -5
  56. data/lib/badline/computer.rb +97 -12
  57. data/lib/badline/control_ports.rb +54 -9
  58. data/lib/badline/cpu/addressing.rb +131 -0
  59. data/lib/badline/cpu/microcode.rb +169 -0
  60. data/lib/badline/cpu/operations.rb +88 -0
  61. data/lib/badline/cpu/stack_operations.rb +82 -0
  62. data/lib/badline/cpu.rb +109 -168
  63. data/lib/badline/cycleable.rb +3 -0
  64. data/lib/badline/datasette.rb +81 -0
  65. data/lib/badline/debug_register.rb +28 -0
  66. data/lib/badline/gui/application.rb +79 -18
  67. data/lib/badline/gui/gamepads.rb +97 -0
  68. data/lib/badline/gui/joy_map.rb +15 -7
  69. data/lib/badline/gui.rb +1 -0
  70. data/lib/badline/input/mouse1351.rb +41 -0
  71. data/lib/badline/input/paddles.rb +36 -0
  72. data/lib/badline/input.rb +4 -0
  73. data/lib/badline/instruction_set/arithmetic.rb +16 -21
  74. data/lib/badline/instruction_set/bitwise.rb +17 -22
  75. data/lib/badline/instruction_set/branch.rb +20 -19
  76. data/lib/badline/instruction_set/flag.rb +7 -7
  77. data/lib/badline/instruction_set/illegal.rb +14 -22
  78. data/lib/badline/instruction_set/inc_dec.rb +8 -10
  79. data/lib/badline/instruction_set/stack.rb +5 -57
  80. data/lib/badline/instruction_set/transfer.rb +9 -9
  81. data/lib/badline/instruction_set.rb +9 -31
  82. data/lib/badline/interrupts.rb +104 -0
  83. data/lib/badline/joystick.rb +9 -9
  84. data/lib/badline/kernal_trap/channel.rb +51 -0
  85. data/lib/badline/kernal_trap/drive/status.rb +49 -0
  86. data/lib/badline/kernal_trap/drive.rb +277 -0
  87. data/lib/badline/kernal_trap/file.rb +5 -25
  88. data/lib/badline/kernal_trap/load.rb +86 -24
  89. data/lib/badline/kernal_trap/routine.rb +51 -0
  90. data/lib/badline/kernal_trap/save.rb +46 -14
  91. data/lib/badline/kernal_trap/serial.rb +171 -0
  92. data/lib/badline/kernal_trap.rb +4 -0
  93. data/lib/badline/keyboard.rb +40 -16
  94. data/lib/badline/media.rb +48 -10
  95. data/lib/badline/memory.rb +5 -0
  96. data/lib/badline/roms/eapi/LICENSE.md +15 -0
  97. data/lib/badline/roms/eapi/README +41 -0
  98. data/lib/badline/roms/eapi/eapi-am29f040-14 +0 -0
  99. data/lib/badline/sid/decimator.rb +46 -0
  100. data/lib/badline/sid/envelope.rb +232 -0
  101. data/lib/badline/sid/filter.rb +213 -0
  102. data/lib/badline/sid/voice.rb +61 -0
  103. data/lib/badline/sid/waveform/combined.rb +126 -0
  104. data/lib/badline/sid/waveform/fast_forward.rb +105 -0
  105. data/lib/badline/sid/waveform/noise_writeback.rb +82 -0
  106. data/lib/badline/sid/waveform.rb +289 -0
  107. data/lib/badline/sid.rb +274 -7
  108. data/lib/badline/status.rb +251 -23
  109. data/lib/badline/storage/crt_file.rb +18 -2
  110. data/lib/badline/storage/d64_image.rb +6 -0
  111. data/lib/badline/storage/d71_image.rb +4 -0
  112. data/lib/badline/storage/d81_image.rb +4 -0
  113. data/lib/badline/storage/disk_image.rb +99 -13
  114. data/lib/badline/storage/host_directory.rb +30 -4
  115. data/lib/badline/storage/sid_file.rb +274 -0
  116. data/lib/badline/storage/song_lengths.rb +65 -0
  117. data/lib/badline/storage/t64.rb +67 -0
  118. data/lib/badline/storage/tap.rb +68 -0
  119. data/lib/badline/storage.rb +19 -0
  120. data/lib/badline/time_of_day.rb +105 -52
  121. data/lib/badline/version.rb +1 -1
  122. data/lib/badline/vic/bank.rb +14 -6
  123. data/lib/badline/vic/border_mask.rb +76 -0
  124. data/lib/badline/vic/collisions.rb +104 -0
  125. data/lib/badline/vic/color_patches.rb +33 -0
  126. data/lib/badline/vic/display_state.rb +92 -29
  127. data/lib/badline/vic/graphics_mode.rb +78 -53
  128. data/lib/badline/vic/graphics_shifter.rb +140 -0
  129. data/lib/badline/vic/register_log.rb +63 -0
  130. data/lib/badline/vic/registers.rb +3 -0
  131. data/lib/badline/vic/sequencer.rb +163 -137
  132. data/lib/badline/vic/sequencer_output.rb +116 -0
  133. data/lib/badline/vic/sprite/internal_bus.rb +49 -0
  134. data/lib/badline/vic/sprite/shifter.rb +129 -0
  135. data/lib/badline/vic/sprite.rb +275 -65
  136. data/lib/badline/vic/sprites.rb +220 -58
  137. data/lib/badline/vic.rb +389 -55
  138. data/lib/badline.rb +5 -0
  139. data/vendor/.gitkeep +0 -0
  140. metadata +104 -4
@@ -0,0 +1,276 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Cartridge
5
+ # Retro Replay: an Am29F010 flash chip of two 64K halves, each eight 8K
6
+ # ROM banks, and four 8K RAM banks, with the Action Replay's register at
7
+ # $DE00 and an extended one at $DE01. The Nordic Replay (subtype 1) adds
8
+ # the Nordic Power configuration. The clock port isn't there.
9
+ #
10
+ # CRT banks 0-7 are the chip's upper half and banks 8-15 its lower half.
11
+ # The cartridge runs from the upper half, or from the lower one with the
12
+ # bank jumper set.
13
+ #
14
+ # $DE00 writes:
15
+ # bit 0 pulls GAME low
16
+ # bit 1 releases EXROM
17
+ # bit 2 switches the registers off until the next freeze or reset
18
+ # bits 3-4,7 bank
19
+ # bit 5 RAM instead of ROM
20
+ # bit 6 acknowledges a freeze: until then the cartridge stays in
21
+ # Ultimax mode with nothing at ROML
22
+ #
23
+ # $DE01 writes the bank again, and once only AllowBank (bit 1), which
24
+ # banks the RAM in I/O as well, NoFreeze (bit 2) and the REU-compatible
25
+ # map (bit 6), which moves the I/O RAM from I/O 2 to I/O 1 above $DE01.
26
+ # $DE00 and $DE01 read back the bank, the write-once bits and the freeze
27
+ # button.
28
+ #
29
+ # The VIC sees Ultimax mode only from a freeze to the next $DE00 write.
30
+ #
31
+ # The RAM at ROML takes writes only in Ultimax mode, except on the Nordic
32
+ # Replay. With RAM and only EXROM released ($22) the Retro Replay maps
33
+ # nothing at ROML and keeps the RAM in I/O, and the Nordic Replay maps
34
+ # the ROM at ROML and the RAM at ROMH, as the Nordic Power does. Frozen
35
+ # in that configuration, the Nordic Replay maps the RAM at $A000.
36
+ #
37
+ # The flash jumper puts the cartridge in flash mode, which starts with
38
+ # the cartridge switched off ($02) and has no freeze. $DE00 selects 8K
39
+ # mode unless it switches the cartridge off, and writes to ROML reach
40
+ # the flash, and the C64's RAM underneath, or the RAM when selected.
41
+ # $DE01 isn't write-once and has no REU-compatible map, and with the
42
+ # bank jumper set its bit 5 clear selects the other half of the flash,
43
+ # until the next $DE00 write. $DE00 reads the flash jumper in bit 0 and
44
+ # the half in bit 5.
45
+ class RetroReplay < Cartridge
46
+ include Freezer
47
+
48
+ CONTROL_MODES = %i[rom8k rom16k off ultimax].freeze
49
+ IO1_PAGE = 0x1e00
50
+ IO2_PAGE = 0x1f00
51
+
52
+ attr_reader :flash
53
+
54
+ def initialize(crt, flash_jumper: false, bank_jumper: false)
55
+ @nordic = crt.subtype == 1
56
+ @flash_jumper = flash_jumper
57
+ @bank_jumper = bank_jumper
58
+ super(crt)
59
+ end
60
+
61
+ def clock=(clock)
62
+ super
63
+ @flash.clock = clock
64
+ end
65
+
66
+ def connect(ram:, open_bus:)
67
+ super
68
+ @through.each { |b| b.backing = ram }
69
+ configure
70
+ end
71
+
72
+ def readable_io_pages
73
+ [0xde, 0xdf]
74
+ end
75
+
76
+ def peek(addr)
77
+ return open_bus(addr) unless @active
78
+
79
+ addr < 0xdf00 ? io1_peek(addr) : io2_peek(addr)
80
+ end
81
+
82
+ def poke(addr, value)
83
+ return unless @active
84
+
85
+ low = addr & 0xff
86
+ if addr >= 0xdf00
87
+ io_ram[IO2_PAGE | low] = value if io_ram_writable?(!@reu_mapping)
88
+ elsif low > 1
89
+ io_ram[IO1_PAGE | low] = value if io_ram_writable?(@reu_mapping)
90
+ else
91
+ low.zero? ? control(value) : extended_control(value)
92
+ end
93
+ end
94
+
95
+ def phi1_ultimax?
96
+ @phi1_ultimax
97
+ end
98
+
99
+ def ultimax_a000
100
+ @isolated[ram_view_bank] if @nordic && @ram_at_a000 && @frozen
101
+ end
102
+
103
+ # Reset leaves the write-once bits of $DE01 as they are.
104
+ def reset
105
+ @active = true
106
+ @frozen = false
107
+ self.nmi = false
108
+ control(@flash_jumper ? 0x02 : 0x00)
109
+ end
110
+
111
+ def freeze!
112
+ @active = @frozen = @phi1_ultimax = true
113
+ @ram_enabled = false
114
+ @bank = 0
115
+ @config = :ultimax
116
+ configure
117
+ end
118
+
119
+ private
120
+
121
+ def freeze_allowed?
122
+ !@no_freeze && !@flash_jumper
123
+ end
124
+
125
+ def status
126
+ ((@bank & 0x03) << 3) | ((@bank & 0x04) << 5) | ((@bank & 0x08) << 2) | (@flash_jumper ? 0x01 : 0) |
127
+ (@allow_bank ? 0x02 : 0) | (button_pressed? ? 0x04 : 0) | (@reu_mapping ? 0x40 : 0)
128
+ end
129
+
130
+ def io1_peek(addr)
131
+ return status if (addr & 0xff) < 2
132
+ return open_bus(addr) unless @reu_mapping && !@frozen
133
+
134
+ io_window(addr, IO1_PAGE)
135
+ end
136
+
137
+ def io2_peek(addr)
138
+ return open_bus(addr) if @reu_mapping || @frozen
139
+
140
+ io_window(addr, IO2_PAGE)
141
+ end
142
+
143
+ # The I/O RAM when selected, otherwise the ROM, which 16K and Ultimax
144
+ # mode leave out.
145
+ def io_window(addr, page)
146
+ if @ram_enabled || @ram_at_a000
147
+ io_ram[page | (addr & 0xff)]
148
+ elsif @config == :rom8k || @config == :off
149
+ @rom.peek(addr)
150
+ else
151
+ open_bus(addr)
152
+ end
153
+ end
154
+
155
+ def io_ram
156
+ @ram_data[ram_view_bank]
157
+ end
158
+
159
+ def io_ram_writable?(window)
160
+ window && !@frozen && (@ram_enabled || (@nordic && @ram_at_a000))
161
+ end
162
+
163
+ def ram_view_bank
164
+ @allow_bank ? @bank & 0x03 : 0
165
+ end
166
+
167
+ def control(value)
168
+ value = flash_mode_control(value)
169
+ @bank = bank_bits(value)
170
+ @phi1_ultimax = false
171
+ @config = CONTROL_MODES[value & 0x03]
172
+ @ram_at_a000 = (value & 0x67) == 0x22
173
+ if @nordic && @ram_at_a000
174
+ @config = :rom16k
175
+ @ram_enabled = false
176
+ else
177
+ acknowledge_freeze if value.anybits?(0x40)
178
+ @ram_enabled = value.anybits?(0x20)
179
+ @config = :off if @ram_at_a000
180
+ end
181
+ @config = :ultimax if @frozen
182
+ configure
183
+ @active = false if value.anybits?(0x04)
184
+ end
185
+
186
+ # Flash mode leaves 8K mode only to switch the cartridge off.
187
+ def flash_mode_control(value)
188
+ return value unless @flash_jumper && (value & 0x03) != 0x02
189
+
190
+ value & ~0x03
191
+ end
192
+
193
+ def acknowledge_freeze
194
+ @frozen = false
195
+ self.nmi = false
196
+ end
197
+
198
+ def extended_control(value)
199
+ unless @write_once
200
+ @allow_bank = value.anybits?(0x02)
201
+ @no_freeze = value.anybits?(0x04)
202
+ @reu_mapping = value.anybits?(0x40) && !@flash_jumper
203
+ @write_once = !@flash_jumper
204
+ end
205
+ @bank = bank_bits(value)
206
+ @bank |= 0x08 if @flash_jumper && @bank_jumper && value.nobits?(0x20)
207
+ configure
208
+ end
209
+
210
+ def bank_bits(value)
211
+ ((value >> 3) & 0x03) | ((value >> 5) & 0x04)
212
+ end
213
+
214
+ def configure
215
+ self.mode = @config
216
+ @rom = flash_window(@bank)
217
+ @roml = roml_window
218
+ @romh = romh_window
219
+ changed!
220
+ end
221
+
222
+ def roml_window
223
+ nothing = @open_bus || EMPTY_BANK
224
+ return @ram_enabled ? WriteOnlyRAM.new(@ram_data[@bank & 0x03], nothing) : nothing if @frozen
225
+ return ram_window(@bank & 0x03) if @ram_enabled
226
+ return flash_writes if (@nordic && @ram_at_a000) || @config != :rom16k
227
+
228
+ nothing
229
+ end
230
+
231
+ # A RAM bank over the flash window takes writes for the chip and the
232
+ # C64's RAM alike.
233
+ def flash_writes
234
+ return @rom unless @flash_jumper
235
+
236
+ RAMBank.new(@rom).tap { |window| window.backing = @ram }
237
+ end
238
+
239
+ def ram_window(number)
240
+ if @config == :ultimax || @flash_jumper
241
+ @isolated[number]
242
+ else
243
+ (@nordic ? @through : @readers)[number]
244
+ end
245
+ end
246
+
247
+ def romh_window
248
+ nordic_ram = @nordic && @ram_at_a000
249
+ return @isolated[ram_view_bank] if nordic_ram && !@frozen
250
+ return @rom if @allow_bank || !(@ram_enabled || nordic_ram)
251
+
252
+ flash_window(@bank & ~0x03)
253
+ end
254
+
255
+ def flash_window(number)
256
+ @flash.window((@bank_jumper ? 0 : 0x10000) + (number * BANK_SIZE))
257
+ end
258
+
259
+ def install_chips(chips)
260
+ data = Array.new(0x20000, 0xff)
261
+ chips.each do |chip|
262
+ bytes = chip.data.first(BANK_SIZE)
263
+ data[((chip.bank & 0x0f) ^ 0x08) * BANK_SIZE, bytes.length] = bytes
264
+ end
265
+ @flash = Flash.new(data, model: Flash::AM29F010)
266
+ @flash.on_change { configure }
267
+ @ram_data = Array.new(4) { Array.new(BANK_SIZE, 0) }
268
+ @through = @ram_data.map { |data| RAMBank.new(data) }
269
+ @isolated = @ram_data.map { |data| RAMBank.new(data) }
270
+ @readers = @ram_data.map { |data| RAMReader.new(data) }
271
+ @allow_bank = @no_freeze = @reu_mapping = @write_once = false
272
+ reset
273
+ end
274
+ end
275
+ end
276
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Cartridge
5
+ # 8K. Reading $DF00-$DFBF switches the ROM out, reading $DFC0-$DFFF
6
+ # switches it back in.
7
+ class RexUtility < Cartridge
8
+ def readable_io_pages
9
+ [0xdf]
10
+ end
11
+
12
+ def peek(addr)
13
+ if addr >= 0xdf00
14
+ self.mode = (addr & 0xff) < 0xc0 ? :off : :rom8k
15
+ changed!
16
+ end
17
+ open_bus(addr)
18
+ end
19
+
20
+ def reset
21
+ self.mode = :rom8k
22
+ changed!
23
+ end
24
+
25
+ private
26
+
27
+ def install_chips(chips)
28
+ @roml = banks_from(chips).first.compact.first
29
+ reset
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Cartridge
5
+ # RGCD: up to eight 8K banks. A write to I/O 1 selects the bank from the
6
+ # low bits, and bit 3 switches the ROM out until reset. The Hucky board
7
+ # (subtype 1) inverts the bank bits and decodes only as many as it has
8
+ # banks.
9
+ class RGCD < Cartridge
10
+ def initialize(crt)
11
+ @hucky = crt.subtype == 1
12
+ super
13
+ end
14
+
15
+ def poke(addr, value)
16
+ return if addr > 0xdeff || @disabled
17
+
18
+ select(value)
19
+ changed!
20
+ end
21
+
22
+ def reset
23
+ @disabled = false
24
+ self.mode = :rom8k
25
+ select(0)
26
+ changed!
27
+ end
28
+
29
+ private
30
+
31
+ def select(value)
32
+ value ^= 0x07 if @hucky
33
+ if value.anybits?(0x08)
34
+ @disabled = true
35
+ self.mode = :off
36
+ else
37
+ @roml = bank(@banks, value & @bank_mask)
38
+ end
39
+ end
40
+
41
+ def install_chips(chips)
42
+ @banks = banks_from(chips).first
43
+ @bank_mask = @hucky ? @banks.length - 1 : 0x07
44
+ reset
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Cartridge
5
+ # 16K. Reading I/O 1 switches ROMH out (8K mode), writing it switches
6
+ # ROMH back in.
7
+ class SimonsBasic < Cartridge
8
+ def readable_io_pages
9
+ [0xde]
10
+ end
11
+
12
+ def peek(addr)
13
+ select_mode(:rom8k) if addr < 0xdf00
14
+ open_bus(addr)
15
+ end
16
+
17
+ def poke(addr, _value)
18
+ select_mode(:rom16k) if addr < 0xdf00
19
+ end
20
+
21
+ def reset
22
+ select_mode(:rom16k)
23
+ end
24
+
25
+ private
26
+
27
+ def select_mode(mode)
28
+ self.mode = mode
29
+ changed!
30
+ end
31
+
32
+ def install_chips(chips)
33
+ roml, romh = banks_from(chips)
34
+ @roml = roml.first
35
+ @romh = romh.first
36
+ reset
37
+ end
38
+ end
39
+ end
40
+ end
@@ -6,25 +6,9 @@ module Badline
6
6
  private
7
7
 
8
8
  def install_chips(chips)
9
- chips.each { |chip| install_chip(chip) }
10
- end
11
-
12
- def install_chip(chip)
13
- if chip.address >= ULTIMAX_ROMH_START
14
- @romh = rom_bank(chip.data, ULTIMAX_ROMH_START)
15
- elsif chip.address >= ROMH_START
16
- @romh = rom_bank(chip.data, ROMH_START)
17
- elsif chip.data.length > BANK_SIZE
18
- install_split_chip(chip)
19
- else
20
- @roml = rom_bank(chip.data, ROML_START)
21
- end
22
- end
23
-
24
- def install_split_chip(chip)
25
- # A single 16K chip at $8000 spans both ROML and ROMH.
26
- @roml = rom_bank(chip.data[0, BANK_SIZE], ROML_START)
27
- @romh = rom_bank(chip.data[BANK_SIZE, BANK_SIZE], ROMH_START)
9
+ roml, romh = banks_from(chips)
10
+ @roml = roml.compact.first
11
+ @romh = romh.compact.first
28
12
  end
29
13
  end
30
14
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Cartridge
5
+ # Super Games: four 16K banks. A write to I/O 2 selects the bank from bits
6
+ # 0-1, switches the ROM out with bit 2, and with bit 3 locks the register
7
+ # until reset.
8
+ class SuperGames < Cartridge
9
+ def poke(addr, value)
10
+ return if addr < 0xdf00 || @locked
11
+
12
+ select(value)
13
+ changed!
14
+ end
15
+
16
+ def reset
17
+ select(0)
18
+ changed!
19
+ end
20
+
21
+ private
22
+
23
+ def select(value)
24
+ @roml = bank(@roml_banks, value & 0x03)
25
+ @romh = bank(@romh_banks, value & 0x03)
26
+ self.mode = value.anybits?(0x04) ? :off : :rom16k
27
+ @locked = value.anybits?(0x08)
28
+ end
29
+
30
+ def install_chips(chips)
31
+ @roml_banks, @romh_banks = banks_from(chips)
32
+ reset
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Cartridge
5
+ # 16K. Reading I/O 2 switches ROMH out (8K mode) until reset.
6
+ class Westermann < Cartridge
7
+ def readable_io_pages
8
+ [0xdf]
9
+ end
10
+
11
+ def peek(addr)
12
+ if addr >= 0xdf00
13
+ self.mode = :rom8k
14
+ changed!
15
+ end
16
+ open_bus(addr)
17
+ end
18
+
19
+ def reset
20
+ self.mode = :rom16k
21
+ changed!
22
+ end
23
+
24
+ private
25
+
26
+ def install_chips(chips)
27
+ roml, romh = banks_from(chips)
28
+ @roml = roml.first
29
+ @romh = romh.first
30
+ reset
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Cartridge
5
+ # Zaxxon / Super Zaxxon: 16K mode with a 4K ROML mirrored across
6
+ # $8000-$9FFF and two 8K ROMH banks. A read through ROML selects the
7
+ # ROMH bank: $8000-$8FFF the first, $9000-$9FFF the second.
8
+ class Zaxxon < Cartridge
9
+ class Low
10
+ def initialize(rom, high)
11
+ @rom = rom
12
+ @high = high
13
+ end
14
+
15
+ def peek(addr)
16
+ @high.select(addr[12])
17
+ @rom.peek(addr)
18
+ end
19
+ end
20
+
21
+ class High
22
+ def initialize(banks)
23
+ @banks = banks
24
+ select(0)
25
+ end
26
+
27
+ def select(number)
28
+ @bank = @banks[number] || EMPTY_BANK
29
+ end
30
+
31
+ def peek(addr)
32
+ @bank.peek(addr)
33
+ end
34
+ end
35
+
36
+ def reset
37
+ @romh.select(0)
38
+ end
39
+
40
+ private
41
+
42
+ def install_chips(chips)
43
+ roml, romh = banks_from(chips)
44
+ @romh = High.new(romh)
45
+ @roml = Low.new(roml.first || EMPTY_BANK, @romh)
46
+ self.mode = :rom16k
47
+ end
48
+ end
49
+ end
50
+ end