badline 0.14.0 → 0.15.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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +86 -0
  3. data/CLAUDE.md +22 -5
  4. data/README.md +101 -11
  5. data/doc/media.md +19 -0
  6. data/doc/pinned-behaviour.md +145 -0
  7. data/doc/snapshot-format.md +244 -46
  8. data/doc/snapshots.md +18 -1
  9. data/exe/badline-ruby +1 -1
  10. data/lib/badline/audio/bare_player.rb +4 -8
  11. data/lib/badline/audio/machine_player.rb +4 -26
  12. data/lib/badline/c128/banks.rb +1 -3
  13. data/lib/badline/c128/bus.rb +19 -1
  14. data/lib/badline/c128/mmu_pages.rb +7 -0
  15. data/lib/badline/c64/bus/extra_sids.rb +80 -0
  16. data/lib/badline/c64/bus.rb +1 -0
  17. data/lib/badline/c64/cartridge/action_replay.rb +6 -2
  18. data/lib/badline/c64/cartridge/comal80.rb +1 -0
  19. data/lib/badline/c64/cartridge/dinamic.rb +3 -1
  20. data/lib/badline/c64/cartridge/final_cartridge3.rb +1 -0
  21. data/lib/badline/c64/cartridge/fun_play.rb +2 -1
  22. data/lib/badline/c64/cartridge/game_system.rb +2 -1
  23. data/lib/badline/c64/cartridge/keyed_state.rb +155 -34
  24. data/lib/badline/c64/cartridge/magic_desk.rb +2 -1
  25. data/lib/badline/c64/cartridge/ocean.rb +1 -0
  26. data/lib/badline/c64/cartridge/pagefox.rb +1 -0
  27. data/lib/badline/c64/cartridge/rgcd.rb +2 -1
  28. data/lib/badline/c64/cartridge/super_games.rb +3 -2
  29. data/lib/badline/c64/cartridge/zaxxon.rb +1 -0
  30. data/lib/badline/c64/cartridge.rb +3 -3
  31. data/lib/badline/c64/keyed_modules.rb +6 -2
  32. data/lib/badline/c64/true_drives.rb +13 -1
  33. data/lib/badline/c64.rb +6 -0
  34. data/lib/badline/chips/ted/beam.rb +13 -14
  35. data/lib/badline/chips/ted/fetches.rb +199 -0
  36. data/lib/badline/chips/ted/keyed_state.rb +156 -0
  37. data/lib/badline/chips/ted/modes.rb +134 -0
  38. data/lib/badline/chips/ted/palette.rb +43 -0
  39. data/lib/badline/chips/ted/picture.rb +182 -0
  40. data/lib/badline/chips/ted/sound/output.rb +86 -0
  41. data/lib/badline/chips/ted/sound.rb +284 -0
  42. data/lib/badline/chips/ted.rb +53 -6
  43. data/lib/badline/chips/tia.rb +28 -0
  44. data/lib/badline/chips/vic/register_log.rb +4 -1
  45. data/lib/badline/drives/datasette/keyed_state.rb +14 -4
  46. data/lib/badline/drives/datasette.rb +22 -5
  47. data/lib/badline/drives/engine/core.rb +6 -14
  48. data/lib/badline/drives/engine/idle.rb +42 -4
  49. data/lib/badline/drives/engine/idle_cpu.rb +18 -0
  50. data/lib/badline/drives/engine/serial_connection.rb +25 -0
  51. data/lib/badline/drives/engine/sleep.rb +1 -0
  52. data/lib/badline/drives/engine.rb +1 -0
  53. data/lib/badline/drives/model1541/byte_ready.rb +19 -3
  54. data/lib/badline/drives/model1541/mechanism.rb +10 -1
  55. data/lib/badline/drives/model1541.rb +1 -0
  56. data/lib/badline/drives/model1551/bus.rb +138 -0
  57. data/lib/badline/drives/model1551/cable.rb +104 -0
  58. data/lib/badline/drives/model1551/cpu.rb +14 -0
  59. data/lib/badline/drives/model1551/keyed_state.rb +65 -0
  60. data/lib/badline/drives/model1551/port.rb +74 -0
  61. data/lib/badline/drives/model1551/tia.rb +92 -0
  62. data/lib/badline/drives/model1551.rb +170 -0
  63. data/lib/badline/drives/model1571.rb +1 -0
  64. data/lib/badline/drives/model1581.rb +1 -0
  65. data/lib/badline/frontend/boot.rb +23 -2
  66. data/lib/badline/frontend/controls.rb +7 -3
  67. data/lib/badline/frontend/keys.rb +14 -2
  68. data/lib/badline/frontend/media_slots.rb +11 -1
  69. data/lib/badline/frontend/menu/menu_dialogs.rb +14 -5
  70. data/lib/badline/frontend/menu/menu_media.rb +102 -14
  71. data/lib/badline/frontend/menu/menu_pages.rb +16 -11
  72. data/lib/badline/frontend/menu/menu_ports.rb +13 -1
  73. data/lib/badline/frontend/player/player_controller.rb +11 -4
  74. data/lib/badline/frontend/screen.rb +33 -12
  75. data/lib/badline/frontend/screens.rb +2 -1
  76. data/lib/badline/frontend/sound.rb +4 -4
  77. data/lib/badline/frontend/timeline.rb +19 -6
  78. data/lib/badline/machine/settling.rb +1 -1
  79. data/lib/badline/machine.rb +30 -6
  80. data/lib/badline/media/plus4_media.rb +68 -0
  81. data/lib/badline/media/program.rb +20 -6
  82. data/lib/badline/media/true_drive.rb +7 -5
  83. data/lib/badline/media.rb +33 -12
  84. data/lib/badline/options/family.rb +1 -0
  85. data/lib/badline/options/help.rb +32 -6
  86. data/lib/badline/options/option.rb +2 -1
  87. data/lib/badline/options/table.rb +29 -4
  88. data/lib/badline/options/validation.rb +66 -4
  89. data/lib/badline/options.rb +71 -11
  90. data/lib/badline/plus4/acia/frames.rb +55 -0
  91. data/lib/badline/plus4/acia.rb +276 -16
  92. data/lib/badline/plus4/bus.rb +245 -46
  93. data/lib/badline/plus4/cassette_sense.rb +21 -0
  94. data/lib/badline/plus4/keyboard_port.rb +35 -10
  95. data/lib/badline/plus4/keyed_modules.rb +116 -0
  96. data/lib/badline/plus4/keyed_parts.rb +172 -0
  97. data/lib/badline/plus4/keyed_state.rb +44 -0
  98. data/lib/badline/plus4/ram_expansion.rb +145 -0
  99. data/lib/badline/plus4/sid_card.rb +95 -0
  100. data/lib/badline/plus4/sound_mix.rb +51 -0
  101. data/lib/badline/plus4/tcbm_drive.rb +120 -0
  102. data/lib/badline/plus4/user_port.rb +11 -2
  103. data/lib/badline/plus4.rb +184 -26
  104. data/lib/badline/roms/README +9 -0
  105. data/lib/badline/roms/dos1551.rom +0 -0
  106. data/lib/badline/snapshot/container.rb +2 -0
  107. data/lib/badline/snapshot/field_doc/markdown.rb +7 -6
  108. data/lib/badline/snapshot/field_doc.rb +48 -7
  109. data/lib/badline/snapshot/image.rb +12 -3
  110. data/lib/badline/snapshot/plus4_setup.rb +81 -0
  111. data/lib/badline/snapshot.rb +9 -4
  112. data/lib/badline/storage/tap.rb +21 -12
  113. data/lib/badline/support/checkpoint.rb +3 -2
  114. data/lib/badline/support/plus4_checkpoint.rb +42 -0
  115. data/lib/badline/version.rb +1 -1
  116. metadata +29 -1
