badline 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -0
- data/CLAUDE.md +23 -23
- data/CONTRIBUTING.md +5 -0
- data/README.md +59 -1
- data/doc/pinned-behaviour.md +4 -2
- data/exe/badline-ruby +2 -1
- data/lib/badline/address_bus/saved_state.rb +72 -0
- data/lib/badline/address_bus.rb +1 -0
- data/lib/badline/audio/stream.rb +6 -0
- data/lib/badline/cartridge/action_replay.rb +15 -0
- data/lib/badline/cartridge/comal80.rb +8 -0
- data/lib/badline/cartridge/dinamic.rb +8 -0
- data/lib/badline/cartridge/easy_flash.rb +20 -1
- data/lib/badline/cartridge/epyx_fastload.rb +8 -0
- data/lib/badline/cartridge/final_cartridge3.rb +12 -0
- data/lib/badline/cartridge/flash.rb +41 -0
- data/lib/badline/cartridge/fun_play.rb +8 -0
- data/lib/badline/cartridge/g_mod2.rb +12 -0
- data/lib/badline/cartridge/game_system.rb +8 -0
- data/lib/badline/cartridge/geo_ram.rb +16 -0
- data/lib/badline/cartridge/magic_desk.rb +8 -0
- data/lib/badline/cartridge/ocean.rb +8 -0
- data/lib/badline/cartridge/pagefox.rb +12 -0
- data/lib/badline/cartridge/retro_replay.rb +44 -1
- data/lib/badline/cartridge/rgcd.rb +10 -0
- data/lib/badline/cartridge/super_games.rb +10 -0
- data/lib/badline/cartridge/zaxxon.rb +16 -0
- data/lib/badline/cartridge.rb +72 -0
- data/lib/badline/cia/interrupt_register.rb +17 -0
- data/lib/badline/cia/serial.rb +44 -0
- data/lib/badline/cia/state.rb +47 -0
- data/lib/badline/cia/timer.rb +33 -0
- data/lib/badline/cia.rb +24 -2
- data/lib/badline/computer/attachments.rb +132 -0
- data/lib/badline/computer/saved_state.rb +116 -0
- data/lib/badline/computer.rb +8 -31
- data/lib/badline/cpu/microcode.rb +8 -0
- data/lib/badline/cpu/saved_state.rb +115 -0
- data/lib/badline/cpu.rb +2 -0
- data/lib/badline/datasette.rb +27 -0
- data/lib/badline/drive1541/bus.rb +89 -13
- data/lib/badline/drive1541/disk.rb +7 -2
- data/lib/badline/drive1541/disk_state.rb +80 -0
- data/lib/badline/drive1541/idle.rb +9 -57
- data/lib/badline/drive1541/mechanism.rb +2 -0
- data/lib/badline/drive1541/mechanism_state.rb +67 -0
- data/lib/badline/drive1541/orbit.rb +81 -0
- data/lib/badline/drive1541/rotation.rb +10 -0
- data/lib/badline/drive1541/serial_port.rb +10 -0
- data/lib/badline/drive1541/sleep.rb +114 -0
- data/lib/badline/drive1541.rb +52 -2
- data/lib/badline/gui/application.rb +41 -14
- data/lib/badline/gui/gamepads.rb +7 -0
- data/lib/badline/gui/snapshots.rb +67 -0
- data/lib/badline/gui.rb +1 -0
- data/lib/badline/iec_bus.rb +16 -0
- data/lib/badline/kernal_trap/channel.rb +27 -0
- data/lib/badline/kernal_trap/drive/channels.rb +25 -0
- data/lib/badline/kernal_trap/drive/memory.rb +9 -0
- data/lib/badline/kernal_trap/drive/status.rb +8 -0
- data/lib/badline/kernal_trap/drive/write_file.rb +21 -0
- data/lib/badline/kernal_trap/drive.rb +19 -0
- data/lib/badline/kernal_trap/save.rb +8 -0
- data/lib/badline/kernal_trap/serial.rb +16 -0
- data/lib/badline/memory.rb +11 -0
- data/lib/badline/options.rb +3 -2
- data/lib/badline/ram_expansion/plus256k.rb +16 -0
- data/lib/badline/ram_expansion/plus60k.rb +12 -0
- data/lib/badline/ram_expansion/unexpanded.rb +6 -0
- data/lib/badline/reu/dma.rb +36 -0
- data/lib/badline/reu/ram.rb +15 -0
- data/lib/badline/reu/saved_state.rb +52 -0
- data/lib/badline/reu.rb +4 -0
- data/lib/badline/sdl/events.rb +2 -0
- data/lib/badline/sid/envelope/state.rb +68 -0
- data/lib/badline/sid/envelope.rb +7 -0
- data/lib/badline/sid/filter.rb +38 -0
- data/lib/badline/sid/voice.rb +10 -0
- data/lib/badline/sid/waveform/state.rb +66 -0
- data/lib/badline/sid/waveform.rb +2 -0
- data/lib/badline/sid.rb +34 -0
- data/lib/badline/snapshot/container.rb +117 -0
- data/lib/badline/snapshot/fields.rb +54 -0
- data/lib/badline/snapshot/image.rb +70 -0
- data/lib/badline/snapshot/machine_state.rb +77 -0
- data/lib/badline/snapshot/setup.rb +58 -0
- data/lib/badline/snapshot/state.rb +178 -0
- data/lib/badline/snapshot/vice/c64_mem.rb +53 -0
- data/lib/badline/snapshot/vice/cias.rb +89 -0
- data/lib/badline/snapshot/vice/cias_import.rb +70 -0
- data/lib/badline/snapshot/vice/main_cpu.rb +76 -0
- data/lib/badline/snapshot/vice/peripherals.rb +47 -0
- data/lib/badline/snapshot/vice/restore.rb +125 -0
- data/lib/badline/snapshot/vice/sid_extended.rb +96 -0
- data/lib/badline/snapshot/vice/sid_registers.rb +46 -0
- data/lib/badline/snapshot/vice/vicii.rb +119 -0
- data/lib/badline/snapshot/vice/vicii_import.rb +131 -0
- data/lib/badline/snapshot/vice.rb +74 -0
- data/lib/badline/snapshot.rb +52 -0
- data/lib/badline/storage/crt_file.rb +9 -3
- data/lib/badline/storage/d64_image.rb +2 -0
- data/lib/badline/storage/d71_image.rb +2 -0
- data/lib/badline/storage/d81_image.rb +2 -0
- data/lib/badline/storage/disk_image.rb +11 -2
- data/lib/badline/storage/host_directory.rb +5 -0
- data/lib/badline/storage/t64.rb +6 -0
- data/lib/badline/storage/tap.rb +10 -1
- data/lib/badline/storage/unavailable.rb +20 -0
- data/lib/badline/storage.rb +34 -0
- data/lib/badline/time_of_day.rb +56 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/via/control_lines.rb +15 -0
- data/lib/badline/via/fast_forward.rb +15 -0
- data/lib/badline/via/interrupt_register.rb +9 -0
- data/lib/badline/via/shift_register.rb +13 -0
- data/lib/badline/via/state.rb +42 -0
- data/lib/badline/via/timer1.rb +19 -0
- data/lib/badline/via/timer2.rb +24 -0
- data/lib/badline/via.rb +2 -0
- data/lib/badline/vic/border_mask.rb +10 -0
- data/lib/badline/vic/collisions.rb +11 -0
- data/lib/badline/vic/color_patches.rb +11 -0
- data/lib/badline/vic/display_state.rb +29 -1
- data/lib/badline/vic/graphics_shifter.rb +26 -0
- data/lib/badline/vic/register_log.rb +18 -0
- data/lib/badline/vic/registers.rb +13 -2
- data/lib/badline/vic/sequencer.rb +12 -0
- data/lib/badline/vic/sequencer_state.rb +89 -0
- data/lib/badline/vic/sprite/internal_bus.rb +11 -0
- data/lib/badline/vic/sprite/saved_state.rb +64 -0
- data/lib/badline/vic/sprite.rb +2 -0
- data/lib/badline/vic/sprites.rb +3 -0
- data/lib/badline/vic/sprites_state.rb +57 -0
- data/lib/badline/vic.rb +94 -10
- data/lib/badline.rb +1 -0
- metadata +37 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
# C64MEM 0.1: the CPU port, the cartridge's EXROM and GAME lines, and
|
|
7
|
+
# the 64K of RAM. The lines are written inactive, to match the empty
|
|
8
|
+
# cartridge port the snapshot shows VICE, and aren't read: badline
|
|
9
|
+
# takes them from its own cartridge.
|
|
10
|
+
#
|
|
11
|
+
# The floating port bits 6 and 7 carry their charge both ways. VICE's
|
|
12
|
+
# clocks for when the charge drains are written as zero and not read:
|
|
13
|
+
# the charge lasts as long as badline holds it.
|
|
14
|
+
module C64Mem
|
|
15
|
+
NAME = "C64MEM"
|
|
16
|
+
MAJOR = 0
|
|
17
|
+
MINOR = 1
|
|
18
|
+
FLOATING = [0x40, 0x80].freeze
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
def reads?(section) = section.major == MAJOR && section.minor == MINOR
|
|
23
|
+
|
|
24
|
+
def export(computer)
|
|
25
|
+
bus = computer.address_bus
|
|
26
|
+
ddr, port_out, floating = bus.port_state
|
|
27
|
+
fields = FieldWriter.new.byte(port_out).byte(ddr).byte(0).byte(0)
|
|
28
|
+
fields.bytes(Array.new(0x10000) { |addr| computer.ram.peek(addr) })
|
|
29
|
+
fields.byte((port_out & ddr) | (floating & ~ddr)).byte(bus.peek(1)).byte(ddr)
|
|
30
|
+
fields.dword(0).dword(0)
|
|
31
|
+
charged = FLOATING.map { |bit| floating.anybits?(bit) }
|
|
32
|
+
draining = FLOATING.map { |bit| floating.anybits?(bit) && ddr.nobits?(bit) }
|
|
33
|
+
(charged + draining).each { |set| fields.flag(set) }
|
|
34
|
+
fields.section(NAME, MAJOR, MINOR)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The port is set without a bus write, which would leave the VIC's
|
|
38
|
+
# phi1 byte in the RAM under $00 and $01.
|
|
39
|
+
def import(section, computer)
|
|
40
|
+
fields = FieldReader.new(section)
|
|
41
|
+
data = fields.byte
|
|
42
|
+
ddr = fields.byte
|
|
43
|
+
fields.skip(2) # EXROM and GAME
|
|
44
|
+
computer.ram.write(0, fields.bytes(0x10000))
|
|
45
|
+
fields.skip(3 + 8)
|
|
46
|
+
charge = FLOATING.sum { |bit| fields.flag? ? bit : 0 }
|
|
47
|
+
driven = ddr & AddressBus::PORT_FLOATING
|
|
48
|
+
computer.address_bus.restore_port(ddr, data, (data & driven) | (charge & ~driven & 0xff))
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
# CIA1 and CIA2 2.3: the registers, both timers' counters and
|
|
7
|
+
# latches, the interrupt flags and mask, the TOD clock, its alarm and
|
|
8
|
+
# latch, and the shift register.
|
|
9
|
+
#
|
|
10
|
+
# Each timer's VICE state word carries its start, one-shot and input
|
|
11
|
+
# bits and whether its count pipeline is full, which is what badline's
|
|
12
|
+
# pipeline holds between loads. A load or one-shot stop in flight,
|
|
13
|
+
# the TOD divider's phase within a tenth and the shift register's
|
|
14
|
+
# progress through a byte are approximations both ways.
|
|
15
|
+
module CIAs
|
|
16
|
+
MAJOR = 2
|
|
17
|
+
MINOR = 3
|
|
18
|
+
NAMES = %w[CIA1 CIA2].freeze
|
|
19
|
+
|
|
20
|
+
# VICE's timer state bits.
|
|
21
|
+
START = 0x001
|
|
22
|
+
COUNT2 = 0x002
|
|
23
|
+
ONESHOT = 0x008
|
|
24
|
+
PHI2IN = 0x020
|
|
25
|
+
COUNT3 = 0x040
|
|
26
|
+
COUNT = 0x800
|
|
27
|
+
|
|
28
|
+
module_function
|
|
29
|
+
|
|
30
|
+
def export(computer)
|
|
31
|
+
[computer.cia1, computer.cia2].each_with_index.map do |cia, i|
|
|
32
|
+
fields = FieldWriter.new
|
|
33
|
+
registers(cia, fields)
|
|
34
|
+
time_of_day(cia.time_of_day, fields)
|
|
35
|
+
internals(cia, fields)
|
|
36
|
+
fields.section(NAMES[i], MAJOR, MINOR)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def registers(cia, fields)
|
|
41
|
+
cia.port_registers.each { |value| fields.byte(value) }
|
|
42
|
+
fields.word(cia.timer_a).word(cia.timer_b)
|
|
43
|
+
cia.time_of_day.registers.first(4).each { |value| fields.byte(value) }
|
|
44
|
+
fields.byte(cia.serial.data).byte(cia.interrupt_control.value)
|
|
45
|
+
fields.byte(cia.control_a.value).byte(cia.control_b.value)
|
|
46
|
+
fields.word(cia.timer_a_latch).word(cia.timer_b_latch).byte(cia.interrupt_status.value)
|
|
47
|
+
ta, tb = cia.timers
|
|
48
|
+
fields.byte((ta.toggle ? 0x40 : 0) | (tb.toggle ? 0x80 : 0) |
|
|
49
|
+
(ta.underflowed ? 0x04 : 0) | (tb.underflowed ? 0x08 : 0))
|
|
50
|
+
fields.byte(cia.serial.steps.clamp(0, 0xff))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def time_of_day(tod, fields)
|
|
54
|
+
registers = tod.registers
|
|
55
|
+
registers[4, 4].each { |value| fields.byte(value) }
|
|
56
|
+
fields.byte(0) # cycles since the ICR was read
|
|
57
|
+
fields.byte(registers[9] | (registers[8] << 1)) # latched, stopped
|
|
58
|
+
tod.latched_fields.each { |value| fields.byte(value) }
|
|
59
|
+
fields.qword(tod.cycles_to_tenth)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def internals(cia, fields)
|
|
63
|
+
cia.timers.each { |timer| fields.word(timer_state(timer)) }
|
|
64
|
+
serial = cia.serial
|
|
65
|
+
shift = serial.shift_register
|
|
66
|
+
fields.byte(shift & 0xff).flag(!serial.pending.nil?).flag(cia.interrupted?)
|
|
67
|
+
fields.byte(cia.time_of_day.pulses)
|
|
68
|
+
fields.byte(shift >> 8).byte(0)
|
|
69
|
+
fields.byte((serial.sp_in ? 0x80 : 0) | (serial.cnt_in ? 0x40 : 0))
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def timer_state(timer)
|
|
73
|
+
control = timer.control.value
|
|
74
|
+
pipe = timer.pipe
|
|
75
|
+
(control & (START | ONESHOT)) | (control.nobits?(0x60) ? PHI2IN : 0) |
|
|
76
|
+
(pipe.anybits?(0b10) ? COUNT2 : 0) | (pipe.anybits?(0b01) ? COUNT3 | COUNT : 0)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Later minor versions add fields at the end.
|
|
80
|
+
def reads?(section) = section.major == MAJOR && section.minor >= MINOR
|
|
81
|
+
|
|
82
|
+
def import(section, computer)
|
|
83
|
+
cia = section.name == NAMES.first ? computer.cia1 : computer.cia2
|
|
84
|
+
Import.new(FieldReader.new(section)).apply(cia)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
module CIAs
|
|
7
|
+
# Reads a CIA module and puts it into one of badline's CIAs.
|
|
8
|
+
class Import
|
|
9
|
+
def initialize(fields)
|
|
10
|
+
@fields = fields
|
|
11
|
+
f = fields
|
|
12
|
+
@ports = f.bytes(4)
|
|
13
|
+
@counters = [f.word, f.word]
|
|
14
|
+
@clock = f.bytes(4)
|
|
15
|
+
@sdr, @mask, @control_a, @control_b = f.bytes(4)
|
|
16
|
+
@latches = [f.word, f.word]
|
|
17
|
+
@status, @toggles = f.bytes(2)
|
|
18
|
+
read_time_of_day
|
|
19
|
+
read_internals
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def apply(cia)
|
|
23
|
+
@ports.each_with_index { |value, port| cia.poke(cia.start + port, value) }
|
|
24
|
+
cia.timer_a, cia.timer_b = @counters
|
|
25
|
+
cia.timer_a_latch, cia.timer_b_latch = @latches
|
|
26
|
+
cia.interrupt_control.value = @mask & 0x1f
|
|
27
|
+
cia.interrupt_status.value = @status
|
|
28
|
+
cia.serial.restore_shift(@sdr, @shift)
|
|
29
|
+
load_timers(cia)
|
|
30
|
+
cia.time_of_day.fifty_hz = @control_a.anybits?(0x80)
|
|
31
|
+
cia.time_of_day.restore_fields(@clock, @alarm, @latch)
|
|
32
|
+
cia.time_of_day.restore_divider(@stopped, @pulses, @ticks)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def read_time_of_day
|
|
38
|
+
f = @fields
|
|
39
|
+
f.skip(1) # the shift register's half-bits
|
|
40
|
+
@alarm = f.bytes(4)
|
|
41
|
+
f.skip(1) # cycles since the ICR was read
|
|
42
|
+
state = f.byte
|
|
43
|
+
@stopped = state.anybits?(2)
|
|
44
|
+
latch = f.bytes(4)
|
|
45
|
+
@latch = state.anybits?(1) ? latch : nil
|
|
46
|
+
@ticks = f.qword
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def read_internals
|
|
50
|
+
f = @fields
|
|
51
|
+
@states = [f.word, f.word]
|
|
52
|
+
@shift = f.byte
|
|
53
|
+
f.skip(2) # whether the SDR holds a byte to send, the IRQ line
|
|
54
|
+
@pulses = f.byte.clamp(0, 5)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Each timer takes its control register without the load strobe,
|
|
58
|
+
# its output toggle, and whether its count pipeline is full.
|
|
59
|
+
def load_timers(cia)
|
|
60
|
+
[@control_a, @control_b].zip(cia.timers).each_with_index do |(control, timer), i|
|
|
61
|
+
state = @states[i]
|
|
62
|
+
pipe = (state.anybits?(COUNT2) ? 0b10 : 0) | (state.anybits?(COUNT3) ? 0b01 : 0)
|
|
63
|
+
timer.restore_pipeline(control, @toggles.anybits?(0x40 << i), pipe)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
# The 6510 as x64sc saves it: MAINCPU 1.4 from VICE 3.8 on, 1.2
|
|
7
|
+
# before, and MAINC64CPU 1.5, the same fields under a new name, in
|
|
8
|
+
# VICE's development versions. The clock, the registers, the last
|
|
9
|
+
# opcode, the ANE and LXA log levels, whether the CPU is jammed and the
|
|
10
|
+
# BA low flags, then the interrupt bookkeeping: the IRQ, NMI and
|
|
11
|
+
# pending-IRQ clocks, the stolen cycles and when they were stolen, the
|
|
12
|
+
# IRQ and NMI source counts, the pending interrupts and the IRQ and NMI
|
|
13
|
+
# delays. 1.2 lacks the log levels and the jammed flag.
|
|
14
|
+
#
|
|
15
|
+
# Written: the registers as they stand, which between instructions is
|
|
16
|
+
# the whole CPU. VICE's interrupt clocks and delays are written as long
|
|
17
|
+
# past, with the IRQ and NMI source counts and a pending NMI.
|
|
18
|
+
#
|
|
19
|
+
# Read: the clock and the registers. The CPU starts on an instruction
|
|
20
|
+
# boundary, as VICE's snapshot is taken on one, and a pending NMI is
|
|
21
|
+
# taken there.
|
|
22
|
+
module MainCPU
|
|
23
|
+
NAME = "MAINCPU"
|
|
24
|
+
TRUNK_NAME = "MAINC64CPU"
|
|
25
|
+
MAJOR = 1
|
|
26
|
+
MINOR = 4
|
|
27
|
+
# The versions read, by name, and whether each has the log levels
|
|
28
|
+
# and the jammed flag.
|
|
29
|
+
VERSIONS = { "MAINCPU 1.2" => false, "MAINCPU 1.4" => true, "MAINC64CPU 1.5" => true }.freeze
|
|
30
|
+
IK_NMI = 0x01
|
|
31
|
+
IK_IRQ = 0x02
|
|
32
|
+
|
|
33
|
+
module_function
|
|
34
|
+
|
|
35
|
+
def export(computer)
|
|
36
|
+
cpu = computer.cpu
|
|
37
|
+
fields = FieldWriter.new.qword(computer.cycles)
|
|
38
|
+
[cpu.a, cpu.x, cpu.y, cpu.stack_pointer].each { |register| fields.byte(register) }
|
|
39
|
+
fields.word(cpu.program_counter).byte(cpu.p)
|
|
40
|
+
fields.dword(0).dword(0).dword(0).dword(cpu.jammed? ? 1 : 0).dword(0)
|
|
41
|
+
5.times { fields.qword(0) }
|
|
42
|
+
interrupts(computer, fields)
|
|
43
|
+
fields.qword(0).qword(0)
|
|
44
|
+
fields.section(NAME, MAJOR, MINOR)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def interrupts(computer, fields)
|
|
48
|
+
irqs = [computer.vic.interrupted?, computer.cia1.interrupted?].count(true)
|
|
49
|
+
nmis = computer.cia2.interrupted? ? 1 : 0
|
|
50
|
+
pending = (irqs.positive? ? IK_IRQ : 0) | (computer.cpu.nmi ? IK_NMI : 0)
|
|
51
|
+
fields.dword(irqs).dword(nmis).dword(pending)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# The module a snapshot has, under either name.
|
|
55
|
+
def find(container) = container[NAME] || container[TRUNK_NAME]
|
|
56
|
+
|
|
57
|
+
def reads?(section) = VERSIONS.key?(section.to_s)
|
|
58
|
+
|
|
59
|
+
def import(section, computer)
|
|
60
|
+
fields = FieldReader.new(section)
|
|
61
|
+
clock = fields.qword
|
|
62
|
+
cpu = computer.cpu
|
|
63
|
+
cpu.a, cpu.x, cpu.y, cpu.stack_pointer = Array.new(4) { fields.byte }
|
|
64
|
+
cpu.program_counter = fields.word
|
|
65
|
+
cpu.p = fields.byte
|
|
66
|
+
fields.skip(VERSIONS.fetch(section.to_s) ? 20 : 8)
|
|
67
|
+
fields.skip(40 + 8)
|
|
68
|
+
pending = fields.dword
|
|
69
|
+
cpu.resume(clock)
|
|
70
|
+
cpu.nmi = pending.anybits?(IK_NMI)
|
|
71
|
+
computer.resume_at(clock)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
# The modules x64sc refuses a snapshot without, for the parts badline
|
|
7
|
+
# doesn't model in VICE's terms: the cartridge port, the file system
|
|
8
|
+
# drives, the glue logic, memory expansions, the tape port, the
|
|
9
|
+
# control ports and the user port. Each is written as a machine with
|
|
10
|
+
# nothing attached leaves it, with the joysticks released and the
|
|
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.
|
|
13
|
+
module Peripherals
|
|
14
|
+
# The glue logic's VIC bank, and its pending bank change.
|
|
15
|
+
GLUE = "GLUE"
|
|
16
|
+
|
|
17
|
+
FIXED = [
|
|
18
|
+
["C64CART", 0, 1, [0x00]], # no cartridge
|
|
19
|
+
["FSDRIVE", 0, 0, Array.new(258, 0)] # no file system drive open
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
22
|
+
AFTER_VIC = [
|
|
23
|
+
["C64MEMHACKS", 0, 0, [0x00]], # no memory expansion hack
|
|
24
|
+
["TAPEPORT", 1, 0, [0x01, 0x01]], # tape port on, datasette in it
|
|
25
|
+
["JOYPORT0", 0, 0, [0x01]], ["JOYSTICK0", 1, 2, [0x00, 0x00]], # joysticks, released
|
|
26
|
+
["JOYPORT1", 0, 0, [0x01]], ["JOYSTICK1", 1, 2, [0x00, 0x00]],
|
|
27
|
+
["USERPORT", 1, 0, [0x01, 0x00]] # user port on, nothing in it
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
30
|
+
module_function
|
|
31
|
+
|
|
32
|
+
def cartridge_port = section(*FIXED[0])
|
|
33
|
+
|
|
34
|
+
def drives = section(*FIXED[1])
|
|
35
|
+
|
|
36
|
+
def glue(computer)
|
|
37
|
+
bank = ~computer.cia2.port_a_lines & 0x03
|
|
38
|
+
FieldWriter.new.byte(0).byte(bank).byte(0).section(GLUE, 1, 0)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def rest = AFTER_VIC.map { |entry| section(*entry) }
|
|
42
|
+
|
|
43
|
+
def section(name, major, minor, bytes) = FieldWriter.new.bytes(bytes).section(name, major, minor)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
# Restores a machine from a snapshot VICE wrote, through the modules
|
|
7
|
+
# badline reads.
|
|
8
|
+
module Restore
|
|
9
|
+
# The modules badline reads, in the order it applies them: the VIC
|
|
10
|
+
# comes last, as it is clocked to its cycle through memory and CIA 2.
|
|
11
|
+
# MAINCPU and C64MEM hold the machine itself, so a version badline
|
|
12
|
+
# doesn't read fails the restore; the others are left out and
|
|
13
|
+
# reported.
|
|
14
|
+
READ = [MainCPU::NAME, C64Mem::NAME, "CIA1", "CIA2", SIDRegisters::NAME, SIDExtended::NAME,
|
|
15
|
+
VICII::NAME].freeze
|
|
16
|
+
REQUIRED = [MainCPU::NAME, C64Mem::NAME].freeze
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# Applies the modules badline reads to `computer`, and returns a
|
|
21
|
+
# Report naming what it applied and, with a reason, what it left out.
|
|
22
|
+
# The snapshot goes into a new machine first, so one that fails leaves
|
|
23
|
+
# `computer` as it was, and one of a machine built another way fails
|
|
24
|
+
# too. Then the machine is taken to power-on with its cartridge out,
|
|
25
|
+
# so nothing it was doing carries into the snapshot's machine.
|
|
26
|
+
def import(container, computer)
|
|
27
|
+
setup = Vice.setup(container)
|
|
28
|
+
ours = Setup.of(computer.address_bus)
|
|
29
|
+
raise FormatError, "the snapshot is of a machine with #{setup}, not #{ours}" unless setup == ours
|
|
30
|
+
|
|
31
|
+
apply(container, setup.build)
|
|
32
|
+
prepare(computer)
|
|
33
|
+
report = apply(container, computer)
|
|
34
|
+
Report.new(applied: report.applied, ignored: report.ignored + drives(computer))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Applies the modules badline reads to a machine built as the
|
|
38
|
+
# snapshot's was (setup), and returns the Report.
|
|
39
|
+
def apply(container, computer)
|
|
40
|
+
applied = []
|
|
41
|
+
ignored = []
|
|
42
|
+
READ.each do |name|
|
|
43
|
+
section = find(name, container)
|
|
44
|
+
next unless section
|
|
45
|
+
|
|
46
|
+
reason = refusal(name, section, container)
|
|
47
|
+
if reason
|
|
48
|
+
ignored << "#{section}: #{reason}, left out"
|
|
49
|
+
else
|
|
50
|
+
import_module(name, section, computer)
|
|
51
|
+
applied << section.name
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
Report.new(applied:, ignored: ignored + left_out(container, applied))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The module a snapshot has for one badline reads, under any of its
|
|
58
|
+
# names.
|
|
59
|
+
def find(name, container)
|
|
60
|
+
case name
|
|
61
|
+
when MainCPU::NAME then MainCPU.find(container)
|
|
62
|
+
when VICII::NAME then VICII.find(container)
|
|
63
|
+
else container[name]
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def prepare(computer)
|
|
68
|
+
computer.address_bus.detach_cartridge if computer.address_bus.cartridge
|
|
69
|
+
computer.power_cycle!
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Why badline leaves the module out, or nil when it reads it.
|
|
73
|
+
def refusal(name, section, container)
|
|
74
|
+
reason = if !reads?(name, section) then "badline doesn't read this version"
|
|
75
|
+
elsif name == SIDExtended::NAME && !resid?(container) then "another SID engine wrote it"
|
|
76
|
+
end
|
|
77
|
+
raise FormatError, "#{section}: #{reason}" if reason && REQUIRED.include?(name)
|
|
78
|
+
|
|
79
|
+
reason
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def reads?(name, section)
|
|
83
|
+
case name
|
|
84
|
+
when MainCPU::NAME then MainCPU.reads?(section)
|
|
85
|
+
when C64Mem::NAME then C64Mem.reads?(section)
|
|
86
|
+
when SIDRegisters::NAME then SIDRegisters.reads?(section)
|
|
87
|
+
when SIDExtended::NAME then SIDExtended.reads?(section)
|
|
88
|
+
when VICII::NAME then VICII.reads?(section)
|
|
89
|
+
else CIAs.reads?(section)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def import_module(name, section, computer)
|
|
94
|
+
case name
|
|
95
|
+
when MainCPU::NAME then MainCPU.import(section, computer)
|
|
96
|
+
when C64Mem::NAME then C64Mem.import(section, computer)
|
|
97
|
+
when SIDRegisters::NAME then SIDRegisters.import(section, computer)
|
|
98
|
+
when SIDExtended::NAME then SIDExtended.import(section, computer)
|
|
99
|
+
when VICII::NAME then VICII.import(section, computer)
|
|
100
|
+
else CIAs.import(section, computer)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# SIDEXTENDED holds the engine's own state, and badline reads reSID's.
|
|
105
|
+
def resid?(container)
|
|
106
|
+
sid = container[SIDRegisters::NAME]
|
|
107
|
+
!sid.nil? && SIDRegisters.engine(sid) == SIDRegisters::RESID
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def left_out(container, applied)
|
|
111
|
+
read = READ + [MainCPU::TRUNK_NAME, VICII::TRUNK_NAME]
|
|
112
|
+
container.sections.reject { |section| applied.include?(section.name) || read.include?(section.name) }
|
|
113
|
+
.map { |section| "#{section}: badline doesn't read this module, left out" }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def drives(computer)
|
|
117
|
+
lines = []
|
|
118
|
+
lines << "the disk mounted in device 8 stays mounted" if computer.mounted?
|
|
119
|
+
lines << "the 1541 stays on the serial bus, reset" if computer.drive1541
|
|
120
|
+
lines
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
# SIDEXTENDED 1.4 for the reSID engine: the state reSID keeps between
|
|
7
|
+
# cycles, voice by voice. badline's SID follows reSID's model, so its
|
|
8
|
+
# oscillators, noise registers and envelopes map across field by
|
|
9
|
+
# field. x64sc won't load a snapshot without this module when it runs
|
|
10
|
+
# reSID. A module another engine wrote has another layout, and isn't
|
|
11
|
+
# read.
|
|
12
|
+
#
|
|
13
|
+
# After the 32 registers, the data bus byte and its countdown, each
|
|
14
|
+
# field is written for the three voices in turn: the phase, the noise
|
|
15
|
+
# register, the envelope's rate and exponential counters, its level,
|
|
16
|
+
# state and hold-at-zero flag, its rate and exponential periods and
|
|
17
|
+
# pipeline, then the noise register's shift pipeline and reset
|
|
18
|
+
# countdown, the floating output's countdown and the pulse level. The
|
|
19
|
+
# voice mask and two bytes before it end the module.
|
|
20
|
+
module SIDExtended
|
|
21
|
+
NAME = "SIDEXTENDED"
|
|
22
|
+
MAJOR = 1
|
|
23
|
+
MINOR = 4
|
|
24
|
+
WAVEFORM_FIELDS = 6
|
|
25
|
+
ENVELOPE_FIELDS = 8
|
|
26
|
+
# What VICE's own snapshots carry for reSID's voice mask.
|
|
27
|
+
VOICE_MASK = 0xf7
|
|
28
|
+
|
|
29
|
+
# Each field's width, and where it sits among a voice's oscillator
|
|
30
|
+
# fields (Waveform#resid_fields, :waveform) or envelope fields
|
|
31
|
+
# (Envelope#resid_fields, :envelope).
|
|
32
|
+
FIELDS = [
|
|
33
|
+
[:dword, :waveform, 0], [:dword, :waveform, 1], [:word, :envelope, 0], [:word, :envelope, 1],
|
|
34
|
+
[:byte, :envelope, 2], [:byte, :envelope, 3], [:byte, :envelope, 4], [:word, :envelope, 5],
|
|
35
|
+
[:word, :envelope, 6], [:byte, :envelope, 7], [:byte, :waveform, 2], [:dword, :waveform, 3],
|
|
36
|
+
[:dword, :waveform, 4], [:word, :waveform, 5]
|
|
37
|
+
].freeze
|
|
38
|
+
|
|
39
|
+
module_function
|
|
40
|
+
|
|
41
|
+
def reads?(section) = section.major == MAJOR && section.minor == MINOR
|
|
42
|
+
|
|
43
|
+
# `computer`'s SID must have caught up.
|
|
44
|
+
def export(computer)
|
|
45
|
+
sid = computer.sid
|
|
46
|
+
fields = FieldWriter.new.bytes(Array.new(0x20) { |reg| sid.register(reg) })
|
|
47
|
+
value, ttl = sid.bus_state
|
|
48
|
+
fields.byte(value).dword(ttl)
|
|
49
|
+
voices = sid.voices.map { |voice| [voice.waveform.resid_fields, voice.envelope.resid_fields] }
|
|
50
|
+
FIELDS.each do |width, part, index|
|
|
51
|
+
voices.each { |waveform, envelope| write(fields, width, (part == :waveform ? waveform : envelope)[index]) }
|
|
52
|
+
end
|
|
53
|
+
fields.byte(0).byte(0).byte(VOICE_MASK).section(NAME, MAJOR, MINOR)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def write(fields, width, value)
|
|
57
|
+
case width
|
|
58
|
+
when :byte then fields.byte(value)
|
|
59
|
+
when :word then fields.word(value)
|
|
60
|
+
else fields.dword(value)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def import(section, computer)
|
|
65
|
+
sid = computer.sid
|
|
66
|
+
voices = sid.voices
|
|
67
|
+
fields = FieldReader.new(section).skip(0x20)
|
|
68
|
+
sid.restore_bus(fields.byte, fields.dword)
|
|
69
|
+
waveforms = Array.new(voices.length * WAVEFORM_FIELDS, 0)
|
|
70
|
+
envelopes = Array.new(voices.length * ENVELOPE_FIELDS, 0)
|
|
71
|
+
FIELDS.each do |width, part, index|
|
|
72
|
+
voices.length.times do |n|
|
|
73
|
+
if part == :waveform
|
|
74
|
+
waveforms[(n * WAVEFORM_FIELDS) + index] = value(fields, width)
|
|
75
|
+
else
|
|
76
|
+
envelopes[(n * ENVELOPE_FIELDS) + index] = value(fields, width)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
voices.each_with_index do |voice, n|
|
|
81
|
+
voice.waveform.restore_resid_fields(waveforms, n * WAVEFORM_FIELDS)
|
|
82
|
+
voice.envelope.restore_resid_fields(envelopes, n * ENVELOPE_FIELDS)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def value(fields, width)
|
|
87
|
+
case width
|
|
88
|
+
when :byte then fields.byte
|
|
89
|
+
when :word then fields.word
|
|
90
|
+
else fields.dword
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Vice
|
|
6
|
+
# SID 1.5, the module every VICE SID engine writes: the number of
|
|
7
|
+
# extra SIDs, whether sound is on, the engine, the model and the 32
|
|
8
|
+
# registers as last written. The engine's own state goes in
|
|
9
|
+
# SIDEXTENDED, which is reSID's and badline doesn't read.
|
|
10
|
+
#
|
|
11
|
+
# Read: the registers are written to the SID in order, so the voices
|
|
12
|
+
# start again from the gates as they stand, with their envelopes and
|
|
13
|
+
# oscillators from zero.
|
|
14
|
+
module SIDRegisters
|
|
15
|
+
NAME = "SID"
|
|
16
|
+
MAJOR = 1
|
|
17
|
+
MINOR = 5
|
|
18
|
+
RESID = 1
|
|
19
|
+
MODELS = %i[mos6581 mos8580].freeze
|
|
20
|
+
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
def export(computer)
|
|
24
|
+
sid = computer.sid
|
|
25
|
+
fields = FieldWriter.new.byte(0).byte(1).byte(RESID).byte(MODELS.index(sid.model))
|
|
26
|
+
fields.bytes(Array.new(0x20) { |reg| sid.register(reg) })
|
|
27
|
+
fields.section(NAME, MAJOR, MINOR)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# 1.4 added the registers as last written, which badline reads.
|
|
31
|
+
def reads?(section) = section.major == MAJOR && section.minor >= 4
|
|
32
|
+
|
|
33
|
+
def engine(section) = FieldReader.new(section).skip(2).byte
|
|
34
|
+
|
|
35
|
+
def model(section)
|
|
36
|
+
MODELS.fetch(FieldReader.new(section).skip(3).byte, :mos6581)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def import(section, computer)
|
|
40
|
+
registers = FieldReader.new(section).skip(4).bytes(0x20)
|
|
41
|
+
registers.first(0x19).each_with_index { |value, reg| computer.sid.poke(0xd400 + reg, value) }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|