badline 0.5.1 → 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 +12 -0
- data/README.md +58 -0
- 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 +8 -0
- data/lib/badline/computer/attachments.rb +132 -0
- data/lib/badline/computer/saved_state.rb +116 -0
- data/lib/badline/computer.rb +7 -30
- 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 +13 -0
- data/lib/badline/drive1541/disk.rb +7 -2
- data/lib/badline/drive1541/disk_state.rb +80 -0
- data/lib/badline/drive1541/mechanism.rb +2 -0
- data/lib/badline/drive1541/mechanism_state.rb +67 -0
- data/lib/badline/drive1541/rotation.rb +10 -0
- data/lib/badline/drive1541/serial_port.rb +10 -0
- data/lib/badline/drive1541.rb +34 -0
- 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/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 +8 -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/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 +13 -0
- data/lib/badline/via/timer2.rb +18 -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 +35 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CPU
|
|
5
|
+
# The CPU's state for a snapshot: the registers, the interrupt lines and
|
|
6
|
+
# their pipeline, and the step of the instruction in flight, named by
|
|
7
|
+
# where its plan comes from.
|
|
8
|
+
module SavedState
|
|
9
|
+
# Writes everything the CPU holds, down to the step of the instruction
|
|
10
|
+
# it is in, for load_state to put back. The interrupt lines, BA stalls
|
|
11
|
+
# and SO are state too; the traps and the debug flag belong to the host.
|
|
12
|
+
def save_state(out)
|
|
13
|
+
out.marker("CPU")
|
|
14
|
+
out.int(@program_counter).int(@stack_pointer).int(@a).int(@x).int(@y).int(@status.value)
|
|
15
|
+
[@irq, @nmi, @irq_sample, @irq_pending, @nmi_sample, @nmi_pending, @skip_poll, @boundary_irq,
|
|
16
|
+
@boundary_nmi, @boundary_crossed, @brk, @pending_write, @so_high, @optional_dummy,
|
|
17
|
+
@branch_taken].each { |flag| out.boolean(flag) }
|
|
18
|
+
out.optional_int(@interrupt).optional_int(@stalled_at).int(@cycles).int(@instructions)
|
|
19
|
+
out.int(plan_source).int(@index).int(OPERATIONS.index(@operation) || -1)
|
|
20
|
+
[@address, @dummy_address, @value, @rmw_result, @pointer].each { |register| out.optional_int(register) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def load_state(input)
|
|
24
|
+
input.marker("CPU")
|
|
25
|
+
@program_counter = input.int
|
|
26
|
+
@stack_pointer = input.int
|
|
27
|
+
@a = input.int
|
|
28
|
+
@x = input.int
|
|
29
|
+
@y = input.int
|
|
30
|
+
@status.value = input.int
|
|
31
|
+
load_flags(input)
|
|
32
|
+
@interrupt = input.optional_int
|
|
33
|
+
@stalled_at = input.optional_int
|
|
34
|
+
@cycles = input.int
|
|
35
|
+
@instructions = input.int
|
|
36
|
+
load_plan(input.int)
|
|
37
|
+
@index = input.int
|
|
38
|
+
operation = input.int
|
|
39
|
+
@operation = operation.negative? ? nil : OPERATIONS.fetch(operation)
|
|
40
|
+
load_working_registers(input)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Drops the instruction or interrupt sequence in flight and the
|
|
44
|
+
# interrupt lines' pipeline, so the next cycle fetches an opcode at the
|
|
45
|
+
# program counter, and sets the cycle count. A snapshot taken between
|
|
46
|
+
# instructions, as VICE's are, restores through here.
|
|
47
|
+
def resume(cycles)
|
|
48
|
+
@irq_sample = @irq_pending = false
|
|
49
|
+
@nmi_sample = @nmi_pending = false
|
|
50
|
+
@skip_poll = false
|
|
51
|
+
@interrupt = nil
|
|
52
|
+
@brk = false
|
|
53
|
+
@stalled_at = nil
|
|
54
|
+
@cycles = cycles
|
|
55
|
+
end_sequence
|
|
56
|
+
@pending_write = false
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
# Where the plan running comes from: an opcode's microcode, or FETCH,
|
|
62
|
+
# INTERRUPT or JAMMED below it.
|
|
63
|
+
def plan_source
|
|
64
|
+
return FETCH if @plan.equal?(FETCH_PLAN)
|
|
65
|
+
return INTERRUPT if @plan.equal?(INTERRUPT_PLAN)
|
|
66
|
+
return JAMMED if @plan.equal?(JAMMED_PLAN)
|
|
67
|
+
|
|
68
|
+
MICROCODE.index { |micro| micro.plan.equal?(@plan) && micro.operation == @operation } ||
|
|
69
|
+
MICROCODE.index { |micro| micro.plan.equal?(@plan) }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def load_plan(source)
|
|
73
|
+
case source
|
|
74
|
+
when FETCH then use_plan(FETCH_PLAN, FETCH_WRITES)
|
|
75
|
+
when INTERRUPT then use_plan(INTERRUPT_PLAN, INTERRUPT_WRITES)
|
|
76
|
+
when JAMMED then use_plan(JAMMED_PLAN, JAMMED_WRITES)
|
|
77
|
+
else
|
|
78
|
+
micro = MICROCODE.fetch(source)
|
|
79
|
+
use_plan(micro.plan, micro.writes)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def use_plan(plan, writes)
|
|
84
|
+
@plan = plan
|
|
85
|
+
@writes = writes
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def load_flags(input)
|
|
89
|
+
@irq = input.boolean?
|
|
90
|
+
@nmi = input.boolean?
|
|
91
|
+
@irq_sample = input.boolean?
|
|
92
|
+
@irq_pending = input.boolean?
|
|
93
|
+
@nmi_sample = input.boolean?
|
|
94
|
+
@nmi_pending = input.boolean?
|
|
95
|
+
@skip_poll = input.boolean?
|
|
96
|
+
@boundary_irq = input.boolean?
|
|
97
|
+
@boundary_nmi = input.boolean?
|
|
98
|
+
@boundary_crossed = input.boolean?
|
|
99
|
+
@brk = input.boolean?
|
|
100
|
+
@pending_write = input.boolean?
|
|
101
|
+
@so_high = input.boolean?
|
|
102
|
+
@optional_dummy = input.boolean?
|
|
103
|
+
@branch_taken = input.boolean?
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def load_working_registers(input)
|
|
107
|
+
@address = input.optional_int
|
|
108
|
+
@dummy_address = input.optional_int
|
|
109
|
+
@value = input.optional_int
|
|
110
|
+
@rmw_result = input.optional_int
|
|
111
|
+
@pointer = input.optional_int
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
data/lib/badline/cpu.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "badline/cpu/microcode"
|
|
|
4
4
|
require "badline/cpu/addressing"
|
|
5
5
|
require "badline/cpu/operations"
|
|
6
6
|
require "badline/cpu/stack_operations"
|
|
7
|
+
require "badline/cpu/saved_state"
|
|
7
8
|
|
|
8
9
|
module Badline
|
|
9
10
|
class CPU
|
|
@@ -16,6 +17,7 @@ module Badline
|
|
|
16
17
|
include Addressing
|
|
17
18
|
include Operations
|
|
18
19
|
include StackOperations
|
|
20
|
+
include SavedState
|
|
19
21
|
|
|
20
22
|
attr_reader :memory, :instructions, :boundary_crossed, :cycles, :pending_write
|
|
21
23
|
attr_accessor :program_counter, :stack_pointer, :status, :a, :x, :y, :nmi, :irq
|
data/lib/badline/datasette.rb
CHANGED
|
@@ -54,6 +54,33 @@ module Badline
|
|
|
54
54
|
|
|
55
55
|
def running? = @playing && @motor && !@tape.nil?
|
|
56
56
|
|
|
57
|
+
# The keys, the motor, the countdown to the next pulse and the tape: its
|
|
58
|
+
# path and how far it has played.
|
|
59
|
+
def save_state(out)
|
|
60
|
+
out.marker("DATASETTE")
|
|
61
|
+
out.boolean(@playing).boolean(@motor).int(@countdown).boolean(!@tape.nil?)
|
|
62
|
+
return unless @tape
|
|
63
|
+
|
|
64
|
+
out.string(@tape.path).int(@tape.position)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# A tape from another file goes in from its path, and a detached
|
|
68
|
+
# reader leaves the tape out. The sense and flag handlers don't fire.
|
|
69
|
+
def load_state(input)
|
|
70
|
+
input.marker("DATASETTE")
|
|
71
|
+
@playing = input.boolean?
|
|
72
|
+
@motor = input.boolean?
|
|
73
|
+
@countdown = input.int
|
|
74
|
+
return @tape = nil unless input.boolean?
|
|
75
|
+
|
|
76
|
+
path = input.string
|
|
77
|
+
position = input.int
|
|
78
|
+
return @tape = nil if input.detached?
|
|
79
|
+
|
|
80
|
+
@tape = Storage::TAP.new(path) unless @tape&.path&.b == path
|
|
81
|
+
@tape.position = position
|
|
82
|
+
end
|
|
83
|
+
|
|
57
84
|
def cycle!
|
|
58
85
|
return unless running?
|
|
59
86
|
|
|
@@ -94,6 +94,19 @@ module Badline
|
|
|
94
94
|
watching!
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
+
# The last byte on the data bus and the RAM. What a watched stretch
|
|
98
|
+
# touched is Idle's, and a restored bus isn't watching.
|
|
99
|
+
def save_state(out)
|
|
100
|
+
out.int(@data)
|
|
101
|
+
@ram.save_state(out)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def load_state(input)
|
|
105
|
+
@data = input.int
|
|
106
|
+
@ram.load_state(input)
|
|
107
|
+
@watching = false
|
|
108
|
+
end
|
|
109
|
+
|
|
97
110
|
def peek(addr)
|
|
98
111
|
return @data = @rom.peek(addr | 0x4000) if addr >= 0x8000
|
|
99
112
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "badline/drive1541/sector_reader"
|
|
4
|
+
require "badline/drive1541/disk_state"
|
|
4
5
|
|
|
5
6
|
module Badline
|
|
6
7
|
class Drive1541
|
|
@@ -16,6 +17,8 @@ module Badline
|
|
|
16
17
|
# host file. The disk is write-protected when the image won't take
|
|
17
18
|
# writes.
|
|
18
19
|
class Disk
|
|
20
|
+
include State
|
|
21
|
+
|
|
19
22
|
MAX_HALF_TRACK = 84
|
|
20
23
|
|
|
21
24
|
# GCR bytes around a track in each speed zone: 200 ms of rotation at
|
|
@@ -52,9 +55,9 @@ module Badline
|
|
|
52
55
|
# anything else as a .d64, formatted. `read_only` opens the image
|
|
53
56
|
# write-protected.
|
|
54
57
|
def self.open(path, read_only: false)
|
|
55
|
-
return from_g64(Storage::G64Image.new(path, read_only:))
|
|
58
|
+
return from_g64(Storage::G64Image.new(path, read_only:)).opened(path, read_only) if State.g64?(path)
|
|
56
59
|
|
|
57
|
-
from_d64(Storage::D64Image.new(path, read_only:))
|
|
60
|
+
from_d64(Storage::D64Image.new(path, read_only:)).opened(path, read_only)
|
|
58
61
|
end
|
|
59
62
|
|
|
60
63
|
# A disk from a G64 image, each half track as the image stores it,
|
|
@@ -145,6 +148,8 @@ module Badline
|
|
|
145
148
|
@image = image
|
|
146
149
|
@tracks = Array.new(MAX_HALF_TRACK + 1)
|
|
147
150
|
@written = {}
|
|
151
|
+
@path = nil
|
|
152
|
+
@read_only = false
|
|
148
153
|
end
|
|
149
154
|
|
|
150
155
|
# The Track at the half track, or nil where there's no data.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Drive1541
|
|
5
|
+
class Disk
|
|
6
|
+
# Saving and restoring a Disk for a snapshot: the image by its path,
|
|
7
|
+
# and every half track as the head sees it, with those written since
|
|
8
|
+
# the last flush. The image itself is read again from its host file.
|
|
9
|
+
module State
|
|
10
|
+
# The image's path, expanded, and whether it was opened read-only,
|
|
11
|
+
# for a disk from Disk.open. Nil for a disk made another way.
|
|
12
|
+
attr_reader :path, :read_only
|
|
13
|
+
|
|
14
|
+
# A .g64 image by its name, and anything else a .d64.
|
|
15
|
+
def self.g64?(path) = File.extname(path).casecmp?(".g64")
|
|
16
|
+
|
|
17
|
+
# A disk for the image at the path, its tracks left for the state.
|
|
18
|
+
def self.reopen(path, read_only)
|
|
19
|
+
if g64?(path)
|
|
20
|
+
Disk.new(Storage::G64Image.new(path, read_only:)).opened(path, read_only)
|
|
21
|
+
else
|
|
22
|
+
Disk.new(Storage::D64Image.new(path, read_only:)).opened(path, read_only)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# The disk a state from save_state describes: +current+ when it was
|
|
27
|
+
# opened from the same image the same way, and otherwise a disk for
|
|
28
|
+
# the image at the path the state names, or one without an image,
|
|
29
|
+
# as a detached reader always gets. Either way its tracks come from
|
|
30
|
+
# the state.
|
|
31
|
+
def self.load(input, current)
|
|
32
|
+
path = input.optional_string
|
|
33
|
+
read_only = input.boolean?
|
|
34
|
+
disk = current if current && !path.nil? && current.path == path && current.read_only == read_only
|
|
35
|
+
disk ||= path && !input.detached? ? reopen(path, read_only) : Disk.new
|
|
36
|
+
disk.load_tracks(input)
|
|
37
|
+
disk
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Notes the image's path and how it was opened.
|
|
41
|
+
def opened(path, read_only)
|
|
42
|
+
@path = File.expand_path(path)
|
|
43
|
+
@read_only = read_only
|
|
44
|
+
self
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def save_state(out)
|
|
48
|
+
out.optional_string(@path).boolean(@read_only)
|
|
49
|
+
@tracks.each do |track|
|
|
50
|
+
out.boolean(!track.nil?)
|
|
51
|
+
save_track(track, out) if track
|
|
52
|
+
end
|
|
53
|
+
out.ints(@written.keys)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def load_tracks(input)
|
|
57
|
+
@tracks.each_index do |half_track|
|
|
58
|
+
@tracks[half_track] = input.boolean? ? load_track(input) : nil
|
|
59
|
+
end
|
|
60
|
+
@written.clear
|
|
61
|
+
input.ints.each { |half_track| @written[half_track] = true }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def save_track(track, out)
|
|
67
|
+
out.int(track.zone).boolean(!track.speeds.nil?)
|
|
68
|
+
out.ints(track.speeds) if track.speeds
|
|
69
|
+
out.blob(track.bytes)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def load_track(input)
|
|
73
|
+
zone = input.int
|
|
74
|
+
speeds = input.boolean? ? input.ints : nil
|
|
75
|
+
Track.new(input.blob, zone, speeds)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "badline/drive1541/rotation"
|
|
4
|
+
require "badline/drive1541/mechanism_state"
|
|
4
5
|
|
|
5
6
|
module Badline
|
|
6
7
|
class Drive1541
|
|
@@ -65,6 +66,7 @@ module Badline
|
|
|
65
66
|
# half track after it.
|
|
66
67
|
class Mechanism
|
|
67
68
|
include Rotation
|
|
69
|
+
include MechanismState
|
|
68
70
|
|
|
69
71
|
MOTOR = 0x04
|
|
70
72
|
LED = 0x08
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Drive1541
|
|
5
|
+
# Saving and restoring the Mechanism for a snapshot.
|
|
6
|
+
module MechanismState
|
|
7
|
+
# The disk (Disk#save_state) and everything the head, the motor, the
|
|
8
|
+
# stepper and the read and write electronics hold, down to the cell
|
|
9
|
+
# under the head and the read clock's phase.
|
|
10
|
+
def save_state(out)
|
|
11
|
+
out.boolean(!@disk.nil?)
|
|
12
|
+
@disk&.save_state(out)
|
|
13
|
+
[@half_track, @slip, @zone, @clock, @time, @clock_at, @clocks, @shift, @ones, @bits, @write_shift,
|
|
14
|
+
@index, @mask, @cell_end, @width, @next].each { |value| out.int(value) }
|
|
15
|
+
[@motor, @led, @sync, @byte_ready, @writing, @write_gate, @protected, @track_written].each do |flag|
|
|
16
|
+
out.boolean(flag)
|
|
17
|
+
end
|
|
18
|
+
out.optional_int(@write_index)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Puts the state back, reusing the disk in when it's the one the state
|
|
22
|
+
# names (Disk::State.load). A disk taken out is flushed, as insert
|
|
23
|
+
# does.
|
|
24
|
+
def load_state(input)
|
|
25
|
+
disk = input.boolean? ? Disk::State.load(input, @disk) : nil
|
|
26
|
+
flush unless disk.equal?(@disk)
|
|
27
|
+
@disk = disk
|
|
28
|
+
load_head(input)
|
|
29
|
+
load_flags(input)
|
|
30
|
+
@write_index = input.optional_int
|
|
31
|
+
head_on_track
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def load_head(input)
|
|
37
|
+
@half_track = input.int
|
|
38
|
+
@slip = input.int
|
|
39
|
+
@zone = input.int
|
|
40
|
+
@clock = input.int
|
|
41
|
+
@time = input.int
|
|
42
|
+
@clock_at = input.int
|
|
43
|
+
@clocks = input.int
|
|
44
|
+
@shift = input.int
|
|
45
|
+
@ones = input.int
|
|
46
|
+
@bits = input.int
|
|
47
|
+
@write_shift = input.int
|
|
48
|
+
@index = input.int
|
|
49
|
+
@mask = input.int
|
|
50
|
+
@cell_end = input.int
|
|
51
|
+
@width = input.int
|
|
52
|
+
@next = input.int
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def load_flags(input)
|
|
56
|
+
@motor = input.boolean?
|
|
57
|
+
@led = input.boolean?
|
|
58
|
+
@sync = input.boolean?
|
|
59
|
+
@byte_ready = input.boolean?
|
|
60
|
+
@writing = input.boolean?
|
|
61
|
+
@write_gate = input.boolean?
|
|
62
|
+
@protected = input.boolean?
|
|
63
|
+
@track_written = input.boolean?
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -92,6 +92,16 @@ module Badline
|
|
|
92
92
|
@writing ? write_bit(at) : read_bit(one)
|
|
93
93
|
end
|
|
94
94
|
|
|
95
|
+
# The track under the head and its layout, leaving the head's place
|
|
96
|
+
# on it as it stands.
|
|
97
|
+
def head_on_track
|
|
98
|
+
@track = @disk&.track(@half_track) || BLANK
|
|
99
|
+
@bytes = @track.bytes
|
|
100
|
+
@widths = @track.widths
|
|
101
|
+
@length = @track.length
|
|
102
|
+
@last = @length - 1
|
|
103
|
+
end
|
|
104
|
+
|
|
95
105
|
# The track under the head, from the cell that's under it +time+
|
|
96
106
|
# into the turn.
|
|
97
107
|
def load_track(time = @time)
|
|
@@ -44,6 +44,16 @@ module Badline
|
|
|
44
44
|
@host = @bus ? @bus.host_lines : 0
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
# The C64's lines as last latched. The jumpers and the bus are the
|
|
48
|
+
# drive's wiring.
|
|
49
|
+
def save_state(out)
|
|
50
|
+
out.int(@host)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def load_state(input)
|
|
54
|
+
@host = input.int
|
|
55
|
+
end
|
|
56
|
+
|
|
47
57
|
# Whether the C64 has moved ATN since the last latch_host.
|
|
48
58
|
def atn_moved?
|
|
49
59
|
lines = @bus ? @bus.host_lines : 0
|
data/lib/badline/drive1541.rb
CHANGED
|
@@ -180,6 +180,40 @@ module Badline
|
|
|
180
180
|
step
|
|
181
181
|
end
|
|
182
182
|
|
|
183
|
+
# The drive's whole state: its clock phase, the CPU, RAM, both VIAs,
|
|
184
|
+
# the serial port's latch of the C64's lines and the mechanism with its
|
|
185
|
+
# disk. The drive settles first, so it is awake and not recording a
|
|
186
|
+
# pass, and a restored drive starts the same way, its orbits forgotten
|
|
187
|
+
# from the state it takes: Idle's and Orbit's bookkeeping
|
|
188
|
+
# isn't stored. The device number, the host's clock rate and whether
|
|
189
|
+
# the drive may sleep are the machine's wiring.
|
|
190
|
+
def save_state(out)
|
|
191
|
+
settle!
|
|
192
|
+
out.marker("DRIVE1541")
|
|
193
|
+
out.int(@phase).int(@cycles).boolean(@so_pending)
|
|
194
|
+
@serial_port.save_state(out)
|
|
195
|
+
@via1.save_state(out)
|
|
196
|
+
@via2.save_state(out)
|
|
197
|
+
@bus.save_state(out)
|
|
198
|
+
@cpu.save_state(out)
|
|
199
|
+
@mechanism.save_state(out)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def load_state(input)
|
|
203
|
+
settle!
|
|
204
|
+
input.marker("DRIVE1541")
|
|
205
|
+
@phase = input.int
|
|
206
|
+
@cycles = input.int
|
|
207
|
+
@so_pending = input.boolean?
|
|
208
|
+
@serial_port.load_state(input)
|
|
209
|
+
@via1.load_state(input)
|
|
210
|
+
@via2.load_state(input)
|
|
211
|
+
@bus.load_state(input)
|
|
212
|
+
@cpu.load_state(input)
|
|
213
|
+
@mechanism.load_state(input)
|
|
214
|
+
forget_orbits
|
|
215
|
+
end
|
|
216
|
+
|
|
183
217
|
def inspect
|
|
184
218
|
"#<#{self.class.name} cycles=#{cycles} cpu=(#{cpu.inspect})>"
|
|
185
219
|
end
|
|
@@ -6,6 +6,9 @@ module Badline
|
|
|
6
6
|
TITLE = "Badline"
|
|
7
7
|
TOGGLE_SYM = SDL::KEY_TAB
|
|
8
8
|
MUTE_SYM = SDL::KEY_F10
|
|
9
|
+
SAVE_SYM = SDL::KEY_F11
|
|
10
|
+
RESTORE_SYM = SDL::KEY_F12
|
|
11
|
+
HOST_SYMS = [TOGGLE_SYM, MUTE_SYM, SAVE_SYM, RESTORE_SYM].freeze
|
|
9
12
|
REVERSE_MOD = SDL::KMOD_SHIFT
|
|
10
13
|
|
|
11
14
|
SHARED_KEYS = %i[cursor_up cursor_left cursor_h cursor_v space w a s d lshift].freeze
|
|
@@ -34,14 +37,12 @@ module Badline
|
|
|
34
37
|
# options (autostart:, song:, disk:) go to Media.attach.
|
|
35
38
|
def initialize(media_path: nil, machine: {}, sound: false, verbose: false, **media)
|
|
36
39
|
@verbose = verbose
|
|
37
|
-
@
|
|
38
|
-
|
|
39
|
-
puts Media.attach(@computer, media_path, **media) if media_path
|
|
40
|
+
@snapshots = Snapshots.new
|
|
41
|
+
@computer = @snapshots.boot(media_path, machine, media)
|
|
40
42
|
|
|
41
43
|
@mode = :keyboard
|
|
42
44
|
@pot_device = nil
|
|
43
|
-
@panes =
|
|
44
|
-
@panes << DriveLedPane.new(@computer.drive1541, @panes.first) if @computer.drive1541
|
|
45
|
+
@panes = panes
|
|
45
46
|
@stream = open_stream if sound
|
|
46
47
|
@paced = ENV["NOVSYNC"].nil?
|
|
47
48
|
@window = Window.new(
|
|
@@ -94,8 +95,7 @@ module Badline
|
|
|
94
95
|
end
|
|
95
96
|
|
|
96
97
|
def handle_key_down(event)
|
|
97
|
-
return
|
|
98
|
-
return toggle_mute if event.sym == MUTE_SYM
|
|
98
|
+
return host_key(event) if HOST_SYMS.include?(event.sym)
|
|
99
99
|
|
|
100
100
|
port, dir = JoyMap.parse(event) if @mode == :joystick
|
|
101
101
|
if port
|
|
@@ -105,6 +105,16 @@ module Badline
|
|
|
105
105
|
end
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
+
# Tab, F10, F11 and F12 drive the front end, not the machine.
|
|
109
|
+
def host_key(event)
|
|
110
|
+
case event.sym
|
|
111
|
+
when TOGGLE_SYM then cycle_mode(event.mod.anybits?(REVERSE_MOD) ? -1 : 1)
|
|
112
|
+
when MUTE_SYM then toggle_mute
|
|
113
|
+
when SAVE_SYM then @snapshots.save(@computer) unless event.repeat
|
|
114
|
+
else restore_snapshot unless event.repeat
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
108
118
|
# RESTORE isn't in the key matrix, so it goes to the machine instead,
|
|
109
119
|
# once per press as the real key's one-shot fires.
|
|
110
120
|
def press_key(key, repeat)
|
|
@@ -115,7 +125,7 @@ module Badline
|
|
|
115
125
|
end
|
|
116
126
|
|
|
117
127
|
def handle_key_up(event)
|
|
118
|
-
return if
|
|
128
|
+
return if HOST_SYMS.include?(event.sym)
|
|
119
129
|
|
|
120
130
|
port, dir = JoyMap.parse(event) if @mode == :joystick
|
|
121
131
|
if port
|
|
@@ -155,6 +165,29 @@ module Badline
|
|
|
155
165
|
update_title
|
|
156
166
|
end
|
|
157
167
|
|
|
168
|
+
# Runs the restored machine in place of the one before: the screen,
|
|
169
|
+
# the drive LED, the gamepads and the sound go over to it. The input
|
|
170
|
+
# mode stays the host's, so a mouse or paddles go back in their port.
|
|
171
|
+
def restore_snapshot
|
|
172
|
+
computer = @snapshots.restore
|
|
173
|
+
return unless computer
|
|
174
|
+
|
|
175
|
+
release_inputs
|
|
176
|
+
@computer = computer
|
|
177
|
+
@panes = panes
|
|
178
|
+
@gamepads.computer = computer
|
|
179
|
+
@stream&.sid = computer.sid
|
|
180
|
+
attach_pot_device if POT_DEVICES.key?(@mode)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# The screen, and the drive LED in its border when a true drive is
|
|
184
|
+
# plugged in.
|
|
185
|
+
def panes
|
|
186
|
+
screen = ScreenPane.new(@computer)
|
|
187
|
+
drive = @computer.drive1541
|
|
188
|
+
drive ? [screen, DriveLedPane.new(drive, screen)] : [screen]
|
|
189
|
+
end
|
|
190
|
+
|
|
158
191
|
def update_title
|
|
159
192
|
tags = [MODES[@mode], @stream&.muted? && "MUTED"].select(&:itself)
|
|
160
193
|
@window.title = [TITLE, *tags.map { |tag| "[#{tag}]" }].join(" ")
|
|
@@ -169,12 +202,6 @@ module Badline
|
|
|
169
202
|
warn "badline: no sound, can't open the audio device: #{e.message}"
|
|
170
203
|
end
|
|
171
204
|
|
|
172
|
-
def machine_options(machine, media_path)
|
|
173
|
-
options = { sid_model: machine[:sid_model] || Media.sid_model(media_path), **machine.slice(:reu) }
|
|
174
|
-
options[:region] = Region::NTSC if machine[:ntsc]
|
|
175
|
-
options
|
|
176
|
-
end
|
|
177
|
-
|
|
178
205
|
def fit_frame
|
|
179
206
|
rate = @window.refresh_rate
|
|
180
207
|
clock_hz = @computer.region.clock_hz
|
data/lib/badline/gui/gamepads.rb
CHANGED
|
@@ -53,6 +53,13 @@ module Badline
|
|
|
53
53
|
.reject(&:null?)
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
+
# Wires the controllers to another machine's joysticks, which take
|
|
57
|
+
# what they hold on the next poll.
|
|
58
|
+
def computer=(computer)
|
|
59
|
+
@computer = computer
|
|
60
|
+
@pressed.map! { [] }
|
|
61
|
+
end
|
|
62
|
+
|
|
56
63
|
def poll
|
|
57
64
|
@controllers.each_with_index { |controller, index| apply(controller, index) }
|
|
58
65
|
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module GUI
|
|
5
|
+
# The window's snapshots: a .vsf opened as the media, F11 saving the
|
|
6
|
+
# machine to a new badline-<date>-<time>.vsf in the working directory,
|
|
7
|
+
# and F12 going back to the snapshot last saved or opened.
|
|
8
|
+
class Snapshots
|
|
9
|
+
def self.snapshot?(path) = !path.nil? && File.extname(path).casecmp?(".vsf")
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@last = nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# The machine the window starts with: for a .vsf the machine it holds,
|
|
16
|
+
# built with that machine's chip models, which F12 goes back to, and
|
|
17
|
+
# otherwise one built from the machine options (Application.new) with
|
|
18
|
+
# the media attached.
|
|
19
|
+
def boot(media_path, machine, media)
|
|
20
|
+
return load(media_path) if Snapshots.snapshot?(media_path)
|
|
21
|
+
|
|
22
|
+
Computer.new(**machine_options(machine, media_path)).tap do |computer|
|
|
23
|
+
Media::TrueDrive.plug(computer) if machine[:true_drive]
|
|
24
|
+
puts Media.attach(computer, media_path, **media) if media_path
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# A new machine, built as the snapshot's was and restored from it.
|
|
29
|
+
def load(path)
|
|
30
|
+
computer = Snapshot.load(path) { |line| puts line }
|
|
31
|
+
@last = path
|
|
32
|
+
puts "Restored #{path}"
|
|
33
|
+
computer
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Saves the machine, and warns when it can't.
|
|
37
|
+
def save(computer)
|
|
38
|
+
path = "badline-#{Time.now.strftime('%Y%m%d-%H%M%S')}.vsf"
|
|
39
|
+
computer.save_snapshot(path)
|
|
40
|
+
@last = path
|
|
41
|
+
puts "Saved #{path}, F12 restores it"
|
|
42
|
+
rescue Snapshot::FormatError, SystemCallError => e
|
|
43
|
+
warn "badline: #{path}: #{e.message}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# A new machine restored from the snapshot last saved or opened, or
|
|
47
|
+
# nil when there is none or it fails, which it warns about.
|
|
48
|
+
def restore
|
|
49
|
+
return load(@last) if @last
|
|
50
|
+
|
|
51
|
+
puts "No snapshot to restore, F11 saves one"
|
|
52
|
+
nil
|
|
53
|
+
rescue Snapshot::FormatError, SystemCallError => e
|
|
54
|
+
warn "badline: #{@last}: #{e.message}"
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def machine_options(machine, media_path)
|
|
61
|
+
options = { sid_model: machine[:sid_model] || Media.sid_model(media_path), **machine.slice(:reu) }
|
|
62
|
+
options[:region] = Region::NTSC if machine[:ntsc]
|
|
63
|
+
options
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|