@@ -7,7 +7,16 @@ module Badline
7
7
  # them, each over its 32 bytes of the I/O area (SIDSlots). The SID
8
8
  # player's machine has nothing else in I/O 1 and 2, so the rest of a
9
9
  # page there reads the open bus.
10
+ #
11
+ # A snapshot holds each in its own keyed module, BL-SID2 onwards in the
12
+ # order they were fitted, with the address it sits at and its model
13
+ # ahead of the SID's own fields.
10
14
  module ExtraSIDs
15
+ SID_MODEL_NAMES = %w[mos6581 mos8580].freeze
16
+
17
+ # The SIDs fitted past the first, in the order they were fitted.
18
+ attr_reader :extra_sids
19
+
11
20
  # Fits a SID at the address it was built at, where it takes its 32
12
21
  # bytes over from whatever maps there.
13
22
  def add_sid(sid)
@@ -18,14 +27,85 @@ module Badline
18
27
  @sid_slots << slots
19
28
  end
20
29
  slots.place(sid)
30
+ @extra_sids << sid
31
+ update_overlays!
32
+ end
33
+
34
+ # Takes the extra SIDs out, giving their addresses back to what maps
35
+ # there.
36
+ def remove_extra_sids
37
+ @sid_slots.clear
38
+ @extra_sids.clear
21
39
  update_overlays!
