badline 0.5.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -0
- data/CLAUDE.md +23 -23
- data/CONTRIBUTING.md +5 -0
- data/README.md +59 -1
- data/doc/pinned-behaviour.md +4 -2
- data/exe/badline-ruby +2 -1
- data/lib/badline/address_bus/saved_state.rb +72 -0
- data/lib/badline/address_bus.rb +1 -0
- data/lib/badline/audio/stream.rb +6 -0
- data/lib/badline/cartridge/action_replay.rb +15 -0
- data/lib/badline/cartridge/comal80.rb +8 -0
- data/lib/badline/cartridge/dinamic.rb +8 -0
- data/lib/badline/cartridge/easy_flash.rb +20 -1
- data/lib/badline/cartridge/epyx_fastload.rb +8 -0
- data/lib/badline/cartridge/final_cartridge3.rb +12 -0
- data/lib/badline/cartridge/flash.rb +41 -0
- data/lib/badline/cartridge/fun_play.rb +8 -0
- data/lib/badline/cartridge/g_mod2.rb +12 -0
- data/lib/badline/cartridge/game_system.rb +8 -0
- data/lib/badline/cartridge/geo_ram.rb +16 -0
- data/lib/badline/cartridge/magic_desk.rb +8 -0
- data/lib/badline/cartridge/ocean.rb +8 -0
- data/lib/badline/cartridge/pagefox.rb +12 -0
- data/lib/badline/cartridge/retro_replay.rb +44 -1
- data/lib/badline/cartridge/rgcd.rb +10 -0
- data/lib/badline/cartridge/super_games.rb +10 -0
- data/lib/badline/cartridge/zaxxon.rb +16 -0
- data/lib/badline/cartridge.rb +72 -0
- data/lib/badline/cia/interrupt_register.rb +17 -0
- data/lib/badline/cia/serial.rb +44 -0
- data/lib/badline/cia/state.rb +47 -0
- data/lib/badline/cia/timer.rb +33 -0
- data/lib/badline/cia.rb +24 -2
- data/lib/badline/computer/attachments.rb +132 -0
- data/lib/badline/computer/saved_state.rb +116 -0
- data/lib/badline/computer.rb +8 -31
- data/lib/badline/cpu/microcode.rb +8 -0
- data/lib/badline/cpu/saved_state.rb +115 -0
- data/lib/badline/cpu.rb +2 -0
- data/lib/badline/datasette.rb +27 -0
- data/lib/badline/drive1541/bus.rb +89 -13
- data/lib/badline/drive1541/disk.rb +7 -2
- data/lib/badline/drive1541/disk_state.rb +80 -0
- data/lib/badline/drive1541/idle.rb +9 -57
- data/lib/badline/drive1541/mechanism.rb +2 -0
- data/lib/badline/drive1541/mechanism_state.rb +67 -0
- data/lib/badline/drive1541/orbit.rb +81 -0
- data/lib/badline/drive1541/rotation.rb +10 -0
- data/lib/badline/drive1541/serial_port.rb +10 -0
- data/lib/badline/drive1541/sleep.rb +114 -0
- data/lib/badline/drive1541.rb +52 -2
- data/lib/badline/gui/application.rb +41 -14
- data/lib/badline/gui/gamepads.rb +7 -0
- data/lib/badline/gui/snapshots.rb +67 -0
- data/lib/badline/gui.rb +1 -0
- data/lib/badline/iec_bus.rb +16 -0
- data/lib/badline/kernal_trap/channel.rb +27 -0
- data/lib/badline/kernal_trap/drive/channels.rb +25 -0
- data/lib/badline/kernal_trap/drive/memory.rb +9 -0
- data/lib/badline/kernal_trap/drive/status.rb +8 -0
- data/lib/badline/kernal_trap/drive/write_file.rb +21 -0
- data/lib/badline/kernal_trap/drive.rb +19 -0
- data/lib/badline/kernal_trap/save.rb +8 -0
- data/lib/badline/kernal_trap/serial.rb +16 -0
- data/lib/badline/memory.rb +11 -0
- data/lib/badline/options.rb +3 -2
- data/lib/badline/ram_expansion/plus256k.rb +16 -0
- data/lib/badline/ram_expansion/plus60k.rb +12 -0
- data/lib/badline/ram_expansion/unexpanded.rb +6 -0
- data/lib/badline/reu/dma.rb +36 -0
- data/lib/badline/reu/ram.rb +15 -0
- data/lib/badline/reu/saved_state.rb +52 -0
- data/lib/badline/reu.rb +4 -0
- data/lib/badline/sdl/events.rb +2 -0
- data/lib/badline/sid/envelope/state.rb +68 -0
- data/lib/badline/sid/envelope.rb +7 -0
- data/lib/badline/sid/filter.rb +38 -0
- data/lib/badline/sid/voice.rb +10 -0
- data/lib/badline/sid/waveform/state.rb +66 -0
- data/lib/badline/sid/waveform.rb +2 -0
- data/lib/badline/sid.rb +34 -0
- data/lib/badline/snapshot/container.rb +117 -0
- data/lib/badline/snapshot/fields.rb +54 -0
- data/lib/badline/snapshot/image.rb +70 -0
- data/lib/badline/snapshot/machine_state.rb +77 -0
- data/lib/badline/snapshot/setup.rb +58 -0
- data/lib/badline/snapshot/state.rb +178 -0
- data/lib/badline/snapshot/vice/c64_mem.rb +53 -0
- data/lib/badline/snapshot/vice/cias.rb +89 -0
- data/lib/badline/snapshot/vice/cias_import.rb +70 -0
- data/lib/badline/snapshot/vice/main_cpu.rb +76 -0
- data/lib/badline/snapshot/vice/peripherals.rb +47 -0
- data/lib/badline/snapshot/vice/restore.rb +125 -0
- data/lib/badline/snapshot/vice/sid_extended.rb +96 -0
- data/lib/badline/snapshot/vice/sid_registers.rb +46 -0
- data/lib/badline/snapshot/vice/vicii.rb +119 -0
- data/lib/badline/snapshot/vice/vicii_import.rb +131 -0
- data/lib/badline/snapshot/vice.rb +74 -0
- data/lib/badline/snapshot.rb +52 -0
- data/lib/badline/storage/crt_file.rb +9 -3
- data/lib/badline/storage/d64_image.rb +2 -0
- data/lib/badline/storage/d71_image.rb +2 -0
- data/lib/badline/storage/d81_image.rb +2 -0
- data/lib/badline/storage/disk_image.rb +11 -2
- data/lib/badline/storage/host_directory.rb +5 -0
- data/lib/badline/storage/t64.rb +6 -0
- data/lib/badline/storage/tap.rb +10 -1
- data/lib/badline/storage/unavailable.rb +20 -0
- data/lib/badline/storage.rb +34 -0
- data/lib/badline/time_of_day.rb +56 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/via/control_lines.rb +15 -0
- data/lib/badline/via/fast_forward.rb +15 -0
- data/lib/badline/via/interrupt_register.rb +9 -0
- data/lib/badline/via/shift_register.rb +13 -0
- data/lib/badline/via/state.rb +42 -0
- data/lib/badline/via/timer1.rb +19 -0
- data/lib/badline/via/timer2.rb +24 -0
- data/lib/badline/via.rb +2 -0
- data/lib/badline/vic/border_mask.rb +10 -0
- data/lib/badline/vic/collisions.rb +11 -0
- data/lib/badline/vic/color_patches.rb +11 -0
- data/lib/badline/vic/display_state.rb +29 -1
- data/lib/badline/vic/graphics_shifter.rb +26 -0
- data/lib/badline/vic/register_log.rb +18 -0
- data/lib/badline/vic/registers.rb +13 -2
- data/lib/badline/vic/sequencer.rb +12 -0
- data/lib/badline/vic/sequencer_state.rb +89 -0
- data/lib/badline/vic/sprite/internal_bus.rb +11 -0
- data/lib/badline/vic/sprite/saved_state.rb +64 -0
- data/lib/badline/vic/sprite.rb +2 -0
- data/lib/badline/vic/sprites.rb +3 -0
- data/lib/badline/vic/sprites_state.rb +57 -0
- data/lib/badline/vic.rb +94 -10
- data/lib/badline.rb +1 -0
- metadata +37 -1
|
@@ -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)
|
data/lib/badline/cartridge.rb
CHANGED
|
@@ -69,9 +69,28 @@ module Badline
|
|
|
69
69
|
end
|
|
70
70
|
mapper.new(crt, **)
|
|
71
71
|
end
|
|
72
|
+
|
|
73
|
+
# A cartridge built afresh from what save_setup wrote, before
|
|
74
|
+
# load_state puts its state in.
|
|
75
|
+
def from_setup(input)
|
|
76
|
+
return GeoRAM.new(size: input.int) if input.int == GEO_RAM_SETUP
|
|
77
|
+
|
|
78
|
+
crt = Storage::CRTFile.new(bytes: input.string)
|
|
79
|
+
return RetroReplay.new(crt, flash_jumper: input.boolean?, bank_jumper: input.boolean?) if input.boolean?
|
|
80
|
+
|
|
81
|
+
from_crt(crt)
|
|
82
|
+
end
|
|
72
83
|
end
|
|
73
84
|
|
|
85
|
+
# What save_setup writes first: a cartridge from a CRT image, or a
|
|
86
|
+
# GEO-RAM.
|
|
87
|
+
CRT_SETUP = 0
|
|
88
|
+
GEO_RAM_SETUP = 1
|
|
89
|
+
|
|
90
|
+
# Keeps the image it was built from, so a snapshot's machine can build
|
|
91
|
+
# the same cartridge before the snapshot's state goes into it.
|
|
74
92
|
def initialize(crt)
|
|
93
|
+
@crt = crt
|
|
75
94
|
@name = crt.name
|
|
76
95
|
@exrom = crt.exrom
|
|
77
96
|
@game = crt.game
|
|
@@ -88,6 +107,31 @@ module Badline
|
|
|
88
107
|
@on_change = block
|
|
89
108
|
end
|
|
90
109
|
|
|
110
|
+
# What builds the same cartridge afresh (Cartridge.from_setup): the CRT
|
|
111
|
+
# image it was built from, and the jumpers of a mapper that has them.
|
|
112
|
+
def save_setup(out)
|
|
113
|
+
out.int(CRT_SETUP).string(Storage::CRTFile.encode(@crt))
|
|
114
|
+
save_jumpers(out)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# The lines, the pull on NMI, the button and the mapper's own state.
|
|
118
|
+
# Loading sets them without calling back into the machine, which maps
|
|
119
|
+
# the cartridge again afterwards.
|
|
120
|
+
def save_state(out)
|
|
121
|
+
out.marker("CARTRIDGE")
|
|
122
|
+
out.int(@exrom).int(@game).boolean(@nmi).boolean(@button == true)
|
|
123
|
+
save_mapper(out)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def load_state(input)
|
|
127
|
+
input.marker("CARTRIDGE")
|
|
128
|
+
@exrom = input.int
|
|
129
|
+
@game = input.int
|
|
130
|
+
@nmi = input.boolean?
|
|
131
|
+
@button = input.boolean?
|
|
132
|
+
load_mapper(input)
|
|
133
|
+
end
|
|
134
|
+
|
|
91
135
|
# Called with the level of the cartridge's pull on the NMI line when it
|
|
92
136
|
# changes. The line is wired-OR with CIA 2's.
|
|
93
137
|
def on_nmi_change(&block)
|
|
@@ -142,6 +186,34 @@ module Badline
|
|
|
142
186
|
|
|
143
187
|
private
|
|
144
188
|
|
|
189
|
+
def save_jumpers(out)
|
|
190
|
+
out.boolean(false)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def save_mapper(_out); end
|
|
194
|
+
|
|
195
|
+
def load_mapper(_input); end
|
|
196
|
+
|
|
197
|
+
# Where the ROML and ROMH windows point, as their places in `windows`,
|
|
198
|
+
# the banks and views the mapper picks them from, or -1 for none.
|
|
199
|
+
def save_windows(out, windows)
|
|
200
|
+
out.int(window_index(@roml, windows)).int(window_index(@romh, windows))
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def load_windows(input, windows)
|
|
204
|
+
@roml = window_at(input.int, windows)
|
|
205
|
+
@romh = window_at(input.int, windows)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def window_index(window, windows)
|
|
209
|
+
return -1 if window.nil?
|
|
210
|
+
|
|
211
|
+
windows.index { |candidate| candidate.equal?(window) } ||
|
|
212
|
+
raise(ArgumentError, "#{self.class.name}: a window that isn't among its banks")
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def window_at(index, windows) = index.negative? ? nil : windows.fetch(index)
|
|
216
|
+
|
|
145
217
|
def nmi=(level)
|
|
146
218
|
return if @nmi == level
|
|
147
219
|
|
|
@@ -34,6 +34,23 @@ module Badline
|
|
|
34
34
|
|
|
35
35
|
def interrupted? = status.value >= 0x80
|
|
36
36
|
|
|
37
|
+
def save_state(out)
|
|
38
|
+
out.int(@mask.value).int(@status.value).int(@pending)
|
|
39
|
+
[@read, @read_last_cycle, @read_two_cycles_ago].each { |read| out.optional_int(read) }
|
|
40
|
+
out.boolean(@timer_b_bug).boolean(@quiet)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def load_state(input)
|
|
44
|
+
@mask.value = input.int
|
|
45
|
+
@status.value = input.int
|
|
46
|
+
@pending = input.int
|
|
47
|
+
@read = input.optional_int
|
|
48
|
+
@read_last_cycle = input.optional_int
|
|
49
|
+
@read_two_cycles_ago = input.optional_int
|
|
50
|
+
@timer_b_bug = input.boolean?
|
|
51
|
+
@quiet = input.boolean?
|
|
52
|
+
end
|
|
53
|
+
|
|
37
54
|
# Raises IR on this cycle.
|
|
38
55
|
def assert_now
|
|
39
56
|
status.interrupt = true
|
data/lib/badline/cia/serial.rb
CHANGED
|
@@ -62,6 +62,19 @@ module Badline
|
|
|
62
62
|
@sp_out = true
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
# The bits shifted so far, and the byte waiting to go out after the
|
|
66
|
+
# one shifting.
|
|
67
|
+
attr_reader :steps, :pending
|
|
68
|
+
|
|
69
|
+
def shift_register = @shift
|
|
70
|
+
|
|
71
|
+
# Sets the data register and the shift register, as a VICE snapshot
|
|
72
|
+
# gives them.
|
|
73
|
+
def restore_shift(data, shift)
|
|
74
|
+
@data = data
|
|
75
|
+
@shift = shift
|
|
76
|
+
end
|
|
77
|
+
|
|
65
78
|
def output?
|
|
66
79
|
@control.serial_mode?
|
|
67
80
|
end
|
|
@@ -71,6 +84,25 @@ module Badline
|
|
|
71
84
|
@cnt = level unless output?
|
|
72
85
|
end
|
|
73
86
|
|
|
87
|
+
def save_state(out)
|
|
88
|
+
out.int(@data).int(@shift).int(@steps).optional_int(@empty_in).optional_int(@busy_in)
|
|
89
|
+
out.boolean(@busy).boolean(@abandoned).optional_int(@pending).boolean(@underflow_high).boolean(@in_flight)
|
|
90
|
+
out.int(@flight_up).int(@flight_down).boolean(@idle)
|
|
91
|
+
[@cnt, @cnt_in, @sp_in, @sp_out].each { |level| out.boolean(level) }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def load_state(input)
|
|
95
|
+
@data = input.int
|
|
96
|
+
@shift = input.int
|
|
97
|
+
@steps = input.int
|
|
98
|
+
@empty_in = input.optional_int
|
|
99
|
+
@busy_in = input.optional_int
|
|
100
|
+
@busy = input.boolean?
|
|
101
|
+
@abandoned = input.boolean?
|
|
102
|
+
@pending = input.optional_int
|
|
103
|
+
load_lines(input)
|
|
104
|
+
end
|
|
105
|
+
|
|
74
106
|
def write(value)
|
|
75
107
|
@data = value
|
|
76
108
|
@pending = LOAD_DELAY
|
|
@@ -132,6 +164,18 @@ module Badline
|
|
|
132
164
|
|
|
133
165
|
private
|
|
134
166
|
|
|
167
|
+
def load_lines(input)
|
|
168
|
+
@underflow_high = input.boolean?
|
|
169
|
+
@in_flight = input.boolean?
|
|
170
|
+
@flight_up = input.int
|
|
171
|
+
@flight_down = input.int
|
|
172
|
+
@idle = input.boolean?
|
|
173
|
+
@cnt = input.boolean?
|
|
174
|
+
@cnt_in = input.boolean?
|
|
175
|
+
@sp_in = input.boolean?
|
|
176
|
+
@sp_out = input.boolean?
|
|
177
|
+
end
|
|
178
|
+
|
|
135
179
|
# Without an underflow, a register with nothing counting down and
|
|
136
180
|
# nothing in the delay lines has nothing to do on a cycle.
|
|
137
181
|
def idle?
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CIA
|
|
5
|
+
# A snapshot of the chip: everything it holds but its wiring, the ports,
|
|
6
|
+
# the PB4 and CNT levels, the timers, the interrupt register, the serial
|
|
7
|
+
# port and the TOD clock.
|
|
8
|
+
module State
|
|
9
|
+
def save_state(out)
|
|
10
|
+
out.marker("CIA")
|
|
11
|
+
out.int(@data_port_a).int(@data_port_b).int(@data_dir_a).int(@data_dir_b)
|
|
12
|
+
out.boolean(@port_b4_high).boolean(@port_b4_driven_high).boolean(@cnt_high).boolean(@cnt_rise)
|
|
13
|
+
out.int(@control_a.value).int(@control_b.value)
|
|
14
|
+
@ta.save_state(out)
|
|
15
|
+
@tb.save_state(out)
|
|
16
|
+
@icr.save_state(out)
|
|
17
|
+
@serial.save_state(out)
|
|
18
|
+
@tod.save_state(out)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def load_state(input)
|
|
22
|
+
input.marker("CIA")
|
|
23
|
+
@data_port_a = input.int
|
|
24
|
+
@data_port_b = input.int
|
|
25
|
+
@data_dir_a = input.int
|
|
26
|
+
@data_dir_b = input.int
|
|
27
|
+
@port_b4_high = input.boolean?
|
|
28
|
+
@port_b4_driven_high = input.boolean?
|
|
29
|
+
@cnt_high = input.boolean?
|
|
30
|
+
@cnt_rise = input.boolean?
|
|
31
|
+
@control_a.value = input.int
|
|
32
|
+
@control_b.value = input.int
|
|
33
|
+
load_parts(input)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def load_parts(input)
|
|
39
|
+
@ta.load_state(input)
|
|
40
|
+
@tb.load_state(input)
|
|
41
|
+
@icr.load_state(input)
|
|
42
|
+
@serial.load_state(input)
|
|
43
|
+
@tod.load_state(input)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/badline/cia/timer.rb
CHANGED
|
@@ -42,6 +42,26 @@ module Badline
|
|
|
42
42
|
settle
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
# The counter, the latch and the pipeline between them. The control
|
|
46
|
+
# register is the CIA's to save.
|
|
47
|
+
def save_state(out)
|
|
48
|
+
out.int(@counter).int(@latch).int(@pipe).int(@load_delay).boolean(@reload).boolean(@underflowed)
|
|
49
|
+
out.int(@oneshot_linger).boolean(@toggle).boolean(@settled).boolean(@empty)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def load_state(input)
|
|
53
|
+
@counter = input.int
|
|
54
|
+
@latch = input.int
|
|
55
|
+
@pipe = input.int
|
|
56
|
+
@load_delay = input.int
|
|
57
|
+
@reload = input.boolean?
|
|
58
|
+
@underflowed = input.boolean?
|
|
59
|
+
@oneshot_linger = input.int
|
|
60
|
+
@toggle = input.boolean?
|
|
61
|
+
@settled = input.boolean?
|
|
62
|
+
@empty = input.boolean?
|
|
63
|
+
end
|
|
64
|
+
|
|
45
65
|
def write_control(value)
|
|
46
66
|
@toggle = true if value.anybits?(0x01) && !started?
|
|
47
67
|
@oneshot_linger = 2 if control.run_mode? && value.nobits?(0x08)
|
|
@@ -59,6 +79,19 @@ module Badline
|
|
|
59
79
|
@counter = @latch unless started?
|
|
60
80
|
end
|
|
61
81
|
|
|
82
|
+
# Whether the timer's PB output toggle is high, and its count pipeline
|
|
83
|
+
# as two bits, the newer stage high.
|
|
84
|
+
attr_reader :toggle, :pipe
|
|
85
|
+
|
|
86
|
+
# Sets the control register without its load strobe, the toggle and
|
|
87
|
+
# the count pipeline, as a VICE snapshot gives them.
|
|
88
|
+
def restore_pipeline(control, toggle, pipe)
|
|
89
|
+
@control.value = control & ~0x10
|
|
90
|
+
@toggle = toggle
|
|
91
|
+
@pipe = pipe
|
|
92
|
+
settle
|
|
93
|
+
end
|
|
94
|
+
|
|
62
95
|
private
|
|
63
96
|
|
|
64
97
|
def started?
|
data/lib/badline/cia.rb
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
require "badline/cia/interrupt_register"
|
|
4
4
|
require "badline/cia/serial"
|
|
5
5
|
require "badline/cia/timer"
|
|
6
|
+
require "badline/cia/state"
|
|
6
7
|
|
|
7
8
|
module Badline
|
|
8
9
|
# CIA (Complex Interface Adapter) chip
|
|
9
10
|
class CIA
|
|
10
11
|
include Addressable
|
|
12
|
+
include State
|
|
11
13
|
|
|
12
14
|
# The original NMOS 6526 of the breadbin C64, and the HMOS 6526A of the
|
|
13
15
|
# C64C, which differs from it only in its interrupt control register.
|
|
@@ -19,6 +21,12 @@ module Badline
|
|
|
19
21
|
# read_a and read_b, and on CIA 1 drives PB4 through port_b4_high?.
|
|
20
22
|
attr_accessor :peripheral
|
|
21
23
|
|
|
24
|
+
# Timer A and timer B.
|
|
25
|
+
def timers = [@ta, @tb]
|
|
26
|
+
|
|
27
|
+
# Port A and B's data and direction registers.
|
|
28
|
+
def port_registers = [@data_port_a, @data_port_b, @data_dir_a, @data_dir_b]
|
|
29
|
+
|
|
22
30
|
def interrupt_status = @icr.status
|
|
23
31
|
|
|
24
32
|
def interrupt_control = @icr.mask
|
|
@@ -62,6 +70,7 @@ module Badline
|
|
|
62
70
|
|
|
63
71
|
@peripheral = peripheral
|
|
64
72
|
@port_b4_handler = nil
|
|
73
|
+
@port_a_handler = nil
|
|
65
74
|
@port_b4_high = true
|
|
66
75
|
reset!
|
|
67
76
|
end
|
|
@@ -87,6 +96,7 @@ module Badline
|
|
|
87
96
|
@ta = Timer.new(@control_a)
|
|
88
97
|
@tb = Timer.new(@control_b)
|
|
89
98
|
@serial = Serial.new(@control_a)
|
|
99
|
+
@port_a_handler&.call
|
|
90
100
|
end
|
|
91
101
|
|
|
92
102
|
# Register a change handler on the PB4 line. On CIA 1, this feeds the
|
|
@@ -95,6 +105,13 @@ module Badline
|
|
|
95
105
|
@port_b4_handler = handler
|
|
96
106
|
end
|
|
97
107
|
|
|
108
|
+
# Register a handler for writes that can move port A's lines: to the
|
|
109
|
+
# data register or DDR, and the reset. On CIA 2, this tells the drives
|
|
110
|
+
# on the serial bus that the C64 may have moved a line.
|
|
111
|
+
def on_port_a_write(&handler)
|
|
112
|
+
@port_a_handler = handler
|
|
113
|
+
end
|
|
114
|
+
|
|
98
115
|
# A falling edge on the FLAG pin. On CIA 1 the datasette's tape read
|
|
99
116
|
# line drives it, on CIA 2 the serial bus SRQ.
|
|
100
117
|
def flag! = raise_interrupt(:flag)
|
|
@@ -153,9 +170,9 @@ module Badline
|
|
|
153
170
|
|
|
154
171
|
def poke(addr, value)
|
|
155
172
|
case offset_of(addr) & 0x0f
|
|
156
|
-
when 0x00 then @data_port_a = value
|
|
173
|
+
when 0x00 then update_port_a { @data_port_a = value }
|
|
157
174
|
when 0x01 then update_port_b { @data_port_b = value }
|
|
158
|
-
when 0x02 then @data_dir_a = value
|
|
175
|
+
when 0x02 then update_port_a { @data_dir_a = value }
|
|
159
176
|
when 0x03 then update_port_b { @data_dir_b = value }
|
|
160
177
|
when 0x04 then @ta.write_latch_low(value)
|
|
161
178
|
when 0x05 then @ta.write_latch_high(value)
|
|
@@ -174,6 +191,11 @@ module Badline
|
|
|
174
191
|
|
|
175
192
|
private
|
|
176
193
|
|
|
194
|
+
def update_port_a
|
|
195
|
+
yield
|
|
196
|
+
@port_a_handler&.call
|
|
197
|
+
end
|
|
198
|
+
|
|
177
199
|
def update_port_b
|
|
178
200
|
yield
|
|
179
201
|
@port_b4_driven_high = driven_lines(@data_port_b, @data_dir_b).anybits?(0x10)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Computer
|
|
5
|
+
# What plugs into the machine besides its chips, the cartridge, the
|
|
6
|
+
# disk device 8 serves through the traps, a true 1541 and an REU, and
|
|
7
|
+
# their state for a snapshot.
|
|
8
|
+
module Attachments
|
|
9
|
+
# Puts the cartridge in the expansion port and wires its clock and NMI
|
|
10
|
+
# line to the machine, leaving the machine's state as it is. A
|
|
11
|
+
# restored snapshot's cartridge comes back through here.
|
|
12
|
+
def connect_cartridge(cartridge)
|
|
13
|
+
cartridge.clock = -> { @cycles }
|
|
14
|
+
cartridge.on_nmi_change { |level| @cartridge_nmi = level }
|
|
15
|
+
address_bus.attach_cartridge(cartridge)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Puts the storage in device 8. Mounting again swaps the disk at any
|
|
19
|
+
# point while the machine runs: the drive keeps its RAM, which only a
|
|
20
|
+
# drive reset clears, and its status.
|
|
21
|
+
def mount(storage)
|
|
22
|
+
return @drive.insert(storage) if @drive
|
|
23
|
+
|
|
24
|
+
@drive = KernalTrap::Drive.new(storage)
|
|
25
|
+
load_trap = KernalTrap::Load.new(cpu:, bus: address_bus, drive: @drive)
|
|
26
|
+
cpu.install_trap(KernalTrap::Load::ADDRESS) { load_trap.call }
|
|
27
|
+
@serial_trap = KernalTrap::Serial.new(cpu:, bus: address_bus, drive: @drive, device: serial_trap_device).install
|
|
28
|
+
save_trap = @save_trap = KernalTrap::Save.new(cpu:, bus: address_bus, drive: @drive)
|
|
29
|
+
cpu.install_trap(KernalTrap::Save::ADDRESS) { save_trap.call }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Takes device 8's mounted storage out, and with it the LOAD, SAVE and
|
|
33
|
+
# serial traps, so the KERNAL's routines go out over the serial bus.
|
|
34
|
+
# Mounting again starts a new drive, with its RAM cleared.
|
|
35
|
+
def unmount
|
|
36
|
+
return unless @drive
|
|
37
|
+
|
|
38
|
+
cpu.remove_trap(KernalTrap::Load::ADDRESS)
|
|
39
|
+
cpu.remove_trap(KernalTrap::Save::ADDRESS)
|
|
40
|
+
@serial_trap.device = nil
|
|
41
|
+
@serial_trap = nil
|
|
42
|
+
@save_trap = nil
|
|
43
|
+
@drive = nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Whether device 8 serves a disk or directory through the traps.
|
|
47
|
+
def mounted? = !@drive.nil?
|
|
48
|
+
|
|
49
|
+
# Unplugs the Drive1541, leaving the serial bus with nothing on it.
|
|
50
|
+
def detach_drive1541
|
|
51
|
+
return unless @drive1541
|
|
52
|
+
|
|
53
|
+
@iec_bus&.detach(@drive1541)
|
|
54
|
+
@drive1541 = nil
|
|
55
|
+
@serial_trap&.device = serial_trap_device
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def save_cartridge(out)
|
|
61
|
+
cartridge = address_bus.cartridge
|
|
62
|
+
out.boolean(!cartridge.nil?)
|
|
63
|
+
cartridge&.save_setup(out)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# A cartridge comes back built afresh from the setup it was built
|
|
67
|
+
# with, before the address bus puts its state into it.
|
|
68
|
+
def load_cartridge(input)
|
|
69
|
+
if input.boolean?
|
|
70
|
+
connect_cartridge(Cartridge.from_setup(input))
|
|
71
|
+
elsif address_bus.cartridge
|
|
72
|
+
address_bus.detach_cartridge
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# The disk device 8 serves through the traps, with the traps' own
|
|
77
|
+
# state: open channels, the drive's status and RAM, a SAVE under way.
|
|
78
|
+
def save_trap_drive(out)
|
|
79
|
+
out.boolean(!@drive.nil?)
|
|
80
|
+
return unless @drive
|
|
81
|
+
|
|
82
|
+
@drive.save_state(out)
|
|
83
|
+
@serial_trap.save_state(out)
|
|
84
|
+
@save_trap.save_state(out)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def load_trap_drive(input)
|
|
88
|
+
return unmount unless input.boolean?
|
|
89
|
+
|
|
90
|
+
storage = Storage.reopen(input)
|
|
91
|
+
unmount
|
|
92
|
+
mount(storage)
|
|
93
|
+
@drive.load_state(input)
|
|
94
|
+
@serial_trap.load_state(input)
|
|
95
|
+
@save_trap.load_state(input)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The serial bus, whose presence decides what CIA 2's port A reads,
|
|
99
|
+
# and a true 1541 on it.
|
|
100
|
+
def save_serial_bus(out)
|
|
101
|
+
out.boolean(!@iec_bus.nil?).boolean(!@drive1541.nil?)
|
|
102
|
+
return unless @drive1541
|
|
103
|
+
|
|
104
|
+
out.int(@drive1541.device)
|
|
105
|
+
@drive1541.save_state(out)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def load_serial_bus(input)
|
|
109
|
+
iec_bus if input.boolean?
|
|
110
|
+
return detach_drive1541 unless input.boolean?
|
|
111
|
+
|
|
112
|
+
device = input.int
|
|
113
|
+
detach_drive1541 if @drive1541 && @drive1541.device != device
|
|
114
|
+
attach_drive1541(Drive1541.new(device:)) unless @drive1541
|
|
115
|
+
@drive1541.load_state(input)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# The REU, which the setup says the machine has, and whether it has
|
|
119
|
+
# asked for the bus. Its IRQ line reaches the CPU as it was.
|
|
120
|
+
def save_reu(out)
|
|
121
|
+
@reu&.save_state(out)
|
|
122
|
+
out.boolean(@dma)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def load_reu(input)
|
|
126
|
+
@reu&.load_state(input)
|
|
127
|
+
@reu_irq = @reu ? @reu.irq? : false
|
|
128
|
+
@dma = input.boolean?
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|