badline 0.8.0 → 0.9.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 +29 -0
- data/README.md +22 -16
- data/doc/library.md +10 -3
- data/doc/media.md +11 -2
- data/doc/pinned-behaviour.md +53 -9
- data/doc/sid-player.md +8 -4
- data/doc/snapshots.md +66 -33
- data/lib/badline/address_bus/roms.rb +32 -0
- data/lib/badline/address_bus.rb +4 -4
- 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/computer/attachments.rb +5 -5
- data/lib/badline/computer.rb +12 -10
- data/lib/badline/core.rb +48 -0
- data/lib/badline/data_path.rb +37 -0
- data/lib/badline/datasette.rb +24 -9
- data/lib/badline/drive1541/disk.rb +3 -3
- data/lib/badline/drive1541/disk_state.rb +14 -5
- data/lib/badline/frontend/app.rb +4 -3
- data/lib/badline/frontend/boot.rb +9 -4
- data/lib/badline/frontend/confirmation.rb +17 -11
- data/lib/badline/frontend/menu_dialogs.rb +52 -14
- data/lib/badline/frontend/menu_media.rb +36 -7
- data/lib/badline/frontend/menu_pages.rb +24 -2
- data/lib/badline/frontend/name_field.rb +88 -0
- data/lib/badline/frontend/pause_menu.rb +25 -7
- data/lib/badline/frontend/player_window.rb +3 -3
- data/lib/badline/frontend/snapshots.rb +145 -17
- data/lib/badline/frontend/sound.rb +11 -5
- data/lib/badline/frontend.rb +1 -0
- data/lib/badline/kernal_trap/load.rb +10 -0
- data/lib/badline/kernal_trap/routine.rb +28 -0
- data/lib/badline/kernal_trap/serial.rb +19 -7
- data/lib/badline/media/disk_list.rb +75 -0
- data/lib/badline/media/disk_set.rb +13 -3
- data/lib/badline/media.rb +8 -3
- data/lib/badline/model.rb +62 -0
- data/lib/badline/options/event.rb +5 -3
- data/lib/badline/options/help.rb +16 -12
- data/lib/badline/options/table.rb +5 -1
- data/lib/badline/options/validation.rb +9 -0
- data/lib/badline/options.rb +7 -4
- 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 +10 -0
- data/lib/badline/roms/kernal-sx64.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/fields.rb +1 -0
- data/lib/badline/snapshot/setup.rb +20 -8
- data/lib/badline/snapshot/state.rb +14 -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/storage/t64.rb +7 -4
- data/lib/badline/storage/tap.rb +7 -5
- data/lib/badline/storage/unavailable.rb +1 -1
- data/lib/badline/storage.rb +5 -5
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic/sprite/timing.rb +5 -9
- data/lib/badline/vic/sprite.rb +4 -3
- data/lib/badline.rb +2 -38
- metadata +10 -1
|
@@ -32,6 +32,33 @@ module Badline
|
|
|
32
32
|
@ram.load_state(input)
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
# The sixteen bytes the REC's registers read from $DF00, without
|
|
36
|
+
# what reading the status does, as VICE's REU1764 module holds them.
|
|
37
|
+
def register_file = Array.new(16) { |register| register_value(register) }
|
|
38
|
+
|
|
39
|
+
# Sets the registers from a register file, as writing them would but
|
|
40
|
+
# without starting a transfer: each address and the length set both
|
|
41
|
+
# the counter and the value it starts from. A command with the
|
|
42
|
+
# execute bit set and the $FF00 trigger on arms the REC.
|
|
43
|
+
def restore_registers(values)
|
|
44
|
+
@status = (values[STATUS] & (INTERRUPT_PENDING | END_OF_BLOCK | FAULT)) | @chips
|
|
45
|
+
@command = values[COMMAND]
|
|
46
|
+
@c64 = @c64_start = values[C64_LOW] | (values[C64_HIGH] << 8)
|
|
47
|
+
@expansion = @expansion_start = values[EXPANSION_LOW] | (values[EXPANSION_HIGH] << 8)
|
|
48
|
+
@bank = @bank_start = values[EXPANSION_BANK] & @bank_bits
|
|
49
|
+
@length = @length_start = values[LENGTH_LOW] | (values[LENGTH_HIGH] << 8)
|
|
50
|
+
@interrupt_mask = values[INTERRUPT_MASK] | 0x1f
|
|
51
|
+
@address_control = values[ADDRESS_CONTROL] | 0x3f
|
|
52
|
+
@armed = @command.anybits?(EXECUTE) && @command.nobits?(NO_FF00_TRIGGER)
|
|
53
|
+
@requested = @running = @ba_was_low = false
|
|
54
|
+
interrupt(@status.anybits?(INTERRUPT_PENDING))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The RAM, every byte of it, as one string.
|
|
58
|
+
def ram_contents = @ram.contents
|
|
59
|
+
|
|
60
|
+
def restore_ram(bytes) = @ram.restore(bytes)
|
|
61
|
+
|
|
35
62
|
private
|
|
36
63
|
|
|
37
64
|
def load_registers(input)
|
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
|
|
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!
|
|
@@ -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,10 @@ 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, still reads.
|
|
18
|
+
SCHEMA = 4
|
|
19
|
+
READABLE = [3, SCHEMA].freeze
|
|
18
20
|
|
|
19
21
|
attr_reader :values, :strings
|
|
20
22
|
|
|
@@ -101,15 +103,19 @@ module Badline
|
|
|
101
103
|
# A detached reader restores a machine that opens no host file and
|
|
102
104
|
# writes none, for a copy the machine runs on its own: a disk image
|
|
103
105
|
# comes from the bytes the state holds, write-protected, a directory
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
+
# device 8 serves is left out, and a true drive's disk keeps the
|
|
107
|
+
# state's tracks without its image file.
|
|
106
108
|
class StateReader
|
|
109
|
+
# The layout check_stamp found.
|
|
110
|
+
attr_reader :schema
|
|
111
|
+
|
|
107
112
|
def initialize(state, detached: false)
|
|
108
113
|
@values = state.values
|
|
109
114
|
@strings = state.strings
|
|
110
115
|
@value = 0
|
|
111
116
|
@string = 0
|
|
112
117
|
@detached = detached
|
|
118
|
+
@schema = State::SCHEMA
|
|
113
119
|
end
|
|
114
120
|
|
|
115
121
|
def detached? = @detached
|
|
@@ -160,12 +166,13 @@ module Badline
|
|
|
160
166
|
raise FormatError, "expected #{name} in the state, found #{found[0, 20].inspect}" unless found == name
|
|
161
167
|
end
|
|
162
168
|
|
|
163
|
-
# Fails unless the State was written in
|
|
164
|
-
# version.
|
|
169
|
+
# Fails unless the State was written in a layout this badline reads
|
|
170
|
+
# (State::READABLE) by this badline version.
|
|
165
171
|
def check_stamp
|
|
166
172
|
schema = int
|
|
167
173
|
version = string
|
|
168
|
-
|
|
174
|
+
@schema = schema
|
|
175
|
+
return if State::READABLE.include?(schema) && version == VERSION
|
|
169
176
|
|
|
170
177
|
raise FormatError, "the state was written by badline #{version.inspect} in layout #{schema}, " \
|
|
171
178
|
"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
|
data/lib/badline/storage/t64.rb
CHANGED
|
@@ -12,15 +12,18 @@ module Badline
|
|
|
12
12
|
TAPE_NAME = 0x28
|
|
13
13
|
ID = "T64".bytes.freeze
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
# `bytes` stands in for the host file's contents, as a snapshot
|
|
16
|
+
# restores the archive it holds.
|
|
17
|
+
def initialize(path, bytes: nil)
|
|
16
18
|
@path = path
|
|
17
|
-
@bytes = File.binread(path).bytes
|
|
19
|
+
@bytes = bytes || File.binread(path).bytes
|
|
18
20
|
raise FormatError, "Missing T64 signature" unless @bytes[0, 3] == SIGNATURE
|
|
19
21
|
end
|
|
20
22
|
|
|
21
|
-
# What Storage.reopen needs to open the archive again
|
|
23
|
+
# What Storage.reopen needs to open the archive again: its path and
|
|
24
|
+
# its bytes.
|
|
22
25
|
def save_setup(out)
|
|
23
|
-
out.int(T64_ARCHIVE).string(::File.expand_path(@path))
|
|
26
|
+
out.int(T64_ARCHIVE).string(::File.expand_path(@path)).blob(@bytes)
|
|
24
27
|
end
|
|
25
28
|
|
|
26
29
|
def read_file(name, **)
|
data/lib/badline/storage/tap.rb
CHANGED
|
@@ -13,13 +13,14 @@ module Badline
|
|
|
13
13
|
HEADER_SIZE = 0x14
|
|
14
14
|
OVERFLOW = 0x100 * 8
|
|
15
15
|
|
|
16
|
-
# The file it was read from, expanded, and the
|
|
17
|
-
|
|
18
|
-
attr_reader :version, :path
|
|
16
|
+
# The file it was read from, expanded, and the file's bytes.
|
|
17
|
+
attr_reader :version, :path, :bytes
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
# `bytes` stands in for the host file's contents, as a snapshot
|
|
20
|
+
# restores the tape it holds.
|
|
21
|
+
def initialize(path, bytes: nil)
|
|
21
22
|
@path = File.expand_path(path)
|
|
22
|
-
@bytes = File.binread(path).bytes
|
|
23
|
+
@bytes = bytes || File.binread(path).bytes
|
|
23
24
|
raise FormatError, "Missing TAP signature" unless @bytes[0, SIGNATURE.length] == SIGNATURE
|
|
24
25
|
|
|
25
26
|
@version = @bytes[0x0c].to_i
|
|
@@ -33,6 +34,7 @@ module Badline
|
|
|
33
34
|
@pos = HEADER_SIZE
|
|
34
35
|
end
|
|
35
36
|
|
|
37
|
+
# The offset of the next pulse byte.
|
|
36
38
|
def position = @pos
|
|
37
39
|
|
|
38
40
|
def position=(offset)
|