22
40
  end
23
41
 
42
+ def save_extra_sid_modules(sections)
43
+ @extra_sids.each_with_index do |sid, index|
44
+ sections << Snapshot::Keyed.section("BL-SID#{index + 2}") do |out|
45
+ out.int("address", sid.start).text("model", sid.model.to_s)
46
+ sid.save_fields(out)
47
+ end
48
+ end
49
+ sections
50
+ end
51
+
52
+ # Fits the extra SIDs the snapshot's modules hold and loads them. A
53
+ # SID already fitted at the same address stays, on the model the
54
+ # snapshot names, and one the snapshot hasn't comes out. A module
55
+ # whose address no SID fits at is left out, and reported.
56
+ def load_extra_sid_modules(modules)
57
+ addresses = []
58
+ inputs = extra_sid_inputs(modules, addresses)
59
+ models = inputs.map { |input| sid_model_named(input.choice("model", SID_MODEL_NAMES, "mos6581")) }
60
+ refit_sids(addresses, models)
61
+ @extra_sids.each_with_index do |sid, index|
62
+ model = models[index]
63
+ sid.model = model unless sid.model == model
64
+ sid.load_fields(inputs[index])
65
+ end
66
+ end
67
+
24
68
  private
25
69
 
26
70
  def map_extra_sids
27
71
  @sid_slots.each { |slots| @read_pages[slots.page] = @write_pages[slots.page] = slots }
28
72
  end
73
+
74
+ def extra_sid_inputs(modules, taken)
75
+ inputs = []
76
+ number = 2
77
+ input = modules.optional("BL-SID2")
78
+ while input
79
+ address = input.word("address", 0xd420)
80
+ if sid_fits?(address) && !taken.include?(address)
81
+ inputs << input
82
+ taken << address
83
+ else
84
+ input.adjust(format("no SID fits at $%04x, so it's left out", address))
85
+ end
86
+ number += 1
87
+ input = modules.optional("BL-SID#{number}")
88
+ end
89
+ inputs
90
+ end
91
+
92
+ # A SID's 32 bytes past the first SID's own, in $D420-$D7FF, or in
93
+ # I/O 1 or 2.
94
+ def sid_fits?(address)
95
+ address.nobits?(0x1f) && ((0xd420..0xd7e0).cover?(address) || (0xde00..0xdfe0).cover?(address))
96
+ end
97
+
98
+ def sid_model_named(name) = name == "mos8580" ? :mos8580 : :mos6581
99
+
100
+ def refit_sids(addresses, models)
101
+ fitted = @extra_sids.dup
102
+ return if fitted.map(&:start) == addresses
103
+
104
+ remove_extra_sids
105
+ addresses.each_with_index do |address, index|
106
+ add_sid(fitted.find { |sid| sid.start == address } || Chips::SID.new(model: models[index], at: address))
107
+ end
108
+ end
29
109
  end
30
110
  end
31
111
  end
@@ -91,6 +91,7 @@ module Badline
91
91
  @cia1.on_port_b4_change { |high| @vic.lightpen_level(high) }
92
92
  @sid = Chips::SID.new(model: sid_model, pots: @control_ports)
93
93
  @sid_slots = []
94
+ @extra_sids = []
94
95
 
95
96
  @datasette = Drives::Datasette.new
96
97
  @datasette.on_flag { @cia1.flag! }
@@ -22,6 +22,10 @@ module Badline
22
22
  include Freezer
23
23
 
24
24
  CONTROL_MODES = %i[rom8k rom16k off ultimax].freeze
25
+
26
+ # Ultimax mode with the RAM at ROML and I/O 2 and the first bank at
27
+ # ROMH, as a freeze leaves the register.
28
+ FREEZE_CONTROL = 0x23
25
29
  IO2_PAGE = 0x1f00
