badline 0.8.0 → 0.10.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 +58 -0
- data/CLAUDE.md +6 -4
- data/README.md +60 -19
- data/doc/library.md +10 -3
- data/doc/media.md +11 -2
- data/doc/pinned-behaviour.md +199 -9
- data/doc/sid-player.md +8 -4
- data/doc/snapshots.md +79 -33
- data/lib/badline/address_bus/pla.rb +130 -0
- data/lib/badline/address_bus/roms.rb +32 -0
- data/lib/badline/address_bus.rb +13 -92
- data/lib/badline/audio/bare_player.rb +24 -0
- data/lib/badline/audio/checkpoints.rb +42 -0
- data/lib/badline/audio/jukebox.rb +11 -4
- data/lib/badline/audio/machine_player.rb +16 -0
- data/lib/badline/audio/renderer.rb +80 -9
- data/lib/badline/audio/stereo.rb +3 -5
- data/lib/badline/audio.rb +1 -0
- data/lib/badline/chrout_trap.rb +4 -3
- data/lib/badline/computer/attachments.rb +52 -12
- data/lib/badline/computer/saved_state.rb +1 -0
- data/lib/badline/computer.rb +50 -33
- data/lib/badline/control_ports.rb +10 -3
- data/lib/badline/core.rb +51 -0
- data/lib/badline/data_path.rb +37 -0
- data/lib/badline/datasette.rb +63 -10
- data/lib/badline/drive1541/disk.rb +3 -3
- data/lib/badline/drive1541/disk_state.rb +14 -5
- data/lib/badline/drive1541/serial_port.rb +5 -8
- data/lib/badline/drive1541/sleep.rb +5 -6
- data/lib/badline/drive1541.rb +3 -3
- data/lib/badline/frontend/app.rb +63 -64
- data/lib/badline/frontend/boot.rb +34 -4
- data/lib/badline/frontend/confirmation.rb +17 -11
- data/lib/badline/frontend/controls.rb +12 -6
- data/lib/badline/frontend/drive_led.rb +9 -10
- data/lib/badline/frontend/frame_rate.rb +4 -4
- data/lib/badline/frontend/machine_window.rb +87 -0
- data/lib/badline/frontend/menu_dialogs.rb +52 -14
- data/lib/badline/frontend/menu_media.rb +70 -16
- data/lib/badline/frontend/menu_pages.rb +47 -42
- data/lib/badline/frontend/menu_ports.rb +69 -0
- data/lib/badline/frontend/name_field.rb +88 -0
- data/lib/badline/frontend/pacer.rb +6 -5
- data/lib/badline/frontend/pause_menu.rb +52 -37
- data/lib/badline/frontend/player_screen.rb +1 -1
- data/lib/badline/frontend/player_window.rb +4 -4
- data/lib/badline/frontend/screen.rb +39 -37
- data/lib/badline/frontend/sid_view.rb +1 -1
- data/lib/badline/frontend/snapshots.rb +145 -17
- data/lib/badline/frontend/sound.rb +17 -9
- data/lib/badline/frontend/timeline.rb +36 -5
- data/lib/badline/frontend.rb +3 -0
- data/lib/badline/iec_bus.rb +16 -28
- data/lib/badline/kernal_trap/drive.rb +3 -0
- data/lib/badline/kernal_trap/file.rb +1 -1
- data/lib/badline/kernal_trap/layout.rb +112 -0
- data/lib/badline/kernal_trap/load.rb +24 -25
- data/lib/badline/kernal_trap/routine.rb +17 -5
- data/lib/badline/kernal_trap/save.rb +13 -23
- data/lib/badline/kernal_trap/serial.rb +38 -24
- data/lib/badline/kernal_trap.rb +1 -0
- data/lib/badline/keyboard.rb +17 -14
- data/lib/badline/machine.rb +60 -0
- data/lib/badline/media/disk_list.rb +75 -0
- data/lib/badline/media/disk_set.rb +13 -3
- data/lib/badline/media/vic20_basic.rb +54 -0
- data/lib/badline/media/vic20_cartridge.rb +74 -0
- data/lib/badline/media/vic20_media.rb +132 -0
- data/lib/badline/media.rb +39 -20
- data/lib/badline/model.rb +62 -0
- data/lib/badline/options/event.rb +10 -6
- data/lib/badline/options/help.rb +25 -13
- data/lib/badline/options/table.rb +15 -1
- data/lib/badline/options/validation.rb +63 -3
- data/lib/badline/options.rb +37 -34
- data/lib/badline/region.rb +16 -4
- data/lib/badline/reu/ram.rb +27 -0
- data/lib/badline/reu/saved_state.rb +27 -0
- data/lib/badline/roms/README +37 -0
- data/lib/badline/roms/kernal-sx64.rom +0 -0
- data/lib/badline/roms/vic20/basic.rom +0 -0
- data/lib/badline/roms/vic20/character.rom +0 -0
- data/lib/badline/roms/vic20/kernal-ntsc.rom +0 -0
- data/lib/badline/roms/vic20/kernal-pal.rom +0 -0
- data/lib/badline/sid/decimator.rb +13 -0
- data/lib/badline/sid/filter.rb +6 -3
- data/lib/badline/sid.rb +19 -0
- data/lib/badline/snapshot/container.rb +2 -0
- data/lib/badline/snapshot/fields.rb +1 -0
- data/lib/badline/snapshot/image.rb +15 -0
- data/lib/badline/snapshot/setup.rb +20 -8
- data/lib/badline/snapshot/state.rb +15 -7
- data/lib/badline/snapshot/vice/main_cpu.rb +1 -1
- data/lib/badline/snapshot/vice/peripherals.rb +12 -2
- data/lib/badline/snapshot/vice/restore.rb +3 -1
- data/lib/badline/snapshot/vice/reu1764.rb +55 -0
- data/lib/badline/snapshot/vice/vicii.rb +59 -13
- data/lib/badline/snapshot/vice/vicii_import.rb +14 -3
- data/lib/badline/snapshot/vice.rb +25 -23
- data/lib/badline/snapshot.rb +10 -0
- data/lib/badline/storage/crt_file.rb +16 -5
- data/lib/badline/storage/disk_image.rb +3 -0
- data/lib/badline/storage/host_directory.rb +3 -0
- data/lib/badline/storage/t64.rb +10 -4
- data/lib/badline/storage/tap.rb +44 -8
- data/lib/badline/storage/unavailable.rb +3 -1
- data/lib/badline/storage.rb +5 -5
- data/lib/badline/timing.rb +12 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/via/shift_register.rb +43 -16
- data/lib/badline/vic/bank.rb +53 -16
- data/lib/badline/vic/registers.rb +20 -4
- data/lib/badline/vic/sprite/timing.rb +5 -9
- data/lib/badline/vic/sprite.rb +4 -3
- data/lib/badline/vic.rb +34 -15
- data/lib/badline/vic20/attachments.rb +103 -0
- data/lib/badline/vic20/bus.rb +308 -0
- data/lib/badline/vic20/cpu.rb +10 -0
- data/lib/badline/vic20/keyboard_via_ports.rb +53 -0
- data/lib/badline/vic20/port_wiring.rb +50 -0
- data/lib/badline/vic20/saved_state.rb +152 -0
- data/lib/badline/vic20/sound/output.rb +83 -0
- data/lib/badline/vic20/sound.rb +233 -0
- data/lib/badline/vic20/user_via_ports.rb +47 -0
- data/lib/badline/vic20/vic/painter.rb +239 -0
- data/lib/badline/vic20/vic/saved_state.rb +55 -0
- data/lib/badline/vic20/vic/text_window.rb +71 -0
- data/lib/badline/vic20/vic.rb +267 -0
- data/lib/badline/vic20.rb +220 -0
- data/lib/badline.rb +2 -38
- metadata +37 -2
- data/lib/badline/address_bus/ultimax_pages.rb +0 -38
data/lib/badline/roms/README
CHANGED
|
@@ -17,6 +17,16 @@ character.rom
|
|
|
17
17
|
-------------
|
|
18
18
|
The character generator ROM.
|
|
19
19
|
|
|
20
|
+
kernal-sx64.rom
|
|
21
|
+
---------------
|
|
22
|
+
The SX-64's KERNAL, 251104-04. It boots with the SX-64's banner and
|
|
23
|
+
colours, and loads from device 8 rather than tape. Computer.new(kernal:
|
|
24
|
+
:sx64) fits it, as --model sx64 does.
|
|
25
|
+
|
|
26
|
+
Downloaded from
|
|
27
|
+
https://www.zimmers.net/anonftp/pub/cbm/firmware/computers/c64/kernal.sx.251104-04.bin
|
|
28
|
+
SHA-1 aa136e91ecf3c5ac64f696b3dbcbfc5ba0871c98.
|
|
29
|
+
|
|
20
30
|
dos1541.rom
|
|
21
31
|
-----------
|
|
22
32
|
The 1541 disk drive's DOS 2.6, 16 KB for $C000-$FFFF: 325302-01
|
|
@@ -26,3 +36,30 @@ Both halves downloaded from
|
|
|
26
36
|
https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/new/1541/
|
|
27
37
|
(1541-c000.325302-01.bin and 1541-e000.901229-05.bin) and concatenated.
|
|
28
38
|
SHA-1 ab16f56989b27d89babe5f89c5a8cb3da71a82f0.
|
|
39
|
+
|
|
40
|
+
Commodore VIC-20 ROMs
|
|
41
|
+
=====================
|
|
42
|
+
|
|
43
|
+
Downloaded from
|
|
44
|
+
https://www.zimmers.net/anonftp/pub/cbm/firmware/computers/vic20/
|
|
45
|
+
Vic20::Bus maps them.
|
|
46
|
+
|
|
47
|
+
vic20/basic.rom
|
|
48
|
+
---------------
|
|
49
|
+
VIC-20 BASIC V2, 901486-01, for $C000-$DFFF.
|
|
50
|
+
SHA-1 587d1e90950675ab6b12d91248a3f0d640d02e8d.
|
|
51
|
+
|
|
52
|
+
vic20/kernal-pal.rom
|
|
53
|
+
--------------------
|
|
54
|
+
The PAL KERNAL, 901486-07, for $E000-$FFFF.
|
|
55
|
+
SHA-1 ce0137ed69f003a299f43538fa9eee27898e621e.
|
|
56
|
+
|
|
57
|
+
vic20/kernal-ntsc.rom
|
|
58
|
+
---------------------
|
|
59
|
+
The NTSC KERNAL, 901486-06, for $E000-$FFFF.
|
|
60
|
+
SHA-1 06de7ec017a5e78bd6746d89c2ecebb646efeb19.
|
|
61
|
+
|
|
62
|
+
vic20/character.rom
|
|
63
|
+
-------------------
|
|
64
|
+
The character generator ROM, 901460-03, for $8000-$8FFF.
|
|
65
|
+
SHA-1 4fd85ab6647ee2ac7ba40f729323f2472d35b9b4.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -22,6 +22,19 @@ module Badline
|
|
|
22
22
|
# Cycles until the current window closes, counting the next one as 1.
|
|
23
23
|
attr_reader :cycles_to_close
|
|
24
24
|
|
|
25
|
+
# How far the current window has got. The clock and the rate are the
|
|
26
|
+
# host's.
|
|
27
|
+
def save_state(out)
|
|
28
|
+
out.int(@phase).int(@sum).int(@count).int(@cycles_to_close)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def load_state(input)
|
|
32
|
+
@phase = input.int
|
|
33
|
+
@sum = input.int
|
|
34
|
+
@count = input.int
|
|
35
|
+
@cycles_to_close = input.int
|
|
36
|
+
end
|
|
37
|
+
|
|
25
38
|
# Returns the averaged sample when these cycles close a window, and
|
|
26
39
|
# nil otherwise. They must not run past the window's end.
|
|
27
40
|
def push(sample, cycles = 1)
|
data/lib/badline/sid/filter.rb
CHANGED
|
@@ -101,10 +101,13 @@ module Badline
|
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
# Takes the input in 1/2^Filter::FRACTION units, and reads the
|
|
104
|
-
# output out in whole ones.
|
|
104
|
+
# output out in whole ones. A step of several cycles moves each pole
|
|
105
|
+
# at most onto its input.
|
|
105
106
|
def cycle!(input, cycles = 1)
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
w0_lowpass = [(W0_LOWPASS >> 8) * cycles, 1 << 12].min
|
|
108
|
+
w0_highpass = [W0_HIGHPASS * cycles, 1 << 20].min
|
|
109
|
+
delta_lowpass = (w0_lowpass * ((input << INPUT_SHIFT) - @lowpass)) >> 12
|
|
110
|
+
delta_highpass = (w0_highpass * (@lowpass - @highpass)) >> 20
|
|
108
111
|
@output = (@lowpass - @highpass) >> FRACTION
|
|
109
112
|
@lowpass += delta_lowpass
|
|
110
113
|
@highpass += delta_highpass
|
data/lib/badline/sid.rb
CHANGED
|
@@ -127,6 +127,25 @@ module Badline
|
|
|
127
127
|
@samples = []
|
|
128
128
|
end
|
|
129
129
|
|
|
130
|
+
# How far the recording has got into the sample it is averaging, for
|
|
131
|
+
# a host that carries on recording from a saved state: it calls
|
|
132
|
+
# #record, then #load_recording.
|
|
133
|
+
def save_recording(out)
|
|
134
|
+
decimator = @decimator
|
|
135
|
+
raise ArgumentError, "the SID isn't recording" if decimator.nil?
|
|
136
|
+
|
|
137
|
+
out.marker("RECORDING")
|
|
138
|
+
decimator.save_state(out)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def load_recording(input)
|
|
142
|
+
decimator = @decimator
|
|
143
|
+
raise ArgumentError, "the SID isn't recording" if decimator.nil?
|
|
144
|
+
|
|
145
|
+
input.marker("RECORDING")
|
|
146
|
+
decimator.load_state(input)
|
|
147
|
+
end
|
|
148
|
+
|
|
130
149
|
# Also keeps each voice's own output, before the filter and the volume,
|
|
131
150
|
# as each recorded sample closes, for #drain_voice_samples.
|
|
132
151
|
def record_voices!
|
|
@@ -26,6 +26,8 @@ module Badline
|
|
|
26
26
|
FORMAT_MINOR = 0
|
|
27
27
|
# x64sc names its machine C64SC, and refuses a snapshot naming another.
|
|
28
28
|
MACHINE = "C64SC"
|
|
29
|
+
# xvic names its machine VIC20.
|
|
30
|
+
VIC20 = "VIC20"
|
|
29
31
|
GZIP_MAGIC = "\x1f\x8b".b
|
|
30
32
|
|
|
31
33
|
# The VICE version block, when VICE wrote the file: four version
|
|
@@ -28,11 +28,16 @@ module Badline
|
|
|
28
28
|
# modules badline reads. Yields a line for each thing left out, or
|
|
29
29
|
# warns it without a block.
|
|
30
30
|
def load(&)
|
|
31
|
+
return load_vic20(&) if vic20?
|
|
32
|
+
|
|
31
33
|
computer = badline? ? opening { Computer.restored(state) } : Vice.setup(@container).build
|
|
32
34
|
tell(badline? ? state_report(computer) : Vice::Restore.apply(@container, computer), &)
|
|
33
35
|
computer
|
|
34
36
|
end
|
|
35
37
|
|
|
38
|
+
# Whether the machine is a VIC-20, as the container names it.
|
|
39
|
+
def vic20? = @container.machine == Container::VIC20
|
|
40
|
+
|
|
36
41
|
# Restores `computer` from the snapshot, and returns the Report. A
|
|
37
42
|
# snapshot that fails leaves the machine as it was.
|
|
38
43
|
def restore(computer, &)
|
|
@@ -45,6 +50,16 @@ module Badline
|
|
|
45
50
|
|
|
46
51
|
def section = @container[MachineState::NAME]
|
|
47
52
|
|
|
53
|
+
# A VIC-20 restores from its BADLINE module alone. xvic's own modules
|
|
54
|
+
# aren't read.
|
|
55
|
+
def load_vic20(&)
|
|
56
|
+
raise FormatError, "badline reads only its own VIC-20 snapshots, not xvic's" unless badline?
|
|
57
|
+
|
|
58
|
+
machine = Vic20.restored(state)
|
|
59
|
+
tell(state_report(machine), &)
|
|
60
|
+
machine
|
|
61
|
+
end
|
|
62
|
+
|
|
48
63
|
def tell(report)
|
|
49
64
|
report.ignored.each { |line| block_given? ? yield(line) : warn("badline: #{line}") }
|
|
50
65
|
end
|
|
@@ -3,27 +3,32 @@
|
|
|
3
3
|
module Badline
|
|
4
4
|
module Snapshot
|
|
5
5
|
# How a machine was built: its VIC, CIA and SID models, its region, its
|
|
6
|
-
# RAM expansion
|
|
7
|
-
# takes. A State starts with them, so a machine to restore it into can
|
|
6
|
+
# RAM expansion, the size of its REU, its KERNAL and whether it has a
|
|
7
|
+
# datasette, the arguments Computer.new takes. A State starts with them, so a machine to restore it into can
|
|
8
8
|
# be built first.
|
|
9
|
-
Setup = Data.define(:vic_model, :cia_model, :sid_model, :region, :ram_expansion, :reu)
|
|
9
|
+
Setup = Data.define(:vic_model, :cia_model, :sid_model, :region, :ram_expansion, :reu, :kernal, :datasette)
|
|
10
10
|
|
|
11
11
|
class Setup
|
|
12
12
|
SID_MODELS = %i[mos6581 mos8580].freeze
|
|
13
|
-
REGIONS = [Region::PAL, Region::NTSC].freeze
|
|
13
|
+
REGIONS = [Region::PAL, Region::NTSC, Region::NTSC_OLD, Region::DREAN].freeze
|
|
14
14
|
RAM_EXPANSIONS = [:none, *RAMExpansion::TYPES.keys].freeze
|
|
15
|
+
KERNALS = AddressBus::ROMs::KERNALS.keys.freeze
|
|
15
16
|
|
|
16
17
|
def self.of(address_bus)
|
|
17
18
|
new(vic_model: address_bus.vic.model, cia_model: address_bus.cia1.model, sid_model: address_bus.sid.model,
|
|
18
|
-
region: address_bus.region, ram_expansion: address_bus.ram_expansion.type, reu: address_bus.reu&.size_kb
|
|
19
|
+
region: address_bus.region, ram_expansion: address_bus.ram_expansion.type, reu: address_bus.reu&.size_kb,
|
|
20
|
+
kernal: address_bus.kernal, datasette: address_bus.datasette.connected?)
|
|
19
21
|
end
|
|
20
22
|
|
|
23
|
+
# Layout 3 has no KERNAL or datasette, and stands for the C64's.
|
|
21
24
|
def self.read(input)
|
|
22
25
|
new(vic_model: VIC::MODELS.fetch(input.int), cia_model: CIA::MODELS.fetch(input.int),
|
|
23
26
|
sid_model: SID_MODELS.fetch(input.int), region: REGIONS.fetch(input.int),
|
|
24
|
-
ram_expansion: ram_expansion(RAM_EXPANSIONS.fetch(input.int)), reu: reu(input.int)
|
|
27
|
+
ram_expansion: ram_expansion(RAM_EXPANSIONS.fetch(input.int)), reu: reu(input.int),
|
|
28
|
+
kernal: input.schema > 3 ? KERNALS.fetch(input.int) : :c64,
|
|
29
|
+
datasette: input.schema > 3 ? input.boolean? : true)
|
|
25
30
|
rescue IndexError
|
|
26
|
-
raise FormatError, "the state names a chip model, region
|
|
31
|
+
raise FormatError, "the state names a chip model, region, RAM expansion or KERNAL badline doesn't know"
|
|
27
32
|
end
|
|
28
33
|
|
|
29
34
|
def self.ram_expansion(name) = name == :none ? nil : name
|
|
@@ -40,17 +45,24 @@ module Badline
|
|
|
40
45
|
out.int(VIC::MODELS.index(vic_model)).int(CIA::MODELS.index(cia_model))
|
|
41
46
|
out.int(SID_MODELS.index(sid_model)).int(REGIONS.index(region))
|
|
42
47
|
out.int(RAM_EXPANSIONS.index(ram_expansion || :none)).int(reu || 0)
|
|
48
|
+
out.int(KERNALS.index(kernal)).boolean(datasette)
|
|
43
49
|
end
|
|
44
50
|
|
|
45
51
|
# A machine built this way, at power-on.
|
|
46
52
|
def build
|
|
47
|
-
Computer.new(vic_model:, cia_model:, sid_model:, region:, ram_expansion:, reu:)
|
|
53
|
+
Computer.new(vic_model:, cia_model:, sid_model:, region:, ram_expansion:, reu:, kernal:, datasette:)
|
|
48
54
|
end
|
|
49
55
|
|
|
56
|
+
# The chips, led by the model's name when they make one of
|
|
57
|
+
# Badline::Model's.
|
|
50
58
|
def to_s
|
|
59
|
+
model = Model.of(self)
|
|
51
60
|
parts = [vic_model, cia_model, sid_model, region.name]
|
|
61
|
+
parts.unshift(model.name) if model
|
|
52
62
|
parts << ram_expansion if ram_expansion
|
|
53
63
|
parts << "a #{reu}K REU" if reu
|
|
64
|
+
parts << "the #{kernal} KERNAL" unless kernal == :c64
|
|
65
|
+
parts << "no datasette" unless datasette
|
|
54
66
|
parts.join(", ")
|
|
55
67
|
end
|
|
56
68
|
end
|
|
@@ -13,8 +13,11 @@ module Badline
|
|
|
13
13
|
class State
|
|
14
14
|
# The layout the chips write their fields in. It goes up whenever a
|
|
15
15
|
# save_state writes something else, so a State in another layout
|
|
16
|
-
# fails before anything is read into a machine.
|
|
17
|
-
|
|
16
|
+
# fails before anything is read into a machine. Layout 3, without the
|
|
17
|
+
# KERNAL and datasette in the setup, and layout 4, without the VIA
|
|
18
|
+
# shift register's clock delay, still read.
|
|
19
|
+
SCHEMA = 5
|
|
20
|
+
READABLE = [3, 4, SCHEMA].freeze
|
|
18
21
|
|
|
19
22
|
attr_reader :values, :strings
|
|
20
23
|
|
|
@@ -101,15 +104,19 @@ module Badline
|
|
|
101
104
|
# A detached reader restores a machine that opens no host file and
|
|
102
105
|
# writes none, for a copy the machine runs on its own: a disk image
|
|
103
106
|
# comes from the bytes the state holds, write-protected, a directory
|
|
104
|
-
#
|
|
105
|
-
#
|
|
107
|
+
# device 8 serves is left out, and a true drive's disk keeps the
|
|
108
|
+
# state's tracks without its image file.
|
|
106
109
|
class StateReader
|
|
110
|
+
# The layout check_stamp found.
|
|
111
|
+
attr_reader :schema
|
|
112
|
+
|
|
107
113
|
def initialize(state, detached: false)
|
|
108
114
|
@values = state.values
|
|
109
115
|
@strings = state.strings
|
|
110
116
|
@value = 0
|
|
111
117
|
@string = 0
|
|
112
118
|
@detached = detached
|
|
119
|
+
@schema = State::SCHEMA
|
|
113
120
|
end
|
|
114
121
|
|
|
115
122
|
def detached? = @detached
|
|
@@ -160,12 +167,13 @@ module Badline
|
|
|
160
167
|
raise FormatError, "expected #{name} in the state, found #{found[0, 20].inspect}" unless found == name
|
|
161
168
|
end
|
|
162
169
|
|
|
163
|
-
# Fails unless the State was written in
|
|
164
|
-
# version.
|
|
170
|
+
# Fails unless the State was written in a layout this badline reads
|
|
171
|
+
# (State::READABLE) by this badline version.
|
|
165
172
|
def check_stamp
|
|
166
173
|
schema = int
|
|
167
174
|
version = string
|
|
168
|
-
|
|
175
|
+
@schema = schema
|
|
176
|
+
return if State::READABLE.include?(schema) && version == VERSION
|
|
169
177
|
|
|
170
178
|
raise FormatError, "the state was written by badline #{version.inspect} in layout #{schema}, " \
|
|
171
179
|
"and this is badline #{VERSION}, layout #{State::SCHEMA}"
|
|
@@ -45,7 +45,7 @@ module Badline
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def interrupts(computer, fields)
|
|
48
|
-
irqs = [computer.vic.interrupted?, computer.cia1.interrupted?].count(true)
|
|
48
|
+
irqs = [computer.vic.interrupted?, computer.cia1.interrupted?, computer.reu&.irq? || false].count(true)
|
|
49
49
|
nmis = computer.cia2.interrupted? ? 1 : 0
|
|
50
50
|
pending = (irqs.positive? ? IK_IRQ : 0) | (computer.cpu.nmi ? IK_NMI : 0)
|
|
51
51
|
fields.dword(irqs).dword(nmis).dword(pending)
|
|
@@ -9,7 +9,8 @@ module Badline
|
|
|
9
9
|
# control ports and the user port. Each is written as a machine with
|
|
10
10
|
# nothing attached leaves it, with the joysticks released and the
|
|
11
11
|
# glue logic's VIC bank taken from CIA 2. A cartridge or tape badline
|
|
12
|
-
# has attached is carried by the BADLINE module only
|
|
12
|
+
# has attached is carried by the BADLINE module only, and an REU by
|
|
13
|
+
# REU1764 as well.
|
|
13
14
|
module Peripherals
|
|
14
15
|
# The glue logic's VIC bank, and its pending bank change.
|
|
15
16
|
GLUE = "GLUE"
|
|
@@ -29,7 +30,16 @@ module Badline
|
|
|
29
30
|
|
|
30
31
|
module_function
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
# C64CART 0.1 lists the REU as the one cartridge in the port when
|
|
34
|
+
# there is one, which REU1764 follows: no main slot cartridge, the
|
|
35
|
+
# lines inactive, no freeze or NMI pending, then the REU's id.
|
|
36
|
+
def cartridge_port(computer)
|
|
37
|
+
return section(*FIXED[0]) unless computer.reu
|
|
38
|
+
|
|
39
|
+
fields = FieldWriter.new.byte(1).dword(-1).byte(0).byte(0).dword(0).dword(0).byte(0).byte(0).byte(0)
|
|
40
|
+
fields.qword(-1).qword(-1).zeros(12 + 16).dword(REU1764::CARTRIDGE_ID)
|
|
41
|
+
fields.section("C64CART", 0, 1)
|
|
42
|
+
end
|
|
33
43
|
|
|
34
44
|
def drives = section(*FIXED[1])
|
|
35
45
|
|
|
@@ -11,7 +11,7 @@ module Badline
|
|
|
11
11
|
# MAINCPU and C64MEM hold the machine itself, so a version badline
|
|
12
12
|
# doesn't read fails the restore; the others are left out and
|
|
13
13
|
# reported.
|
|
14
|
-
READ = [MainCPU::NAME, C64Mem::NAME, "CIA1", "CIA2", SIDRegisters::NAME, SIDExtended::NAME,
|
|
14
|
+
READ = [MainCPU::NAME, C64Mem::NAME, REU1764::NAME, "CIA1", "CIA2", SIDRegisters::NAME, SIDExtended::NAME,
|
|
15
15
|
VICII::NAME].freeze
|
|
16
16
|
REQUIRED = [MainCPU::NAME, C64Mem::NAME].freeze
|
|
17
17
|
|
|
@@ -83,6 +83,7 @@ module Badline
|
|
|
83
83
|
case name
|
|
84
84
|
when MainCPU::NAME then MainCPU.reads?(section)
|
|
85
85
|
when C64Mem::NAME then C64Mem.reads?(section)
|
|
86
|
+
when REU1764::NAME then REU1764.reads?(section)
|
|
86
87
|
when SIDRegisters::NAME then SIDRegisters.reads?(section)
|
|
87
88
|
when SIDExtended::NAME then SIDExtended.reads?(section)
|
|
88
89
|
when VICII::NAME then VICII.reads?(section)
|
|
@@ -94,6 +95,7 @@ module Badline
|
|
|
94
95
|
case name
|
|
95
96
|
when MainCPU::NAME then MainCPU.import(section, computer)
|
|
96
97
|
when C64Mem::NAME then C64Mem.import(section, computer)
|
|
98
|
+
when REU1764::NAME then REU1764.import(section, computer)
|
|
97
99
|
when SIDRegisters::NAME then SIDRegisters.import(section, computer)
|
|
98
100
|
when SIDExtended::NAME then SIDExtended.import(section, computer)
|
|
99
101
|
when VICII::NAME then VICII.import(section, computer)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
# REU1764 0.0, VICE's module for an REU of any size, which x64sc
|
|
7
|
+
# writes straight after C64CART when the cartridge port lists the
|
|
8
|
+
# REU: the size in K, the sixteen bytes the REC's registers read
|
|
9
|
+
# from $DF00, and the RAM.
|
|
10
|
+
#
|
|
11
|
+
# Written: the registers and the RAM. VICE saves between transfers,
|
|
12
|
+
# so badline's copy of the machine runs a transfer in flight to its
|
|
13
|
+
# end first (Vice.settled).
|
|
14
|
+
#
|
|
15
|
+
# Read: the registers, as the values the counters run from and
|
|
16
|
+
# start from, and the RAM. The status register's interrupt, end of
|
|
17
|
+
# block and fault bits come back with the IRQ line, and a command
|
|
18
|
+
# waiting for a write to $FF00 stays armed.
|
|
19
|
+
module REU1764
|
|
20
|
+
NAME = "REU1764"
|
|
21
|
+
MAJOR = 0
|
|
22
|
+
MINOR = 0
|
|
23
|
+
# C64CART's id for the REU, VICE's CARTRIDGE_REU.
|
|
24
|
+
CARTRIDGE_ID = -105
|
|
25
|
+
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
def export(computer)
|
|
29
|
+
reu = computer.reu
|
|
30
|
+
FieldWriter.new.dword(reu.size_kb).bytes(reu.register_file).bytes(reu.ram_contents)
|
|
31
|
+
.section(NAME, MAJOR, MINOR)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def reads?(section) = section.major == MAJOR && section.minor == MINOR
|
|
35
|
+
|
|
36
|
+
# The REU's size in K, which builds the machine.
|
|
37
|
+
def size_kb(section)
|
|
38
|
+
size = FieldReader.new(section).dword
|
|
39
|
+
return size if REU::SIZES_KB.include?(size)
|
|
40
|
+
|
|
41
|
+
raise FormatError, "#{section}: a #{size}K REU, which badline doesn't build"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def import(section, computer)
|
|
45
|
+
reu = computer.reu
|
|
46
|
+
fields = FieldReader.new(section)
|
|
47
|
+
fields.skip(4)
|
|
48
|
+
registers = fields.bytes(16)
|
|
49
|
+
reu.restore_ram(fields.string(reu.size_kb * 1024))
|
|
50
|
+
reu.restore_registers(registers)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -25,9 +25,13 @@ module Badline
|
|
|
25
25
|
MAJOR = 1
|
|
26
26
|
MINOR = 3
|
|
27
27
|
VERSIONS = ["VIC-II 1.3", "VIC-IISC 1.4"].freeze
|
|
28
|
-
|
|
28
|
+
# The VIC model and region each of VICE's model numbers builds: the
|
|
29
|
+
# 6569, the 8565 and the 6569R1 on PAL, the 6567 and the 8562 on
|
|
30
|
+
# NTSC, the 6567R56A on old NTSC and the 6572 on PAL-N.
|
|
31
|
+
VIC_MODELS = %i[mos6569 mos8565 mos6569 mos6569 mos8565 mos6569 mos6569].freeze
|
|
32
|
+
REGIONS = [Region::PAL, Region::PAL, Region::PAL, Region::NTSC, Region::NTSC, Region::NTSC_OLD,
|
|
33
|
+
Region::DREAN].freeze
|
|
29
34
|
FRAME_WIDTH = 384
|
|
30
|
-
FRAME_HEIGHT = 312
|
|
31
35
|
# The pixel pipeline x64sc keeps between cycles, in bytes.
|
|
32
36
|
PIPELINE = 16 + 32 + 6 + 32 + 8 + 3 + 24 + 0x2f + 2 + 4
|
|
33
37
|
|
|
@@ -35,21 +39,61 @@ module Badline
|
|
|
35
39
|
|
|
36
40
|
def export(computer)
|
|
37
41
|
vic = computer.vic
|
|
38
|
-
fields = FieldWriter.new.byte(
|
|
42
|
+
fields = FieldWriter.new.byte(number(vic)).bytes(vic.register_file)
|
|
39
43
|
beam(vic, fields)
|
|
40
44
|
counters(vic, fields)
|
|
41
45
|
borders(vic, fields)
|
|
42
46
|
fields.bytes(Array.new(0x400) { |offset| computer.address_bus.color_ram.nibble(0xd800 + offset) })
|
|
43
47
|
8.times { |n| sprite(vic, n, fields) }
|
|
44
48
|
pipeline(vic, fields)
|
|
45
|
-
|
|
46
|
-
fields.
|
|
49
|
+
height = frame_height(vic.region)
|
|
50
|
+
fields.dword(drawn_line(vic)).dword(FRAME_WIDTH).dword(height).dword(FRAME_WIDTH)
|
|
51
|
+
fields.zeros(FRAME_WIDTH * (height + 4))
|
|
47
52
|
fields.section(NAME, MAJOR, MINOR)
|
|
48
53
|
end
|
|
49
54
|
|
|
55
|
+
# VICE has no HMOS chip for old NTSC or PAL-N, so an 8565 there is
|
|
56
|
+
# written as the NMOS one.
|
|
57
|
+
def number(vic)
|
|
58
|
+
hmos = vic.model == :mos8565
|
|
59
|
+
case vic.region.name
|
|
60
|
+
when :ntsc then hmos ? 4 : 3
|
|
61
|
+
when :ntscold then 5
|
|
62
|
+
when :drean then 6
|
|
63
|
+
else hmos ? 1 : 0
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# x64sc's frame buffer, in lines, which it reads back at the height
|
|
68
|
+
# the module gives: 312 on PAL and PAL-N, 275 on either NTSC.
|
|
69
|
+
def frame_height(region) = region.lines_per_frame == 312 ? 312 : 275
|
|
70
|
+
|
|
71
|
+
# VICE's cycle in the line is badline's column plus one, so
|
|
72
|
+
# badline's last column is VICE's cycle 0 of the next line. VICE
|
|
73
|
+
# moves its line on in cycle 0, but line 0 a cycle later, holding
|
|
74
|
+
# the last line's number in cycle 0 with the start of frame flag
|
|
75
|
+
# set.
|
|
76
|
+
def last_column?(vic) = vic.column + 1 == vic.region.cycles_per_line
|
|
77
|
+
|
|
78
|
+
def vice_cycle(vic) = last_column?(vic) ? 0 : vic.column + 1
|
|
79
|
+
|
|
80
|
+
def frame_start?(vic) = last_column?(vic) && vic.rasterline + 1 == vic.region.lines_per_frame
|
|
81
|
+
|
|
82
|
+
def vice_line(vic)
|
|
83
|
+
line = vic.rasterline
|
|
84
|
+
last_column?(vic) && !frame_start?(vic) ? line + 1 : line
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The line VICE's frame buffer is drawing, its line but for the
|
|
88
|
+
# last, where it starts the next frame.
|
|
89
|
+
def drawn_line(vic)
|
|
90
|
+
line = vice_line(vic)
|
|
91
|
+
line >= vic.region.lines_per_frame - 1 ? 0 : line
|
|
92
|
+
end
|
|
93
|
+
|
|
50
94
|
def beam(vic, fields)
|
|
51
95
|
registers = vic.registers
|
|
52
|
-
fields.dword(vic
|
|
96
|
+
fields.dword(vice_cycle(vic)).dword(0).dword(vice_line(vic)).flag(frame_start?(vic))
|
|
53
97
|
fields.byte((registers[0x19] & 0x0f) | (vic.interrupted? ? 0x80 : 0))
|
|
54
98
|
fields.dword(registers.raster_target).flag(vic.latch_bits.anybits?(1))
|
|
55
99
|
[vic.character_buffer, vic.color_buffer].each { |buffer| fields.bytes(buffer.map { |value| value || 0 }) }
|
|
@@ -98,17 +142,19 @@ module Badline
|
|
|
98
142
|
|
|
99
143
|
def reads?(section) = VERSIONS.include?(section.to_s)
|
|
100
144
|
|
|
101
|
-
# VICE's model
|
|
102
|
-
#
|
|
103
|
-
|
|
104
|
-
def model(section)
|
|
145
|
+
# VICE's model number, which a snapshot of a model badline doesn't
|
|
146
|
+
# build fails on.
|
|
147
|
+
def model_number(section)
|
|
105
148
|
number = FieldReader.new(section).byte
|
|
106
|
-
return
|
|
107
|
-
return :mos6569 if [0, 2].include?(number)
|
|
149
|
+
return number if number < VIC_MODELS.length
|
|
108
150
|
|
|
109
|
-
raise FormatError, "#{section}:
|
|
151
|
+
raise FormatError, "#{section}: VICE's VIC-II model #{number}, which badline doesn't build"
|
|
110
152
|
end
|
|
111
153
|
|
|
154
|
+
def model(section) = VIC_MODELS[model_number(section)]
|
|
155
|
+
|
|
156
|
+
def region(section) = REGIONS[model_number(section)]
|
|
157
|
+
|
|
112
158
|
def import(section, computer)
|
|
113
159
|
snapshot = Import.new(FieldReader.new(section))
|
|
114
160
|
snapshot.apply(computer)
|
|
@@ -34,7 +34,7 @@ module Badline
|
|
|
34
34
|
@cycle = f.dword
|
|
35
35
|
f.skip(4)
|
|
36
36
|
@line = f.dword
|
|
37
|
-
f.
|
|
37
|
+
@frame_start = f.flag?
|
|
38
38
|
@irq_status = f.byte
|
|
39
39
|
f.skip(4)
|
|
40
40
|
@raster_match = f.flag?
|
|
@@ -95,15 +95,26 @@ module Badline
|
|
|
95
95
|
# Clocks the VIC alone from the start of the line to the cycle,
|
|
96
96
|
# so the line's fetches and sprite checks have run.
|
|
97
97
|
def position(vic)
|
|
98
|
-
vic.
|
|
98
|
+
region = vic.region
|
|
99
|
+
vic.restore_line(line(region.lines_per_frame))
|
|
99
100
|
load_display_state(vic)
|
|
100
|
-
|
|
101
|
+
column(region.cycles_per_line).times { vic.cycle! }
|
|
101
102
|
registers = vic.registers
|
|
102
103
|
registers.bytes[0x19] = @irq_status & 0x0f
|
|
103
104
|
registers.bytes[0x1e], registers.bytes[0x1f] = @collisions
|
|
104
105
|
registers.update_irq_line
|
|
105
106
|
end
|
|
106
107
|
|
|
108
|
+
# badline's column, VICE's cycle less one, with VICE's cycle 0
|
|
109
|
+
# the last column of the line before (VICII.vice_cycle).
|
|
110
|
+
def column(cycles_per_line) = @cycle.zero? ? cycles_per_line - 1 : @cycle - 1
|
|
111
|
+
|
|
112
|
+
def line(lines)
|
|
113
|
+
return @line unless @cycle.zero? && !@frame_start
|
|
114
|
+
|
|
115
|
+
@line.zero? ? lines - 1 : @line - 1
|
|
116
|
+
end
|
|
117
|
+
|
|
107
118
|
def load_display_state(vic)
|
|
108
119
|
vic.display_state.restore_counters([@vc_base, @vc, @rc, @vmli], @display, @bad_lines_enabled)
|
|
109
120
|
end
|
|
@@ -6,6 +6,7 @@ require "badline/snapshot/vice/cias"
|
|
|
6
6
|
require "badline/snapshot/vice/cias_import"
|
|
7
7
|
require "badline/snapshot/vice/sid_registers"
|
|
8
8
|
require "badline/snapshot/vice/sid_extended"
|
|
9
|
+
require "badline/snapshot/vice/reu1764"
|
|
9
10
|
require "badline/snapshot/vice/peripherals"
|
|
10
11
|
require "badline/snapshot/vice/vicii"
|
|
11
12
|
require "badline/snapshot/vice/vicii_import"
|
|
@@ -23,36 +24,32 @@ module Badline
|
|
|
23
24
|
# boundary: the longest instruction, stretched by the VIC's longest
|
|
24
25
|
# hold on the bus.
|
|
25
26
|
BOUNDARY_CYCLES = 100
|
|
27
|
+
# Cycles more it may run for an REU transfer in flight to end: a
|
|
28
|
+
# swap of 64K, two cycles a byte, stretched by the VIC's holds on
|
|
29
|
+
# the bus.
|
|
30
|
+
TRANSFER_CYCLES = 0x40000
|
|
26
31
|
|
|
27
32
|
# The modules a badline snapshot carries for VICE, in x64sc's order.
|
|
28
33
|
# They describe a copy of the machine restored from `state`, so saving
|
|
29
34
|
# leaves the machine itself alone. VICE saves between instructions,
|
|
30
35
|
# and resumes by fetching an opcode at the program counter, so when
|
|
31
|
-
# badline's CPU is inside an instruction
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
# VIC-II yet, and fails for a machine with either.
|
|
36
|
+
# badline's CPU is inside an instruction, or an REU transfer holds
|
|
37
|
+
# the bus, the copy runs on to the end of both. The copy's SID
|
|
38
|
+
# catches up, so its registers and voices stand as of that cycle.
|
|
35
39
|
def self.export(state)
|
|
36
|
-
check_exportable(Computer.setup(state))
|
|
37
40
|
machine = settled(state)
|
|
38
|
-
[MainCPU.export(machine), C64Mem.export(machine), Peripherals.cartridge_port
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def self.check_exportable(setup)
|
|
44
|
-
raise FormatError, "badline can't save an REU in VICE's terms yet, so can't save this machine" if setup.reu
|
|
45
|
-
return if setup.region == Region::PAL
|
|
46
|
-
|
|
47
|
-
raise FormatError, "badline can't save an NTSC VIC-II in VICE's terms yet, so can't save this machine"
|
|
41
|
+
sections = [MainCPU.export(machine), C64Mem.export(machine), Peripherals.cartridge_port(machine)]
|
|
42
|
+
sections << REU1764.export(machine) if machine.reu
|
|
43
|
+
sections + [*CIAs.export(machine), SIDRegisters.export(machine), SIDExtended.export(machine),
|
|
44
|
+
Peripherals.drives, VICII.export(machine), Peripherals.glue(machine), *Peripherals.rest]
|
|
48
45
|
end
|
|
49
46
|
|
|
50
47
|
# The copy is detached from the host's files (Snapshot::StateReader),
|
|
51
48
|
# so it reads none and its traps write none.
|
|
52
49
|
def self.settled(state)
|
|
53
50
|
copy = Computer.restored(state, detached: true)
|
|
54
|
-
BOUNDARY_CYCLES.times do
|
|
55
|
-
break if copy.cpu.boundary?
|
|
51
|
+
(BOUNDARY_CYCLES + TRANSFER_CYCLES).times do
|
|
52
|
+
break if copy.cpu.boundary? && !copy.reu&.dma?
|
|
56
53
|
|
|
57
54
|
copy.cycle!
|
|
58
55
|
end
|
|
@@ -60,14 +57,19 @@ module Badline
|
|
|
60
57
|
copy
|
|
61
58
|
end
|
|
62
59
|
|
|
63
|
-
# How the machine the snapshot was saved from was built
|
|
64
|
-
#
|
|
65
|
-
#
|
|
60
|
+
# How the machine the snapshot was saved from was built: the VIC
|
|
61
|
+
# model and the region from the VIC-II's model, and an REU from
|
|
62
|
+
# REU1764. VICE doesn't save the CIA model; the 8565 and 8562 go
|
|
63
|
+
# with the 6526A.
|
|
66
64
|
def self.setup(container)
|
|
67
|
-
vic = VICII.find(container)
|
|
65
|
+
vic = VICII.find(container)
|
|
66
|
+
model = vic ? VICII.model(vic) : :mos6569
|
|
68
67
|
sid = container[SIDRegisters::NAME] ? SIDRegisters.model(container[SIDRegisters::NAME]) : :mos6581
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
reu = container[REU1764::NAME]
|
|
69
|
+
reu = nil unless reu && REU1764.reads?(reu)
|
|
70
|
+
Setup.new(vic_model: model, cia_model: model == :mos8565 ? :mos6526a : :mos6526, sid_model: sid,
|
|
71
|
+
region: vic ? VICII.region(vic) : Region::PAL, ram_expansion: nil,
|
|
72
|
+
reu: reu ? REU1764.size_kb(reu) : nil, kernal: :c64, datasette: true)
|
|
71
73
|
end
|
|
72
74
|
end
|
|
73
75
|
end
|