badline 0.12.0 → 0.12.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/badline/address_bus/saved_state.rb +4 -10
- data/lib/badline/address_bus.rb +8 -27
- data/lib/badline/audio/bare_player.rb +1 -1
- data/lib/badline/c128/bus.rb +13 -35
- data/lib/badline/c128/bus_state.rb +5 -5
- data/lib/badline/c128.rb +5 -64
- data/lib/badline/computer/attachments.rb +16 -45
- data/lib/badline/computer/common.rb +34 -0
- data/lib/badline/computer/kernal_traps.rb +65 -5
- data/lib/badline/computer/true_drives.rb +12 -23
- data/lib/badline/computer.rb +5 -68
- data/lib/badline/core.rb +2 -2
- data/lib/badline/cpu_port.rb +112 -0
- data/lib/badline/drive1541/disk.rb +14 -17
- data/lib/badline/drive1541/disk_state.rb +7 -15
- data/lib/badline/drive1581/wd1772.rb +1 -1
- data/lib/badline/frontend/boot.rb +8 -59
- data/lib/badline/kernal_trap/{file.rb → file_routine.rb} +1 -1
- data/lib/badline/kernal_trap/load.rb +1 -1
- data/lib/badline/kernal_trap/save.rb +1 -1
- data/lib/badline/kernal_trap.rb +1 -1
- data/lib/badline/machine/clocking.rb +50 -0
- data/lib/badline/machine.rb +58 -2
- data/lib/badline/media/boot_disk.rb +2 -2
- data/lib/badline/media/disk_list.rb +3 -3
- data/lib/badline/media/disk_set.rb +1 -1
- data/lib/badline/media/extensions.rb +33 -0
- data/lib/badline/media/program.rb +28 -0
- data/lib/badline/media/true_drive.rb +3 -2
- data/lib/badline/media/tune.rb +35 -0
- data/lib/badline/media/vic20_cartridge.rb +1 -7
- data/lib/badline/media/vic20_media.rb +38 -80
- data/lib/badline/media.rb +82 -79
- data/lib/badline/options/event.rb +3 -1
- data/lib/badline/sid.rb +1 -1
- data/lib/badline/snapshot/machine_state.rb +9 -1
- data/lib/badline/snapshot/state.rb +5 -4
- data/lib/badline/snapshot.rb +11 -9
- data/lib/badline/storage/d64_image.rb +15 -7
- data/lib/badline/storage/disk_image/writing.rb +2 -2
- data/lib/badline/storage/disk_image.rb +1 -1
- data/lib/badline/storage/host_directory.rb +1 -1
- data/lib/badline/storage/t64.rb +1 -1
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic/bank.rb +1 -1
- data/lib/badline/vic/border_mask.rb +1 -1
- data/lib/badline/vic/collisions.rb +1 -1
- data/lib/badline/vic/color_patches.rb +1 -1
- data/lib/badline/vic/display_state.rb +1 -1
- data/lib/badline/vic/graphics_mode.rb +1 -1
- data/lib/badline/vic/graphics_shifter.rb +1 -1
- data/lib/badline/vic/register_log.rb +1 -1
- data/lib/badline/vic/registers.rb +1 -1
- data/lib/badline/vic/sequencer.rb +1 -1
- data/lib/badline/vic/sequencer_output.rb +1 -1
- data/lib/badline/vic/sequencer_state.rb +1 -1
- data/lib/badline/vic/sprite/internal_bus.rb +1 -1
- data/lib/badline/vic/sprite/saved_state.rb +1 -1
- data/lib/badline/vic/sprite/shifter.rb +1 -1
- data/lib/badline/vic/sprite/timing.rb +1 -1
- data/lib/badline/vic/sprite.rb +1 -1
- data/lib/badline/vic/sprites.rb +1 -1
- data/lib/badline/vic/sprites_state.rb +1 -1
- data/lib/badline/vic.rb +6 -5
- data/lib/badline/vic20/attachments.rb +10 -80
- data/lib/badline/vic20/saved_state.rb +0 -30
- data/lib/badline/vic20.rb +1 -37
- metadata +8 -3
- data/lib/badline/cycleable.rb +0 -38
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8872c5d5117a691aa38d054acd5dd7e84f7abf737c977a2dc309c6dc3072381d
|
|
4
|
+
data.tar.gz: fa4f4e5b3729989c78443f73bd655c2083555d62a8d3ee45437d147bfe0e5fb8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d278ba0e0341242af3e1782a8c003bc4cbdb1594be434021b5bbccae126ddf12dd89ffa0520dd615075a77f89b1285595d2101868db0e6679b69130586ed210c
|
|
7
|
+
data.tar.gz: 5dabb1d53a29dc43b619f90abd67495ab3847d3ba8d8f89275d45f276aac4fbe3ca37a4963f048e300e88cd50228b35bd39da7efbd431f46527b893b1c4bd95c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.12.1](https://github.com/elektronaut/badline/compare/v0.12.0...v0.12.1) (2026-10-10)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* keep the 1581's idle deadline within a native build's integers ([a98848f](https://github.com/elektronaut/badline/commit/a98848f0889ca7ad4c12007976b63d54bbbf122b))
|
|
9
|
+
* refuse snapshot values a native build can't hold, on CRuby too ([5ec51e8](https://github.com/elektronaut/badline/commit/5ec51e81cfabfdb585367bb2f8183516ac64d2b0))
|
|
10
|
+
|
|
3
11
|
## [0.12.0](https://github.com/elektronaut/badline/compare/v0.11.1...v0.12.0) (2026-10-10)
|
|
4
12
|
|
|
5
13
|
|
|
@@ -17,15 +17,12 @@ module Badline
|
|
|
17
17
|
|
|
18
18
|
# The 6510 port's direction and output registers, and the charge left
|
|
19
19
|
# on its floating bits.
|
|
20
|
-
def port_state = [@
|
|
20
|
+
def port_state = [@cpu_port.ddr, @cpu_port.data, @cpu_port.floating]
|
|
21
21
|
|
|
22
22
|
# Sets the port as port_state reads it, as a snapshot restores it,
|
|
23
23
|
# leaving the RAM under $00 and $01 alone.
|
|
24
24
|
def restore_port(ddr, out, floating)
|
|
25
|
-
@
|
|
26
|
-
@port_out = out
|
|
27
|
-
@port_floating = floating
|
|
28
|
-
update_port!
|
|
25
|
+
@cpu_port.restore(ddr, out, floating)
|
|
29
26
|
end
|
|
30
27
|
|
|
31
28
|
# The machine's memory and everything on the bus but the keyboard, the
|
|
@@ -33,7 +30,7 @@ module Badline
|
|
|
33
30
|
# already in the port, built from its setup (Computer#load_state).
|
|
34
31
|
def save_state(out)
|
|
35
32
|
out.marker("BUS")
|
|
36
|
-
out.int(@
|
|
33
|
+
out.int(@cpu_port.ddr).int(@cpu_port.data).int(@cpu_port.floating)
|
|
37
34
|
@ram.save_state(out)
|
|
38
35
|
@color_ram.save_state(out)
|
|
39
36
|
@ram_expansion.save_state(out)
|
|
@@ -55,10 +52,7 @@ module Badline
|
|
|
55
52
|
@ram_expansion.load_state(input)
|
|
56
53
|
@cartridge&.load_state(input)
|
|
57
54
|
@datasette.load_state(input)
|
|
58
|
-
@
|
|
59
|
-
@port_out = port_out
|
|
60
|
-
@port_floating = floating
|
|
61
|
-
@io_port.value = port_value
|
|
55
|
+
@cpu_port.load(ddr, port_out, floating)
|
|
62
56
|
update_overlays!
|
|
63
57
|
@vic.load_state(input)
|
|
64
58
|
@cia1.load_state(input)
|
data/lib/badline/address_bus.rb
CHANGED
|
@@ -52,9 +52,9 @@ module Badline
|
|
|
52
52
|
def poke(_addr, _value); end
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
# The 6510's port pins with pull-ups, and the floating ones (CPUPort).
|
|
55
56
|
PORT_PULLUPS = 0b0001_0111
|
|
56
57
|
PORT_FLOATING = 0b1100_1000
|
|
57
|
-
TAPE_SENSE = 0b0001_0000
|
|
58
58
|
|
|
59
59
|
# RAM powers on in runs of $00 $00 $FF $FF $FF $FF $00 $00, inverted in
|
|
60
60
|
# the second and fourth 16K: a C64C (ASSY 250469 R4) from
|
|
@@ -92,7 +92,7 @@ module Badline
|
|
|
92
92
|
|
|
93
93
|
@datasette = Datasette.new
|
|
94
94
|
@datasette.on_flag { @cia1.flag! }
|
|
95
|
-
@datasette.on_sense_change { @
|
|
95
|
+
@datasette.on_sense_change { @cpu_port.refresh }
|
|
96
96
|
|
|
97
97
|
@color_ram = ColorMemory.new(@vic)
|
|
98
98
|
@vic.vic_bank.connect(cia2: @cia2, color_ram: @color_ram)
|
|
@@ -100,10 +100,9 @@ module Badline
|
|
|
100
100
|
@open_bus = OpenBus.new(@vic)
|
|
101
101
|
@cpu_off_bus = false
|
|
102
102
|
|
|
103
|
-
@
|
|
104
|
-
@
|
|
105
|
-
@
|
|
106
|
-
@io_port = PortStatus.new(%i[basic kernal io tape_out tape_switch tape_motor], value: port_value)
|
|
103
|
+
@cpu_port = CPUPort.new(@datasette, pullups: PORT_PULLUPS, floating: PORT_FLOATING)
|
|
104
|
+
@cpu_port.on_change { update_overlays! }
|
|
105
|
+
@io_port = @cpu_port.status
|
|
107
106
|
|
|
108
107
|
@read_pages = Array.new(256)
|
|
109
108
|
@write_pages = Array.new(256)
|
|
@@ -134,9 +133,7 @@ module Badline
|
|
|
134
133
|
# their charge.
|
|
135
134
|
def reset!
|
|
136
135
|
@ram_expansion.reset!
|
|
137
|
-
@
|
|
138
|
-
@port_out = 0x00
|
|
139
|
-
update_port!
|
|
136
|
+
@cpu_port.reset!
|
|
140
137
|
end
|
|
141
138
|
|
|
142
139
|
def disable_overlays!
|
|
@@ -150,7 +147,7 @@ module Badline
|
|
|
150
147
|
end
|
|
151
148
|
|
|
152
149
|
def peek(addr)
|
|
153
|
-
return @
|
|
150
|
+
return @cpu_port.ddr if addr.zero?
|
|
154
151
|
return @io_port.value if addr == 0x01
|
|
155
152
|
|
|
156
153
|
@read_pages[addr >> 8].peek(addr)
|
|
@@ -162,8 +159,7 @@ module Badline
|
|
|
162
159
|
def poke(addr, value)
|
|
163
160
|
if addr < 0x02
|
|
164
161
|
@ram.poke(addr, @vic.phi1_data)
|
|
165
|
-
addr.zero? ? @
|
|
166
|
-
update_port!
|
|
162
|
+
addr.zero? ? @cpu_port.write_ddr(value) : @cpu_port.write_data(value)
|
|
167
163
|
elsif !@cpu_off_bus
|
|
168
164
|
@write_pages[addr >> 8].poke(addr, value)
|
|
169
165
|
end
|
|
@@ -185,21 +181,6 @@ module Badline
|
|
|
185
181
|
|
|
186
182
|
private
|
|
187
183
|
|
|
188
|
-
def update_port!
|
|
189
|
-
driven = @port_ddr & PORT_FLOATING
|
|
190
|
-
@port_floating = (@port_floating & ~driven) | (@port_out & driven)
|
|
191
|
-
@io_port.value = port_value
|
|
192
|
-
# $01 bit 5 drives the motor through an inverter: low runs it.
|
|
193
|
-
@datasette.motor = !@io_port.tape_motor?
|
|
194
|
-
update_overlays!
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
def port_value
|
|
198
|
-
input = PORT_PULLUPS | (@port_floating & PORT_FLOATING)
|
|
199
|
-
input &= ~TAPE_SENSE if @datasette.sense_low?
|
|
200
|
-
(@port_out & @port_ddr) | (input & ~@port_ddr & 0xff)
|
|
201
|
-
end
|
|
202
|
-
|
|
203
184
|
# Banking changes only on $01 writes and cartridge line/bank changes,
|
|
204
185
|
# so reads and writes dispatch through per-page handler tables instead
|
|
205
186
|
# of range checks.
|
data/lib/badline/c128/bus.rb
CHANGED
|
@@ -47,7 +47,6 @@ module Badline
|
|
|
47
47
|
PORT_PULLUPS = 0b0001_0111
|
|
48
48
|
PORT_FLOATING = 0b1000_1000
|
|
49
49
|
CAPS_LOCK = 0b0100_0000
|
|
50
|
-
TAPE_SENSE = 0b0001_0000
|
|
51
50
|
|
|
52
51
|
# The C64's power-on pattern (AddressBus::RAM_POWER_ON) in both 64K
|
|
53
52
|
# banks. The C128's own DRAMs' pattern is unmeasured.
|
|
@@ -126,9 +125,6 @@ module Badline
|
|
|
126
125
|
# KERNAL and the character ROM's C128 set.
|
|
127
126
|
attr_reader :basic_low_rom, :basic_high_rom, :editor_rom, :c128_kernal_rom, :c128_character_rom
|
|
128
127
|
|
|
129
|
-
# Whether CAPS LOCK is down, holding P6 low.
|
|
130
|
-
attr_reader :caps_lock
|
|
131
|
-
|
|
132
128
|
# The chips +model+, a Model::Profile, names, with the SID
|
|
133
129
|
# +sid_model+. +mode+ is the mode the MMU resets into.
|
|
134
130
|
def initialize(model, sid_model: model.sid_model, mode: :c64)
|
|
@@ -139,7 +135,6 @@ module Badline
|
|
|
139
135
|
@mmu = MMU.new(mode)
|
|
140
136
|
@cartridge = nil
|
|
141
137
|
@debug_page = nil
|
|
142
|
-
@caps_lock = false
|
|
143
138
|
|
|
144
139
|
load_roms
|
|
145
140
|
load_c128_roms
|
|
@@ -150,7 +145,7 @@ module Badline
|
|
|
150
145
|
|
|
151
146
|
@datasette = Datasette.new
|
|
152
147
|
@datasette.on_flag { @cia1.flag! }
|
|
153
|
-
@datasette.on_sense_change { @
|
|
148
|
+
@datasette.on_sense_change { @cpu_port.refresh }
|
|
154
149
|
|
|
155
150
|
@color_lines = ColorLines.new(@vic, @cia2, character_rom, @c128_character_rom)
|
|
156
151
|
@color_ram = @color_lines.color_ram(1)
|
|
@@ -160,10 +155,8 @@ module Badline
|
|
|
160
155
|
@configuration_reads = ConfigurationPage.new(@mmu, @open_bus)
|
|
161
156
|
@configuration_writes = ConfigurationPage.new(@mmu, @open_bus)
|
|
162
157
|
|
|
163
|
-
@
|
|
164
|
-
@
|
|
165
|
-
@port_floating = 0x00
|
|
166
|
-
@io_port = PortStatus.new(%i[basic kernal io tape_out tape_switch tape_motor], value: port_value)
|
|
158
|
+
@cpu_port = CPUPort.new(@datasette, pullups: PORT_PULLUPS, floating: PORT_FLOATING, caps_lock: CAPS_LOCK)
|
|
159
|
+
@io_port = @cpu_port.status
|
|
167
160
|
|
|
168
161
|
@read_pages = Array.new(256)
|
|
169
162
|
@write_pages = Array.new(256)
|
|
@@ -171,6 +164,7 @@ module Badline
|
|
|
171
164
|
@data = 0
|
|
172
165
|
@io_mapped = false
|
|
173
166
|
@mmu.on_change { update_overlays! }
|
|
167
|
+
@cpu_port.on_change { update_overlays! }
|
|
174
168
|
update_overlays!
|
|
175
169
|
end
|
|
176
170
|
|
|
@@ -191,19 +185,19 @@ module Badline
|
|
|
191
185
|
@ram.clear!(RAM_POWER_ON)
|
|
192
186
|
end
|
|
193
187
|
|
|
194
|
-
# The RES line clears the port's direction and
|
|
195
|
-
#
|
|
188
|
+
# The RES line resets the MMU and clears the port's direction and
|
|
189
|
+
# output registers. The port's floating bit keeps its charge.
|
|
196
190
|
def reset!
|
|
197
|
-
@port_ddr = 0x00
|
|
198
|
-
@port_out = 0x00
|
|
199
191
|
@mmu.reset!
|
|
200
|
-
|
|
192
|
+
@cpu_port.reset!
|
|
201
193
|
end
|
|
202
194
|
|
|
195
|
+
# Whether CAPS LOCK is down, holding P6 low.
|
|
196
|
+
def caps_lock = @cpu_port.caps_lock
|
|
197
|
+
|
|
203
198
|
# Holds CAPS LOCK down, or lets it up.
|
|
204
199
|
def caps_lock=(down)
|
|
205
|
-
@caps_lock = down
|
|
206
|
-
@io_port.value = port_value
|
|
200
|
+
@cpu_port.caps_lock = down
|
|
207
201
|
end
|
|
208
202
|
|
|
209
203
|
# Calls the block whenever MCR bit 6 takes the machine into C64 mode or
|
|
@@ -236,7 +230,7 @@ module Badline
|
|
|
236
230
|
@data = if addr > 0x01
|
|
237
231
|
@read_pages[addr >> 8].peek(addr)
|
|
238
232
|
else
|
|
239
|
-
addr.zero? ? @
|
|
233
|
+
addr.zero? ? @cpu_port.ddr : @io_port.value
|
|
240
234
|
end
|
|
241
235
|
end
|
|
242
236
|
|
|
@@ -254,8 +248,7 @@ module Badline
|
|
|
254
248
|
@data = value
|
|
255
249
|
if addr < 0x02
|
|
256
250
|
@write_pages[0].poke(addr, @vic.phi1_data)
|
|
257
|
-
addr.zero? ? @
|
|
258
|
-
update_port!
|
|
251
|
+
addr.zero? ? @cpu_port.write_ddr(value) : @cpu_port.write_data(value)
|
|
259
252
|
else
|
|
260
253
|
@write_pages[addr >> 8].poke(addr, value)
|
|
261
254
|
end
|
|
@@ -294,21 +287,6 @@ module Badline
|
|
|
294
287
|
@vdc = VDC.new(model: model.vdc_model, ram_kb: model.vdc_ram_kb, clock_hz: @region.clock_hz)
|
|
295
288
|
end
|
|
296
289
|
|
|
297
|
-
def update_port!
|
|
298
|
-
driven = @port_ddr & PORT_FLOATING
|
|
299
|
-
@port_floating = (@port_floating & ~driven) | (@port_out & driven)
|
|
300
|
-
@io_port.value = port_value
|
|
301
|
-
@datasette.motor = !@io_port.tape_motor?
|
|
302
|
-
update_overlays!
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
def port_value
|
|
306
|
-
input = PORT_PULLUPS | (@port_floating & PORT_FLOATING)
|
|
307
|
-
input |= CAPS_LOCK unless @caps_lock
|
|
308
|
-
input &= ~TAPE_SENSE if @datasette.sense_low?
|
|
309
|
-
(@port_out & @port_ddr) | (input & ~@port_ddr & 0xff)
|
|
310
|
-
end
|
|
311
|
-
|
|
312
290
|
def update_overlays!
|
|
313
291
|
@ultimax = @cartridge ? @cartridge.ultimax? : false
|
|
314
292
|
@phi1_ultimax = @cartridge ? @cartridge.phi1_ultimax? : false
|
|
@@ -11,7 +11,7 @@ module Badline
|
|
|
11
11
|
# host holds. A cartridge is already in the port, built from its setup.
|
|
12
12
|
def save_state(out)
|
|
13
13
|
out.marker("C128 BUS")
|
|
14
|
-
out.int(@
|
|
14
|
+
out.int(@cpu_port.ddr).int(@cpu_port.data).int(@cpu_port.floating).int(@address).int(@data)
|
|
15
15
|
@mmu.save_state(out)
|
|
16
16
|
@ram.save_state(out)
|
|
17
17
|
@color_lines.color_ram(0).save_state(out)
|
|
@@ -27,9 +27,9 @@ module Badline
|
|
|
27
27
|
|
|
28
28
|
def load_state(input)
|
|
29
29
|
input.marker("C128 BUS")
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
ddr = input.int
|
|
31
|
+
data = input.int
|
|
32
|
+
floating = input.int
|
|
33
33
|
@address = input.int
|
|
34
34
|
@data = input.int
|
|
35
35
|
@mmu.load_state(input)
|
|
@@ -38,7 +38,7 @@ module Badline
|
|
|
38
38
|
@color_lines.color_ram(1).load_state(input)
|
|
39
39
|
@cartridge&.load_state(input)
|
|
40
40
|
@datasette.load_state(input)
|
|
41
|
-
@
|
|
41
|
+
@cpu_port.load(ddr, data, floating)
|
|
42
42
|
update_overlays!
|
|
43
43
|
@vic.load_state(input)
|
|
44
44
|
@control_ports.extra_rows = 0xf8 | @vic.extra_keyboard_lines
|
data/lib/badline/c128.rb
CHANGED
|
@@ -44,6 +44,8 @@ module Badline
|
|
|
44
44
|
class C128
|
|
45
45
|
include IntegerHelper
|
|
46
46
|
include KeyboardBuffer
|
|
47
|
+
include Machine::Clocking
|
|
48
|
+
include Computer::Common
|
|
47
49
|
include Computer::Attachments
|
|
48
50
|
include Computer::KernalTraps
|
|
49
51
|
include Keys
|
|
@@ -71,10 +73,6 @@ module Badline
|
|
|
71
73
|
|
|
72
74
|
attr_reader :cpu, :cycles, :drive1541, :model, :init_threshold
|
|
73
75
|
|
|
74
|
-
# The path of the disk or directory device 8 serves through the traps
|
|
75
|
-
# (Computer::Attachments), or an empty one.
|
|
76
|
-
def mounted_path = @drive.nil? ? "" : @drive.path
|
|
77
|
-
|
|
78
76
|
def family = :c128
|
|
79
77
|
|
|
80
78
|
def address_bus = @bus
|
|
@@ -173,36 +171,6 @@ module Badline
|
|
|
173
171
|
@cycles += 1
|
|
174
172
|
end
|
|
175
173
|
|
|
176
|
-
# Runs `count` cycles, one #cycle! after another.
|
|
177
|
-
def run_cycles(count)
|
|
178
|
-
i = 0
|
|
179
|
-
while i < count
|
|
180
|
-
cycle!
|
|
181
|
-
i += 1
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
# Runs cycles until the block returns true or the cycle count passes
|
|
186
|
-
# `limit`, checking before each cycle.
|
|
187
|
-
def run_until(limit)
|
|
188
|
-
cycle! until yield || @cycles > limit
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def on_init(&block)
|
|
192
|
-
if @cycles < init_threshold
|
|
193
|
-
@init_handlers << block
|
|
194
|
-
else
|
|
195
|
-
block.call
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
# The clock, the raster and the crop of the VIC-IIe's region.
|
|
200
|
-
def timing
|
|
201
|
-
region = @bus.region
|
|
202
|
-
Timing.new(clock_hz: region.clock_hz, cycles_per_line: region.cycles_per_line,
|
|
203
|
-
lines_per_frame: region.lines_per_frame, crop: region.crop, pixel_width: 1)
|
|
204
|
-
end
|
|
205
|
-
|
|
206
174
|
# The chip whose #display a front end shows: the VIC-IIe, or with
|
|
207
175
|
# :vdc the VDC, whose display paints only while its #render is on.
|
|
208
176
|
def video(chip = :vic) = chip == :vdc ? @vdc : @vic
|
|
@@ -217,12 +185,6 @@ module Badline
|
|
|
217
185
|
# The chip a front end records the machine's sound from.
|
|
218
186
|
def sound_source = @sid
|
|
219
187
|
|
|
220
|
-
def load_prg(data)
|
|
221
|
-
uint16(data[0], data[1]).tap do |load_addr|
|
|
222
|
-
ram.write(load_addr, data[2..])
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
|
|
226
188
|
# A cartridge goes in with the power off.
|
|
227
189
|
def attach_cartridge(cartridge)
|
|
228
190
|
connect_cartridge(cartridge)
|
|
@@ -255,19 +217,11 @@ module Badline
|
|
|
255
217
|
reset_z80
|
|
256
218
|
end
|
|
257
219
|
|
|
258
|
-
# CHROUT is at $FFD2 in both KERNALs, and the trap follows the mode.
|
|
259
|
-
def capture_output
|
|
260
|
-
@capture_output ||= ChroutTrap.new(cpu:, bus: @bus, layout: trap_layout).tap do |trap|
|
|
261
|
-
cpu.install_trap(ChroutTrap::ADDRESS) { trap.call }
|
|
262
|
-
end
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
def inspect
|
|
266
|
-
"#<#{self.class.name} cycles=#{@cycles} cpu=(#{@cpu.inspect})>"
|
|
267
|
-
end
|
|
268
|
-
|
|
269
220
|
private
|
|
270
221
|
|
|
222
|
+
# The bus the KERNAL traps read and write through.
|
|
223
|
+
def trap_bus = @bus
|
|
224
|
+
|
|
271
225
|
# The C128 KERNAL leaves $D02F at $FF, every extra keyboard row
|
|
272
226
|
# deselected, before it jumps to the C64's reset.
|
|
273
227
|
def enter_c64_mode
|
|
@@ -275,15 +229,6 @@ module Badline
|
|
|
275
229
|
@bus.control_ports.extra_rows = 0xff
|
|
276
230
|
end
|
|
277
231
|
|
|
278
|
-
# The NMI line is wired-OR between CIA 2, the cartridge and RESTORE,
|
|
279
|
-
# and the CPU takes an interrupt on its falling edge.
|
|
280
|
-
def drive_nmi
|
|
281
|
-
nmi = @cia2.interrupted? || @cartridge_nmi || @restore_pulse
|
|
282
|
-
@restore_pulse = false
|
|
283
|
-
@cpu.nmi = true if nmi && !@nmi_asserted
|
|
284
|
-
@nmi_asserted = nmi
|
|
285
|
-
end
|
|
286
|
-
|
|
287
232
|
# BA halts the CPU on a read cycle.
|
|
288
233
|
def clock_cpu
|
|
289
234
|
@cpu.pending_write? || !@vic.ba_low? ? @cpu.cycle! : @cpu.stall!
|
|
@@ -311,9 +256,5 @@ module Badline
|
|
|
311
256
|
@cpu.cycle!
|
|
312
257
|
@vic.take_cpu_bus(phi1, @bus.data, @bus.vic_access?)
|
|
313
258
|
end
|
|
314
|
-
|
|
315
|
-
def handle_init
|
|
316
|
-
@init_handlers.each(&:call)
|
|
317
|
-
end
|
|
318
259
|
end
|
|
319
260
|
end
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
class Computer
|
|
5
|
-
# What plugs into the machine besides its chips, the cartridge,
|
|
6
|
-
#
|
|
7
|
-
#
|
|
5
|
+
# What plugs into the machine besides its chips, the cartridge, its
|
|
6
|
+
# true drives (TrueDrives) and an REU, and their state for a snapshot.
|
|
7
|
+
# The disk device 8 serves through the traps is KernalTraps'.
|
|
8
8
|
module Attachments
|
|
9
9
|
include TrueDrives
|
|
10
10
|
|
|
@@ -17,30 +17,6 @@ module Badline
|
|
|
17
17
|
address_bus.attach_cartridge(cartridge)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
# Puts the storage in device 8. Mounting again swaps the disk at any
|
|
21
|
-
# point while the machine runs: the drive keeps its RAM, which only a
|
|
22
|
-
# drive reset clears, and its status. The traps go on the KERNAL the
|
|
23
|
-
# machine runs (#trap_layout).
|
|
24
|
-
def mount(storage)
|
|
25
|
-
return @drive.insert(storage) if @drive
|
|
26
|
-
|
|
27
|
-
@drive = KernalTrap::Drive.new(storage)
|
|
28
|
-
install_kernal_traps(trap_layout)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Takes device 8's mounted storage out, and with it the LOAD, SAVE and
|
|
32
|
-
# serial traps, so the KERNAL's routines go out over the serial bus.
|
|
33
|
-
# Mounting again starts a new drive, with its RAM cleared.
|
|
34
|
-
def unmount
|
|
35
|
-
return unless @drive
|
|
36
|
-
|
|
37
|
-
remove_kernal_traps
|
|
38
|
-
@drive = nil
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Whether device 8 serves a disk or directory through the traps.
|
|
42
|
-
def mounted? = !@drive.nil?
|
|
43
|
-
|
|
44
20
|
# The serial bus, which CIA 2's port A drives through the C64's own
|
|
45
21
|
# inverters and reads back on PA6 and PA7, with or without a drive on
|
|
46
22
|
# it.
|
|
@@ -78,26 +54,21 @@ module Badline
|
|
|
78
54
|
end
|
|
79
55
|
end
|
|
80
56
|
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
def
|
|
84
|
-
out.boolean(!@
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
57
|
+
# A true 1541 on the serial bus, then a 1581. The leading flag is the
|
|
58
|
+
# bus itself, which every machine now has.
|
|
59
|
+
def save_serial_bus(out)
|
|
60
|
+
out.boolean(true).boolean(!@drive1541.nil?)
|
|
61
|
+
if @drive1541
|
|
62
|
+
out.int(@drive1541.device)
|
|
63
|
+
@drive1541.save_state(out)
|
|
64
|
+
end
|
|
65
|
+
save_drive1581(out)
|
|
90
66
|
end
|
|
91
67
|
|
|
92
|
-
def
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
unmount
|
|
97
|
-
mount(storage)
|
|
98
|
-
@drive.load_state(input)
|
|
99
|
-
@serial_trap.load_state(input)
|
|
100
|
-
@save_trap.load_state(input)
|
|
68
|
+
def load_serial_bus(input)
|
|
69
|
+
input.boolean?
|
|
70
|
+
load_drive1541(input)
|
|
71
|
+
load_drive1581(input)
|
|
101
72
|
end
|
|
102
73
|
|
|
103
74
|
# The REU, which the setup says the machine has, and whether it has
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Computer
|
|
5
|
+
# What the C128 does as the C64 does: the NMI line, a PRG put into RAM
|
|
6
|
+
# and the region's timing. Computer and C128 each include it.
|
|
7
|
+
module Common
|
|
8
|
+
# The clock, the raster and the crop of the machine's region, a pixel
|
|
9
|
+
# to a window pixel.
|
|
10
|
+
def timing
|
|
11
|
+
region = address_bus.region
|
|
12
|
+
Timing.new(clock_hz: region.clock_hz, cycles_per_line: region.cycles_per_line,
|
|
13
|
+
lines_per_frame: region.lines_per_frame, crop: region.crop, pixel_width: 1)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def load_prg(data)
|
|
17
|
+
uint16(data[0], data[1]).tap do |load_addr|
|
|
18
|
+
ram.write(load_addr, data[2..])
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
# The NMI line is wired-OR between CIA 2, the cartridge and the RESTORE
|
|
25
|
+
# key, and the CPU takes an interrupt on its falling edge.
|
|
26
|
+
def drive_nmi
|
|
27
|
+
nmi = @cia2.interrupted? || @cartridge_nmi || @restore_pulse
|
|
28
|
+
@restore_pulse = false
|
|
29
|
+
@cpu.nmi = true if nmi && !@nmi_asserted
|
|
30
|
+
@nmi_asserted = nmi
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -2,18 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
class Computer
|
|
5
|
-
# The LOAD, SAVE and serial traps
|
|
6
|
-
#
|
|
5
|
+
# The disk device 8 serves through the LOAD, SAVE and serial traps, and
|
|
6
|
+
# CHROUT capture, on the KERNAL the machine runs. Computer, Vic20 and
|
|
7
|
+
# C128 each include it and answer two private methods: #trap_layout,
|
|
8
|
+
# the KernalTrap::Layout of that KERNAL, and #trap_bus, the bus the
|
|
9
|
+
# traps read and write through.
|
|
7
10
|
module KernalTraps
|
|
11
|
+
# Puts the storage in device 8. Mounting again swaps the disk at any
|
|
12
|
+
# point while the machine runs: the drive keeps its RAM, which only a
|
|
13
|
+
# drive reset clears, and its status. The traps go on the KERNAL the
|
|
14
|
+
# machine runs (#trap_layout).
|
|
15
|
+
def mount(storage)
|
|
16
|
+
return @drive.insert(storage) if @drive
|
|
17
|
+
|
|
18
|
+
@drive = KernalTrap::Drive.new(storage)
|
|
19
|
+
install_kernal_traps(trap_layout)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Takes device 8's mounted storage out, and with it the LOAD, SAVE and
|
|
23
|
+
# serial traps, so the KERNAL's routines go out over the serial bus.
|
|
24
|
+
# Mounting again starts a new drive, with its RAM cleared.
|
|
25
|
+
def unmount
|
|
26
|
+
return unless @drive
|
|
27
|
+
|
|
28
|
+
remove_kernal_traps
|
|
29
|
+
@drive = nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Whether device 8 serves a disk or directory through the traps.
|
|
33
|
+
def mounted? = !@drive.nil?
|
|
34
|
+
|
|
35
|
+
# The path of the disk or directory device 8 serves through the
|
|
36
|
+
# traps, or an empty one.
|
|
37
|
+
def mounted_path = @drive.nil? ? "" : @drive.path
|
|
38
|
+
|
|
39
|
+
# Records what the KERNAL prints through CHROUT (ChroutTrap).
|
|
40
|
+
def capture_output
|
|
41
|
+
@capture_output ||= ChroutTrap.new(cpu:, bus: trap_bus, layout: trap_layout).tap do |trap|
|
|
42
|
+
cpu.install_trap(ChroutTrap::ADDRESS) { trap.call }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
8
46
|
private
|
|
9
47
|
|
|
10
48
|
def install_kernal_traps(layout)
|
|
11
49
|
@trap_layout = layout
|
|
12
|
-
load_trap = KernalTrap::Load.new(cpu:, bus:
|
|
50
|
+
load_trap = KernalTrap::Load.new(cpu:, bus: trap_bus, layout:, drive: @drive)
|
|
13
51
|
cpu.install_trap(layout.load) { load_trap.call }
|
|
14
|
-
@serial_trap = KernalTrap::Serial.new(cpu:, bus:
|
|
52
|
+
@serial_trap = KernalTrap::Serial.new(cpu:, bus: trap_bus, layout:, drive: @drive,
|
|
15
53
|
device: serial_trap_device).install
|
|
16
|
-
save_trap = @save_trap = KernalTrap::Save.new(cpu:, bus:
|
|
54
|
+
save_trap = @save_trap = KernalTrap::Save.new(cpu:, bus: trap_bus, layout:, drive: @drive)
|
|
17
55
|
cpu.install_trap(layout.save) { save_trap.call }
|
|
18
56
|
end
|
|
19
57
|
|
|
@@ -24,6 +62,28 @@ module Badline
|
|
|
24
62
|
@serial_trap = nil
|
|
25
63
|
@save_trap = nil
|
|
26
64
|
end
|
|
65
|
+
|
|
66
|
+
# The disk device 8 serves through the traps, with the traps' own
|
|
67
|
+
# state: open channels, the drive's status and RAM, a SAVE under way.
|
|
68
|
+
def save_trap_drive(out)
|
|
69
|
+
out.boolean(!@drive.nil?)
|
|
70
|
+
return unless @drive
|
|
71
|
+
|
|
72
|
+
@drive.save_state(out)
|
|
73
|
+
@serial_trap.save_state(out)
|
|
74
|
+
@save_trap.save_state(out)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def load_trap_drive(input)
|
|
78
|
+
return unmount unless input.boolean?
|
|
79
|
+
|
|
80
|
+
storage = Storage.reopen(input)
|
|
81
|
+
unmount
|
|
82
|
+
mount(storage)
|
|
83
|
+
@drive.load_state(input)
|
|
84
|
+
@serial_trap.load_state(input)
|
|
85
|
+
@save_trap.load_state(input)
|
|
86
|
+
end
|
|
27
87
|
end
|
|
28
88
|
end
|
|
29
89
|
end
|