26
30
 
27
31
  def connect(ram:, open_bus:)
@@ -59,8 +63,7 @@ module Badline
59
63
 
60
64
  def freeze!
61
65
  @active = true
62
- @rom = bank(@rom_banks, 0)
63
- select(:ultimax, @isolated, io2_ram: true)
66
+ control(FREEZE_CONTROL)
64
67
  end
65
68
 
66
69
  private
@@ -72,6 +75,7 @@ module Badline
72
75
  end
73
76
 
74
77
  def control(value)
78
+ @control = value
75
79
  self.nmi = false if value.anybits?(0x40)
76
80
  @rom = bank(@rom_banks, (value >> 3) & 0x03)
77
81
  configure(value)
@@ -37,6 +37,7 @@ module Badline
37
37
  def windows = [*@roml_banks, *@romh_banks, EMPTY_BANK]
38
38
 
39
39
  def select(number, mode)
40
+ @bank = number
40
41
  @roml = bank(@roml_banks, number)
41
42
  @romh = bank(@romh_banks, number)
42
43
  self.mode = mode
@@ -12,13 +12,15 @@ module Badline
12
12
 
13
13
  def peek(addr)
14
14
  if addr < 0xde10
15
- @roml = bank(@banks, addr & 0x0f)
15
+ @bank = addr & 0x0f
16
+ @roml = bank(@banks, @bank)
16
17
  changed!
17
18
  end
18
19
  open_bus(addr)
19
20
  end
20
21
 
21
22
  def reset
23
+ @bank = 0
22
24
  @roml = bank(@banks, 0)
23
25
  changed!
24
26
  end
@@ -64,6 +64,7 @@ module Badline
64
64
  def windows = [*@roml_banks, *@romh_banks, EMPTY_BANK]
65
65
 
66
66
  def select(number)
67
+ @bank = number
67
68
  @io_bank = @roml = bank(@roml_banks, number)
68
69
  @romh = bank(@romh_banks, number)
69
70
  end
@@ -10,7 +10,8 @@ module Badline
10
10
  def poke(addr, value)
11
11
  return if addr > 0xdeff
12
12
 
13
- @roml = bank(@banks, self.class.bank_number(value))
13
+ @bank = self.class.bank_number(value)
14
+ @roml = bank(@banks, @bank)
14
15
  case value & 0xc6
15
16
  when 0x00 then self.mode = :rom8k
16
17
  when 0x86 then self.mode = :off
@@ -27,7 +27,8 @@ module Badline
27
27
  private
28
28
 
29
29
  def select(addr)
30
- @roml = bank(@banks, addr & 0x3f)
30
+ @bank = addr & 0x3f
31
+ @roml = bank(@banks, @bank)
31
32
  changed!
32
33
  end
33
34
 
@@ -6,9 +6,13 @@ module Badline
6
6
  # A cartridge's fields in a keyed snapshot module (Snapshot::Keyed,
7
7
  # BL-CART): the CRT image it was built from and its jumpers, which
8
8
  # build it again (Cartridge.from_fields), the EXROM and GAME lines,
9
- # its pull on NMI, the button, and where ROML and ROMH point, each by
10
- # the name of the bank or view it shows (window_labels), and whatever
11
- # each mapper adds.
9
+ # its pull on NMI, the button, and each mapper's own registers, from
10
+ # which a load works out where ROML and ROMH point again.
11
+ #
12
+ # Snapshots from 0.14.0 name the bank or view each window shows
13
+ # instead (window_labels), under roml and romh. A load reads those
14
+ # when the mapper's register isn't there, and works the register out
15
+ # from the windows.
12
16
  module KeyedState
13
17
  def save_fields(out)
14
18
  save_setup_fields(out)
@@ -34,12 +38,28 @@ module Badline
34
38
 
35
39
  def save_jumper_fields(_out); end
36
40
 
37
- def save_mapper_fields(out)
38
- save_window_fields(out)
39
- end
41
+ def save_mapper_fields(_out); end
42
+
43
+ def load_mapper_fields(_input); end
44
+
45
+ # The register at `key`, cut to `mask`, or nil for a snapshot that
46
+ # names the windows instead, with the windows put back from their
47
+ # names.
48
+ def latch_or_windows(input, key, default, mask)
49
+ return input.masked(key, default, mask) if input.tags.key?(key)
40
50
 
41
- def load_mapper_fields(input)
42
51
  load_window_fields(input)
52
+ nil
53
+ end
54
+
55
+ # The number of the bank among `banks` that `window` shows, or of the
56
+ # first the image leaves out for erased ROM.
57
+ def number_showing(banks, window)
58
+ number = banks.index { |each| each.equal?(window) }
59
+ return number if number
60
+ return 0 unless window.equal?(EMPTY_BANK)
61
+
62
+ banks.index(nil) || banks.length
43
63
  end
44
64
 
45
65
  # The name of each of `windows`, in the same order: bank<N> for the
@@ -51,21 +71,6 @@ module Badline
51
71
  [*Array.new(banks.length) { |number| "bank#{number}" }, "empty"]
52
72
  end
53
73
 
54
- def save_window_fields(out)
55
- choices = windows
56
- return unless choices
57
-
58
- labels = window_labels
59
- save_window(out, "roml", @roml, choices, labels)
60
- save_window(out, "romh", @romh, choices, labels)
61
- end
62
-
63
- def save_window(out, key, window, choices, labels)
64
- return if window.nil?
65
-
66
- out.text(key, labels.fetch(window_index(window, choices)))
67
- end
68
-
69
74
  def load_window_fields(input)
70
75
  choices = windows
71
76
  return unless choices
@@ -97,21 +102,104 @@ module Badline
97
102
  def from_jumper_fields(crt, _input) = new(crt)
98
103
  end
99
104
 
100
- # Two banks of ROM, one behind each window: roml<N> and romh<N>.
105
+ # One bank register, under bank, selecting the bank ROML shows.
106
+ module BankLatch
107
+ private
108
+
109
+ def save_mapper_fields(out)
110
+ out.int("bank", @bank)
111
+ end
112
+
113
+ def load_mapper_fields(input)
114
+ latch = latch_or_windows(input, "bank", @bank, bank_latch_mask)
115
+ if latch.nil?
116
+ @bank = number_showing(@banks, @roml)
117
+ else
118
+ @bank = latch
119
+ show_bank
120
+ end
121
+ end
122
+
123
+ def show_bank
124
+ @roml = bank(@banks, @bank)
125
+ end
126
+ end
127
+
128
+ # Two banks of ROM, one behind each window: roml<N> and romh<N>, both
129
+ # selected by one bank register, under bank.
101
130
  module SplitBanks
102
131
  private
103
132
 
133
+ def save_mapper_fields(out)
134
+ out.int("bank", @bank)
135
+ end
136
+
137
+ def load_mapper_fields(input)
138
+ latch = latch_or_windows(input, "bank", @bank, bank_latch_mask)
139
+ if latch.nil?
140
+ @bank = number_showing(@roml_banks, @roml)
141
+ else
142
+ @bank = latch
143
+ @roml = bank(@roml_banks, @bank)
144
+ @romh = bank(@romh_banks, @bank)
145
+ end
146
+ end
147
+
104
148
  def window_labels
105
149
  [*Array.new(@roml_banks.length) { |number| "roml#{number}" },
106
150
  *Array.new(@romh_banks.length) { |number| "romh#{number}" }, "empty"]
107
151
  end
108
152
  end
109
153
 
154
+ class Ocean
155
+ include BankLatch
156
+
157
+ private
158
+
159
+ def bank_latch_mask = @bank_mask
160
+
161
+ def show_bank = select_bank(@bank)
162
+ end
163
+
164
+ class FunPlay
165
+ include BankLatch
166
+
167
+ private
168
+
169
+ def bank_latch_mask = 0x0f
170
+ end
171
+
172
+ class GameSystem
173
+ include BankLatch
174
+
175
+ private
176
+
177
+ def bank_latch_mask = 0x3f
178
+ end
179
+
180
+ class Dinamic
181
+ include BankLatch
182
+
183
+ private
184
+
185
+ def bank_latch_mask = 0x0f
186
+ end
187
+
188
+ class MagicDesk
189
+ include BankLatch
190
+
191
+ private
192
+
193
+ def bank_latch_mask = @bank_mask
194
+ end
195
+
110
196
  class SuperGames
111
197
  include SplitBanks
112
198
 
113
199
  private
114
200
 
201
+ def bank_latch_mask = 0x03
202
+
115
203
  def save_mapper_fields(out)
116
204
  super
117
205
  out.bool("locked", @locked)
@@ -125,6 +213,10 @@ module Badline
125
213
 
126
214
  class Comal80
127
215
  include SplitBanks
216
+
217
+ private
218
+
219
+ def bank_latch_mask = 0x07
128
220
  end
129
221
 
130
222
  class FinalCartridge3
@@ -132,6 +224,8 @@ module Badline
132
224
 
133
225
  private
134
226
 
227
+ def bank_latch_mask = @bank_mask
228
+
135
229
  def save_mapper_fields(out)
136
230
  super
137
231
  out.bool("register_visible", @register_visible).bool("phi1_ultimax", @phi1_ultimax)
@@ -146,8 +240,12 @@ module Badline
146
240
  end
147
241
 
148
242
  class RGCD
243
+ include BankLatch
244
+
149
245
  private
150
246
 
247
+ def bank_latch_mask = @bank_mask
248
+
151
249
  def save_mapper_fields(out)
152
250
  super
153
251
  out.bool("disabled", @disabled)
@@ -162,12 +260,11 @@ module Badline
162
260
  class Zaxxon
163
261
  class High
164
262
  def save_fields(out)
165
- out.int("high_bank", [*@banks, EMPTY_BANK].index { |bank| bank.equal?(@bank) }.to_i)
263
+ out.int("high_bank", @number)
166
264
  end
167
265
 
168
266
  def load_fields(input)
169
- choices = [*@banks, EMPTY_BANK]
170
- @bank = choices.fetch(input.int("high_bank", 0).clamp(0, choices.length - 1))
267
+ select(input.int("high_bank", 0).clamp(0, @banks.length))
171
268
  end
172
269
  end
173
270
 
@@ -204,18 +301,35 @@ module Badline
204
301
  end
205
302
 
206
303
  def save_mapper_fields(out)
207
- super
208
- out.text("rom", window_labels.fetch(window_index(@rom, windows))).bool("active", @active)
209
- out.bool("io2_ram", @io2_ram).bytes("ram", @ram_data)
304
+ out.int("control", @control).bool("active", @active).bytes("ram", @ram_data)
210
305
  end
211
306
 
212
307
  def load_mapper_fields(input)
213
- super
308
+ latch = latch_or_windows(input, "control", @control, 0xff)
309
+ if latch.nil?
310
+ load_legacy_fields(input)
311
+ else
312
+ @control = latch
313
+ @rom = bank(@rom_banks, (@control >> 3) & 0x03)
314
+ configure(@control)
315
+ end
316
+ @active = input.bool("active", @active)
317
+ input.bytes_into("ram", @ram_data)
318
+ end
319
+
320
+ # The ROM bank by name, and whether I/O 2 shows the RAM, from which
321
+ # the register is worked out.
322
+ def load_legacy_fields(input)
214
323
  labels = window_labels
215
324
  @rom = window_named(input.choice("rom", labels, "rom0"), windows, labels)
216
- @active = input.bool("active", @active)
217
325
  @io2_ram = input.bool("io2_ram", @io2_ram)
218
- input.bytes_into("ram", @ram_data)
326
+ number = number_showing(@rom_banks, @rom) << 3
327
+ @control = if @roml.equal?(@contended) || @romh.equal?(@isolated)
328
+ 0x22 | number
329
+ else
330
+ mode = @exrom.zero? ? 1 - @game : 3 - @game
331
+ mode | (@io2_ram ? 0x20 : 0) | number
332
+ end
219
333
  end
220
334
  end
221
335
 
@@ -237,12 +351,19 @@ module Badline
237
351
  end
238
352
 
239
353
  def save_mapper_fields(out)
240
- super
354
+ out.int("control", @control)
241
355
  @ram_banks.each_with_index { |ram, number| out.bytes("ram#{number}.data", ram.data) }
242
356
  end
243
357
 
244
358
  def load_mapper_fields(input)
245
- super
359
+ latch = latch_or_windows(input, "control", @control, 0xff)
360
+ if latch.nil?
361
+ selected = (0...8).find { |select| window(select >> 1, select & 0x01) == [@roml, @romh] } || 0
362
+ @control = (selected << 1) | (@exrom == 1 && @game == 1 ? 0x10 : 0)
363
+ else
364
+ @control = latch
365
+ @roml, @romh = window((@control >> 2) & 0x03, (@control >> 1) & 0x01)
366
+ end
246
367
  @ram_banks.each_with_index { |ram, number| input.bytes_into("ram#{number}.data", ram.data) }
247
368
  end
248
369
  end
@@ -11,7 +11,8 @@ module Badline
11
11
  return if addr > 0xdeff
12
12
 
13
13
  @exrom = value.anybits?(0x80) ? 1 : 0
14
- @roml = bank(@banks, value & @bank_mask)
14
+ @bank = value & @bank_mask
15
+ @roml = bank(@banks, @bank)
15
16
  changed!
16
17
  end
17
18
 
@@ -22,6 +22,7 @@ module Badline
22
22
 
23
23
  # In 16K mode the selected bank shows through ROMH as well.
24
24
  def select_bank(number)
25
+ @bank = number
25
26
  @roml = bank(@banks, number)
26
27
  @romh = @roml if game.zero?
27
28
  end
@@ -33,6 +33,7 @@ module Badline
33
33
  def windows = [*@roml_banks, *@romh_banks, *@ram_banks, @open_bus || EMPTY_BANK, EMPTY_BANK]
34
34
 
35
35
  def select(value)
36
+ @control = value
36
37
  half = (value >> 1) & 0x01
37
38
  chip = (value >> 2) & 0x03
38
39
  @roml, @romh = window(chip, half)
@@ -35,7 +35,8 @@ module Badline
35
35
  @disabled = true
36
36
  self.mode = :off
37
37
  else
38
- @roml = bank(@banks, value & @bank_mask)
38
+ @bank = value & @bank_mask
39
+ @roml = bank(@banks, @bank)
39
40
  end
40
41
  end
41
42
 
@@ -24,8 +24,9 @@ module Badline
24
24
  def windows = [*@roml_banks, *@romh_banks, EMPTY_BANK]
25
25
 
26
26
  def select(value)
27
- @roml = bank(@roml_banks, value & 0x03)
28
- @romh = bank(@romh_banks, value & 0x03)
27
+ @bank = value & 0x03
28
+ @roml = bank(@roml_banks, @bank)
29
+ @romh = bank(@romh_banks, @bank)
29
30
  self.mode = value.anybits?(0x04) ? :off : :rom16k
30
31
  @locked = value.anybits?(0x08)
31
32
  end
@@ -26,6 +26,7 @@ module Badline
26
26
  end
27
27
 
28
28
  def select(number)
29
+ @number = number
29
30
  @bank = @banks[number] || EMPTY_BANK
30
31
  end
31
32
 
@@ -152,9 +152,9 @@ module Badline
152
152
 
153
153
  private
154
154
 
155
- # The banks and views ROML and ROMH can point at, which a snapshot
156
- # names them by: the mapper's banks, or nothing for a mapper whose
157
- # windows stay where install_chips put them.
155
+ # The banks and views ROML and ROMH can point at, which a 0.14.0
156
+ # snapshot names them by: the mapper's banks, or nothing for a mapper
157
+ # whose windows stay where install_chips put them.
158
158
  def windows
159
159
  [*@banks, EMPTY_BANK] if @banks
160
160
  end
@@ -4,8 +4,9 @@ module Badline
4
4
  class C64
5
5
  # The whole machine as keyed snapshot modules (Snapshot::Keyed), in the
6
6
  # order a load applies them: BL-MACHINE with the setup, the memories,
7
- # the cartridge, the CPU with its port, the chips, an REU, the
8
- # datasette, device 8 through the traps and the true drives. A load
7
+ # the cartridge, the CPU with its port, the chips with any extra SIDs
8
+ # (Bus::ExtraSIDs), an REU, the datasette, device 8 through the traps
9
+ # and the true drives. A load
9
10
  # reads the CPU's port before the chips, so the banks are mapped as the
10
11
  # chips load, and the CPU's own fields after them. What each unit works
11
12
  # out again from its fields comes last, once every unit is loaded.
@@ -60,6 +61,7 @@ module Badline
60
61
  sections << Snapshot::Keyed.section("BL-CIA1") { |out| @cia1.save_fields(out) }
61
62
  sections << Snapshot::Keyed.section("BL-CIA2") { |out| @cia2.save_fields(out) }
62
63
  sections << Snapshot::Keyed.section("BL-SID") { |out| @sid.save_fields(out) }
64
+ bus.save_extra_sid_modules(sections)
63
65
  end
64
66
 
65
67
  def load_chip_modules(modules, cpu)
@@ -67,6 +69,7 @@ module Badline
67
69
  @cia1.load_fields(modules.required("BL-CIA1"))
68
70
  @cia2.load_fields(modules.required("BL-CIA2"))
69
71
  @sid.load_fields(modules.required("BL-SID"))
72
+ address_bus.load_extra_sid_modules(modules)
70
73
  @reu&.load_fields(modules.required("BL-REU"))
71
74
  @cpu.load_fields(cpu)
72
75
  end
@@ -94,6 +97,7 @@ module Badline
94
97
  @cia1.restored!
95
98
  @cia2.restored!
96
99
  @sid.restored!
100
+ address_bus.extra_sids.each(&:restored!)
97
101
  @reu&.restored!
98
102
  machine_restored!
99
103
  end
@@ -9,7 +9,7 @@ module Badline
9
9
  include Drives::Model1581::Slot
10
10
 
11
11
  # The true drive models a snapshot names.
12
- DRIVE_MODELS = %w[1541 1571 1581].freeze
12
+ DRIVE_MODELS = %w[1541 1551 1571 1581].freeze
13
13
 
14
14
  # Plugs in a Drives::Model1541, which then runs alongside the machine on its
15
15
  # own clock and talks to it over the serial bus. The serial traps stop
@@ -44,6 +44,7 @@ module Badline
44
44
  drive1581 = @drive1581
45
45
  drive1581&.save_modules(sections, "BL-D#{drive1581.device}")
46
46
  save_drive1571_modules(sections)
47
+ save_drive1551_modules(sections)
47
48
  sections
48
49
  end
49
50
 
@@ -63,6 +64,7 @@ module Badline
63
64
  detach_drive1541 unless models.include?("1541")
64
65
  detach_drive1581 unless models.include?("1581")
65
66
  detach_drive1571 unless models.include?("1571")
67
+ detach_drive1551 unless models.include?("1551")
66
68
  end
67
69
 
68
70
  # Unplugs the Drives::Model1541, leaving the serial bus with nothing on it.
@@ -91,6 +93,7 @@ module Badline
91
93
  case model
92
94
  when "1581" then load_drive1581_modules(modules, input, device)
93
95
  when "1571" then load_drive1571_modules(modules, input, device)
96
+ when "1551" then load_drive1551_modules(modules, input, device)
94
97
  else load_drive1541_modules(modules, input, device)
95
98
  end
96
99
  end
@@ -115,6 +118,15 @@ module Badline
115
118
  end
116
119
 
117
120
  def detach_drive1571 = nil
121
+
122
+ # The 264 series' 1551, which only the Plus/4 has (Plus4::TCBMDrive).
123
+ def save_drive1551_modules(sections) = sections
124
+
125
+ def load_drive1551_modules(_modules, input, _device)
126
+ input.adjust("a 1551 doesn't plug into this machine, left out")
127
+ end
128
+
129
+ def detach_drive1551 = nil
118
130
  end
119
131
  end
120
132
  end
data/lib/badline/c64.rb CHANGED
@@ -68,6 +68,7 @@ module Badline
68
68
  @cia1 = @address_bus.cia1
69
69
  @cia2 = @address_bus.cia2
70
70
  @sid = @address_bus.sid
71
+ @extra_sids = @address_bus.extra_sids
71
72
  @datasette = @address_bus.datasette
72
73
  @cycles = 0
73
74
  @nmi_asserted = false
@@ -100,6 +101,7 @@ module Badline
100
101
  @cia1.cycle!
101
102
  @cia2.cycle!
102
103
  @sid.cycle!
104
+ clock_extra_sids unless @extra_sids.empty?
103
105
  @datasette.cycle!
104
106
 
105
107
  @cpu.irq = @cia1.interrupted? || @vic.interrupted? || @reu_irq
@@ -202,6 +204,10 @@ module Badline
202
204
  reu
203
205
  end
204
206
 
207
+ # The SIDs fitted past the first, which clock ahead of the CPU as the
208
+ # first does.
209
+ def clock_extra_sids = @extra_sids.each(&:cycle!)
210
+
205
211
  # BA halts the CPU on a read cycle, and so does an REU holding the bus
206
212
  # for a transfer.
207
213
  def clock_cpu