badline 0.12.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/badline/address_bus/saved_state.rb +4 -10
- data/lib/badline/address_bus.rb +8 -27
- data/lib/badline/audio/bare_player.rb +1 -1
- data/lib/badline/c128/bus.rb +13 -35
- data/lib/badline/c128/bus_state.rb +5 -5
- data/lib/badline/c128.rb +5 -64
- data/lib/badline/computer/attachments.rb +16 -45
- data/lib/badline/computer/common.rb +34 -0
- data/lib/badline/computer/kernal_traps.rb +65 -5
- data/lib/badline/computer/true_drives.rb +12 -23
- data/lib/badline/computer.rb +5 -68
- data/lib/badline/core.rb +2 -2
- data/lib/badline/cpu_port.rb +112 -0
- data/lib/badline/drive1541/disk.rb +14 -17
- data/lib/badline/drive1541/disk_state.rb +7 -15
- data/lib/badline/drive1581/wd1772.rb +1 -1
- data/lib/badline/frontend/boot.rb +8 -59
- data/lib/badline/kernal_trap/{file.rb → file_routine.rb} +1 -1
- data/lib/badline/kernal_trap/load.rb +1 -1
- data/lib/badline/kernal_trap/save.rb +1 -1
- data/lib/badline/kernal_trap.rb +1 -1
- data/lib/badline/machine/clocking.rb +50 -0
- data/lib/badline/machine.rb +58 -2
- data/lib/badline/media/boot_disk.rb +2 -2
- data/lib/badline/media/disk_list.rb +3 -3
- data/lib/badline/media/disk_set.rb +1 -1
- data/lib/badline/media/extensions.rb +33 -0
- data/lib/badline/media/program.rb +28 -0
- data/lib/badline/media/true_drive.rb +3 -2
- data/lib/badline/media/tune.rb +35 -0
- data/lib/badline/media/vic20_cartridge.rb +1 -7
- data/lib/badline/media/vic20_media.rb +38 -80
- data/lib/badline/media.rb +82 -79
- data/lib/badline/options/event.rb +3 -1
- data/lib/badline/sid.rb +1 -1
- data/lib/badline/snapshot/machine_state.rb +9 -1
- data/lib/badline/snapshot/state.rb +5 -4
- data/lib/badline/snapshot.rb +11 -9
- data/lib/badline/storage/d64_image.rb +15 -7
- data/lib/badline/storage/disk_image/writing.rb +2 -2
- data/lib/badline/storage/disk_image.rb +1 -1
- data/lib/badline/storage/host_directory.rb +1 -1
- data/lib/badline/storage/t64.rb +1 -1
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic/bank.rb +1 -1
- data/lib/badline/vic/border_mask.rb +1 -1
- data/lib/badline/vic/collisions.rb +1 -1
- data/lib/badline/vic/color_patches.rb +1 -1
- data/lib/badline/vic/display_state.rb +1 -1
- data/lib/badline/vic/graphics_mode.rb +1 -1
- data/lib/badline/vic/graphics_shifter.rb +1 -1
- data/lib/badline/vic/register_log.rb +1 -1
- data/lib/badline/vic/registers.rb +1 -1
- data/lib/badline/vic/sequencer.rb +1 -1
- data/lib/badline/vic/sequencer_output.rb +1 -1
- data/lib/badline/vic/sequencer_state.rb +1 -1
- data/lib/badline/vic/sprite/internal_bus.rb +1 -1
- data/lib/badline/vic/sprite/saved_state.rb +1 -1
- data/lib/badline/vic/sprite/shifter.rb +1 -1
- data/lib/badline/vic/sprite/timing.rb +1 -1
- data/lib/badline/vic/sprite.rb +1 -1
- data/lib/badline/vic/sprites.rb +1 -1
- data/lib/badline/vic/sprites_state.rb +1 -1
- data/lib/badline/vic.rb +6 -5
- data/lib/badline/vic20/attachments.rb +10 -80
- data/lib/badline/vic20/saved_state.rb +0 -30
- data/lib/badline/vic20.rb +1 -37
- metadata +8 -3
- data/lib/badline/cycleable.rb +0 -38
|
@@ -2,23 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
class Computer
|
|
5
|
-
# The true drives on the
|
|
6
|
-
# (Drive1581::Slot), each in a field of its own,
|
|
7
|
-
#
|
|
5
|
+
# The true drives on the machine's serial bus: a Drive1541, and a
|
|
6
|
+
# Drive1581 (Drive1581::Slot), each in a field of its own. Computer,
|
|
7
|
+
# Vic20 and C128 each include it.
|
|
8
8
|
module TrueDrives
|
|
9
9
|
include Drive1581::Slot
|
|
10
10
|
|
|
11
|
-
# Plugs in a Drive1541, which then runs alongside the
|
|
12
|
-
# clock and talks to it over the serial bus. The serial traps stop
|
|
11
|
+
# Plugs in a Drive1541, which then runs alongside the machine on its
|
|
12
|
+
# own clock and talks to it over the serial bus. The serial traps stop
|
|
13
13
|
# answering the drive's device number, so the KERNAL's TALK, LISTEN and
|
|
14
14
|
# byte transfers reach the drive. The LOAD and SAVE traps stay, and
|
|
15
15
|
# still serve a mounted image.
|
|
16
16
|
def attach_drive1541(drive)
|
|
17
17
|
@iec_bus.detach(@drive1541) if @drive1541
|
|
18
|
-
drive.host_clock_hz =
|
|
18
|
+
drive.host_clock_hz = timing.clock_hz
|
|
19
19
|
@drive1541 = drive
|
|
20
|
-
drive.connect(iec_bus)
|
|
20
|
+
drive.connect(@iec_bus)
|
|
21
21
|
@serial_trap&.device = serial_trap_device
|
|
22
|
+
drive1541_attached
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
# The true drive on the serial bus: the Drive1581 ahead of the
|
|
@@ -50,23 +51,11 @@ module Badline
|
|
|
50
51
|
@drive1541&.device == device || drive1581_on_device8? ? nil : device
|
|
51
52
|
end
|
|
52
53
|
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
def
|
|
56
|
-
out.boolean(true).boolean(!@drive1541.nil?)
|
|
57
|
-
if @drive1541
|
|
58
|
-
out.int(@drive1541.device)
|
|
59
|
-
@drive1541.save_state(out)
|
|
60
|
-
end
|
|
61
|
-
save_drive1581(out)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def load_serial_bus(input)
|
|
65
|
-
input.boolean?
|
|
66
|
-
load_drive1541(input)
|
|
67
|
-
load_drive1581(input)
|
|
68
|
-
end
|
|
54
|
+
# What the machine does as a 1541 goes on its bus. The VIC-20 waits
|
|
55
|
+
# for its DOS to boot before typing.
|
|
56
|
+
def drive1541_attached = nil
|
|
69
57
|
|
|
58
|
+
# A true 1541 on the serial bus, with its device number.
|
|
70
59
|
def load_drive1541(input)
|
|
71
60
|
return detach_drive1541 unless input.boolean?
|
|
72
61
|
|
data/lib/badline/computer.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "badline/computer/common"
|
|
3
4
|
require "badline/computer/true_drives"
|
|
4
5
|
require "badline/computer/attachments"
|
|
5
6
|
require "badline/computer/kernal_traps"
|
|
@@ -9,16 +10,14 @@ module Badline
|
|
|
9
10
|
class Computer
|
|
10
11
|
include IntegerHelper
|
|
11
12
|
include KeyboardBuffer
|
|
13
|
+
include Machine::Clocking
|
|
14
|
+
include Common
|
|
12
15
|
include Attachments
|
|
13
16
|
include KernalTraps
|
|
14
17
|
include SavedState
|
|
15
18
|
|
|
16
19
|
attr_reader :address_bus, :cpu, :cycles, :drive1541
|
|
17
20
|
|
|
18
|
-
# The path of the disk or directory device 8 serves through the traps
|
|
19
|
-
# (Attachments), or an empty one.
|
|
20
|
-
def mounted_path = @drive.nil? ? "" : @drive.path
|
|
21
|
-
|
|
22
21
|
def family = :c64
|
|
23
22
|
|
|
24
23
|
def region = address_bus.region
|
|
@@ -108,46 +107,17 @@ module Badline
|
|
|
108
107
|
@cycles += 1
|
|
109
108
|
end
|
|
110
109
|
|
|
111
|
-
# Runs `count` cycles, one #cycle! after another.
|
|
112
|
-
def run_cycles(count)
|
|
113
|
-
i = 0
|
|
114
|
-
while i < count
|
|
115
|
-
cycle!
|
|
116
|
-
i += 1
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Runs cycles until the block returns true or the cycle count passes
|
|
121
|
-
# `limit`, checking before each cycle.
|
|
122
|
-
def run_until(limit)
|
|
123
|
-
cycle! until yield || @cycles > limit
|
|
124
|
-
end
|
|
125
|
-
|
|
126
110
|
# The cycle at which #on_init's handlers run, once the KERNAL has booted.
|
|
127
111
|
def init_threshold
|
|
128
112
|
INIT_THRESHOLD
|
|
129
113
|
end
|
|
130
114
|
|
|
131
|
-
# The clock, the raster and the crop of the machine's region, a pixel
|
|
132
|
-
# to a window pixel.
|
|
133
|
-
def timing
|
|
134
|
-
region = address_bus.region
|
|
135
|
-
Timing.new(clock_hz: region.clock_hz, cycles_per_line: region.cycles_per_line,
|
|
136
|
-
lines_per_frame: region.lines_per_frame, crop: region.crop, pixel_width: 1)
|
|
137
|
-
end
|
|
138
|
-
|
|
139
115
|
# The chip whose #display a front end shows.
|
|
140
116
|
def video = @vic
|
|
141
117
|
|
|
142
118
|
# The chip a front end records the machine's sound from.
|
|
143
119
|
def sound_source = @sid
|
|
144
120
|
|
|
145
|
-
def load_prg(data)
|
|
146
|
-
uint16(data[0], data[1]).tap do |load_addr|
|
|
147
|
-
ram.write(load_addr, data[2..])
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
|
|
151
121
|
def attach_cartridge(cartridge)
|
|
152
122
|
connect_cartridge(cartridge)
|
|
153
123
|
power_cycle!
|
|
@@ -208,37 +178,12 @@ module Badline
|
|
|
208
178
|
# The one-shot has pulsed already, so letting go does nothing.
|
|
209
179
|
def release_restore; end
|
|
210
180
|
|
|
211
|
-
def capture_output
|
|
212
|
-
@capture_output ||= ChroutTrap.new(cpu:, bus: address_bus, layout: KernalTrap::C64_LAYOUT).tap do |trap|
|
|
213
|
-
cpu.install_trap(ChroutTrap::ADDRESS) { trap.call }
|
|
214
|
-
end
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
def inspect
|
|
218
|
-
"#<#{self.class.name} cycles=#{@cycles} cpu=(#{@cpu.inspect})>"
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
def on_init(&block)
|
|
222
|
-
if booting?
|
|
223
|
-
@init_handlers << block
|
|
224
|
-
else
|
|
225
|
-
block.call
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
|
|
229
181
|
private
|
|
230
182
|
|
|
231
|
-
# The KERNAL the traps stand in for (
|
|
183
|
+
# The KERNAL the traps stand in for (KernalTraps#mount).
|
|
232
184
|
def trap_layout = KernalTrap::C64_LAYOUT
|
|
233
185
|
|
|
234
|
-
|
|
235
|
-
# key, and the CPU takes an interrupt on its falling edge.
|
|
236
|
-
def drive_nmi
|
|
237
|
-
nmi = @cia2.interrupted? || @cartridge_nmi || @restore_pulse
|
|
238
|
-
@restore_pulse = false
|
|
239
|
-
@cpu.nmi = true if nmi && !@nmi_asserted
|
|
240
|
-
@nmi_asserted = nmi
|
|
241
|
-
end
|
|
186
|
+
def trap_bus = address_bus
|
|
242
187
|
|
|
243
188
|
def plug_reu(size_kb)
|
|
244
189
|
reu = REU.new(size_kb, bus: @address_bus, vic: @vic)
|
|
@@ -278,13 +223,5 @@ module Badline
|
|
|
278
223
|
@freezing = false
|
|
279
224
|
address_bus.cartridge.freeze!
|
|
280
225
|
end
|
|
281
|
-
|
|
282
|
-
def booting?
|
|
283
|
-
@cycles < init_threshold
|
|
284
|
-
end
|
|
285
|
-
|
|
286
|
-
def handle_init
|
|
287
|
-
@init_handlers.each(&:call)
|
|
288
|
-
end
|
|
289
226
|
end
|
|
290
227
|
end
|
data/lib/badline/core.rb
CHANGED
|
@@ -15,6 +15,7 @@ require "badline/memory"
|
|
|
15
15
|
require "badline/color_memory"
|
|
16
16
|
require "badline/rom"
|
|
17
17
|
require "badline/ram_expansion"
|
|
18
|
+
require "badline/cpu_port"
|
|
18
19
|
require "badline/address_bus"
|
|
19
20
|
require "badline/instruction"
|
|
20
21
|
require "badline/instruction_set"
|
|
@@ -23,7 +24,6 @@ require "badline/keyboard"
|
|
|
23
24
|
require "badline/joystick"
|
|
24
25
|
require "badline/control_ports"
|
|
25
26
|
require "badline/input"
|
|
26
|
-
require "badline/cycleable"
|
|
27
27
|
require "badline/datasette"
|
|
28
28
|
require "badline/time_of_day"
|
|
29
29
|
require "badline/cia"
|
|
@@ -42,6 +42,7 @@ require "badline/drive1581"
|
|
|
42
42
|
require "badline/drive1581/slot"
|
|
43
43
|
require "badline/snapshot/state"
|
|
44
44
|
require "badline/snapshot/setup"
|
|
45
|
+
require "badline/machine"
|
|
45
46
|
require "badline/computer"
|
|
46
47
|
require "badline/vic20"
|
|
47
48
|
require "badline/checkpoint"
|
|
@@ -50,4 +51,3 @@ require "badline/cartridge"
|
|
|
50
51
|
require "badline/kernal_trap"
|
|
51
52
|
require "badline/chrout_trap"
|
|
52
53
|
require "badline/media"
|
|
53
|
-
require "badline/machine"
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
# The I/O port of the 6510 and the 8502 at $00 (direction) and $01
|
|
5
|
+
# (data). An input pin reads its pull-up, P4 reads the datasette's sense
|
|
6
|
+
# line, and a pin without a pull-up holds the charge last driven onto
|
|
7
|
+
# it. P5 drives the datasette motor through an inverter: low runs it.
|
|
8
|
+
#
|
|
9
|
+
# The 6510 has pull-ups on P0-P2 and P4 and leaves P3, P6 and P7
|
|
10
|
+
# floating. The 8502 adds a CAPS LOCK pin on P6, high while the key is
|
|
11
|
+
# up.
|
|
12
|
+
class CPUPort
|
|
13
|
+
TAPE_SENSE = 0b0001_0000
|
|
14
|
+
|
|
15
|
+
# The direction and output registers, and the charge on the floating
|
|
16
|
+
# pins.
|
|
17
|
+
attr_reader :ddr, :data, :floating
|
|
18
|
+
|
|
19
|
+
# The port as the banking logic reads it (PortStatus).
|
|
20
|
+
attr_reader :status
|
|
21
|
+
|
|
22
|
+
# Whether CAPS LOCK is down, holding its pin low.
|
|
23
|
+
attr_reader :caps_lock
|
|
24
|
+
|
|
25
|
+
# +pullups+ and +floating+ are the masks of the pins with a pull-up and
|
|
26
|
+
# of the floating ones, and +caps_lock+ the CAPS LOCK pin's, 0 where
|
|
27
|
+
# there is none.
|
|
28
|
+
def initialize(datasette, pullups:, floating:, caps_lock: 0)
|
|
29
|
+
@datasette = datasette
|
|
30
|
+
@pullups = pullups
|
|
31
|
+
@floating_mask = floating
|
|
32
|
+
@caps_lock_pin = caps_lock
|
|
33
|
+
@caps_lock = false
|
|
34
|
+
@ddr = 0x00
|
|
35
|
+
@data = 0x00
|
|
36
|
+
@floating = 0x00
|
|
37
|
+
@change_handler = nil
|
|
38
|
+
@status = PortStatus.new(%i[basic kernal io tape_out tape_switch tape_motor], value: input_value)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Calls the block after every write to the port, for the bus to remap
|
|
42
|
+
# its banks.
|
|
43
|
+
def on_change(&handler)
|
|
44
|
+
@change_handler = handler
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# What a read of $01 sees.
|
|
48
|
+
def value = @status.value
|
|
49
|
+
|
|
50
|
+
def write_ddr(value)
|
|
51
|
+
@ddr = value
|
|
52
|
+
update
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def write_data(value)
|
|
56
|
+
@data = value
|
|
57
|
+
update
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# The RES line clears the direction and output registers. The floating
|
|
61
|
+
# pins keep their charge.
|
|
62
|
+
def reset!
|
|
63
|
+
@ddr = 0x00
|
|
64
|
+
@data = 0x00
|
|
65
|
+
update
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Sets the registers and the charge, as a snapshot restores them, and
|
|
69
|
+
# drives the motor and calls the change handler as a write does.
|
|
70
|
+
def restore(ddr, data, floating)
|
|
71
|
+
load(ddr, data, floating)
|
|
72
|
+
update
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Sets the registers and the charge, as a snapshot loads them, without
|
|
76
|
+
# driving the motor or calling the change handler.
|
|
77
|
+
def load(ddr, data, floating)
|
|
78
|
+
@ddr = ddr
|
|
79
|
+
@data = data
|
|
80
|
+
@floating = floating
|
|
81
|
+
refresh
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Holds CAPS LOCK down, or lets it up.
|
|
85
|
+
def caps_lock=(down)
|
|
86
|
+
@caps_lock = down
|
|
87
|
+
refresh
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Reads the inputs again, after the datasette's sense line changes.
|
|
91
|
+
def refresh
|
|
92
|
+
@status.value = input_value
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def update
|
|
98
|
+
driven = @ddr & @floating_mask
|
|
99
|
+
@floating = (@floating & ~driven) | (@data & driven)
|
|
100
|
+
refresh
|
|
101
|
+
@datasette.motor = !@status.tape_motor?
|
|
102
|
+
@change_handler&.call
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def input_value
|
|
106
|
+
input = @pullups | (@floating & @floating_mask)
|
|
107
|
+
input |= @caps_lock_pin unless @caps_lock
|
|
108
|
+
input &= ~TAPE_SENSE if @datasette.sense_low?
|
|
109
|
+
(@data & @ddr) | (input & ~@ddr & 0xff)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -35,20 +35,10 @@ module Badline
|
|
|
35
35
|
# The half track entries a side of a .g71 holds.
|
|
36
36
|
G71_SIDE_ENTRIES = 84
|
|
37
37
|
|
|
38
|
-
# GCR bytes around a track in each speed zone:
|
|
39
|
-
# 300 rpm, in bytes of 32, 30, 28 and 26 µs.
|
|
38
|
+
# GCR bytes around a track in each speed zone (Storage::D64Image.zone):
|
|
39
|
+
# 200 ms of rotation at 300 rpm, in bytes of 32, 30, 28 and 26 µs.
|
|
40
40
|
TRACK_LENGTHS = [6250, 6666, 7142, 7692].freeze
|
|
41
41
|
|
|
42
|
-
# The speed zone each track is written in: 3 for the 21 sectors of
|
|
43
|
-
# tracks 1-17, 2 for 18-24, 1 for 25-30 and 0 from 31 on.
|
|
44
|
-
def self.zone(track)
|
|
45
|
-
if track <= 17 then 3
|
|
46
|
-
elsif track <= 24 then 2
|
|
47
|
-
elsif track <= 30 then 1
|
|
48
|
-
else 0
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
42
|
# How far round the DOS's N: starts each track from the one before,
|
|
53
43
|
# in each zone, in 1/10000 of a turn: it steps in and starts writing
|
|
54
44
|
# the next track that long after it started the last.
|
|
@@ -69,10 +59,17 @@ module Badline
|
|
|
69
59
|
# a .d71 formatted on both sides, and anything else as a .d64,
|
|
70
60
|
# formatted. `read_only` opens the image write-protected.
|
|
71
61
|
def self.open(path, read_only: false)
|
|
72
|
-
|
|
73
|
-
|
|
62
|
+
image = image_for(path, read_only)
|
|
63
|
+
(State.g64?(path) ? from_g64(image) : from_d64(image)).opened(path, read_only)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The image at +path+, by its name: a .g64 or .g71, a .d71, and
|
|
67
|
+
# anything else a .d64.
|
|
68
|
+
def self.image_for(path, read_only)
|
|
69
|
+
return Storage::G64Image.new(path, read_only:) if State.g64?(path)
|
|
70
|
+
return Storage::D71Image.new(path, read_only:) if File.extname(path).casecmp?(".d71")
|
|
74
71
|
|
|
75
|
-
|
|
72
|
+
Storage::D64Image.new(path, read_only:)
|
|
76
73
|
end
|
|
77
74
|
|
|
78
75
|
# A disk from a G64 or G71 image, each half track as the image stores
|
|
@@ -141,7 +138,7 @@ module Badline
|
|
|
141
138
|
angle = 0
|
|
142
139
|
(1..image.track_count).each do |track|
|
|
143
140
|
half_track = d64_half_track(image, track)
|
|
144
|
-
zone = zone((half_track % SIDE) / 2)
|
|
141
|
+
zone = Storage::D64Image.zone((half_track % SIDE) / 2)
|
|
145
142
|
angle = half_track == SIDE + 2 ? 0 : (angle + SKEWS[zone]) % SKEW_TURN if track > 1
|
|
146
143
|
bytes = format_track(image, track, id, zone)
|
|
147
144
|
disk.write(half_track, Track.new(bytes.rotate(-(bytes.length * angle / SKEW_TURN)), zone))
|
|
@@ -149,7 +146,7 @@ module Badline
|
|
|
149
146
|
disk
|
|
150
147
|
end
|
|
151
148
|
|
|
152
|
-
def self.format_track(image, track, id, zone = zone(track))
|
|
149
|
+
def self.format_track(image, track, id, zone = Storage::D64Image.zone(track))
|
|
153
150
|
sectors = image.sectors_in(track)
|
|
154
151
|
length = TRACK_LENGTHS[zone]
|
|
155
152
|
gap = (length - (sectors * SECTOR_LENGTH)) / sectors
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "badline/media/extensions"
|
|
4
|
+
|
|
3
5
|
module Badline
|
|
4
6
|
class Drive1541
|
|
5
7
|
class Disk
|
|
@@ -12,22 +14,12 @@ module Badline
|
|
|
12
14
|
# for a disk from Disk.open. Nil for a disk made another way.
|
|
13
15
|
attr_reader :path, :read_only
|
|
14
16
|
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
def self.g64?(path) = %w[.g64 .g71].include?(File.extname(path).downcase)
|
|
18
|
-
|
|
19
|
-
def self.d71?(path) = File.extname(path).casecmp?(".d71")
|
|
17
|
+
# Whether the image is a .g64 or .g71, by its name.
|
|
18
|
+
def self.g64?(path) = Media::Extensions.kind(path) == :gcr
|
|
20
19
|
|
|
21
|
-
# A disk for the image at the path, its tracks left
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Disk.new(Storage::G64Image.new(path, read_only:)).opened(path, read_only)
|
|
25
|
-
elsif d71?(path)
|
|
26
|
-
Disk.new(Storage::D71Image.new(path, read_only:)).opened(path, read_only)
|
|
27
|
-
else
|
|
28
|
-
Disk.new(Storage::D64Image.new(path, read_only:)).opened(path, read_only)
|
|
29
|
-
end
|
|
30
|
-
end
|
|
20
|
+
# A disk for the image at the path (Disk.image_for), its tracks left
|
|
21
|
+
# for the state.
|
|
22
|
+
def self.reopen(path, read_only) = Disk.new(Disk.image_for(path, read_only)).opened(path, read_only)
|
|
31
23
|
|
|
32
24
|
# The disk a state from save_state describes: +current+ when it was
|
|
33
25
|
# opened from the same image the same way, and otherwise a disk for
|
|
@@ -51,7 +51,7 @@ module Badline
|
|
|
51
51
|
STEP_RATES = [12_000, 24_000, 4_000, 6_000].freeze
|
|
52
52
|
SETTLE = 30_000
|
|
53
53
|
|
|
54
|
-
NEVER = 1 <<
|
|
54
|
+
NEVER = 1 << 60
|
|
55
55
|
|
|
56
56
|
# Status bits. Bits 1 and 2 report the index pulse and track 0 after
|
|
57
57
|
# a type I command, and DRQ and lost data after the others; bit 5
|
|
@@ -33,7 +33,7 @@ module Badline
|
|
|
33
33
|
def self.media_problem
|
|
34
34
|
yield
|
|
35
35
|
""
|
|
36
|
-
rescue Unsupported, Media::TrueDrive::Error, Media::DiskList::Error, Media::NotDisk, Datasette::Missing,
|
|
36
|
+
rescue Machine::Unsupported, Media::TrueDrive::Error, Media::DiskList::Error, Media::NotDisk, Datasette::Missing,
|
|
37
37
|
SystemCallError, Storage::SIDFile::FormatError, Storage::T64::FormatError, Storage::TAP::FormatError,
|
|
38
38
|
Storage::CRTFile::FormatError, Storage::G64Image::FormatError, Cartridge::UnsupportedTypeError,
|
|
39
39
|
Snapshot::FormatError => e
|
|
@@ -53,11 +53,10 @@ module Badline
|
|
|
53
53
|
start(options, media, options.writable?)
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
# A new machine built from the options, with
|
|
57
|
-
# attached and started as the command line does,
|
|
58
|
-
# `writable`. The
|
|
59
|
-
#
|
|
60
|
-
# tune.
|
|
56
|
+
# A new machine built from the options (Machine.for_options), with
|
|
57
|
+
# `media`, if not nil, attached and started as the command line does,
|
|
58
|
+
# its disk writable if `writable`. The VIC-20 raises
|
|
59
|
+
# Machine::Unsupported for a .sid tune.
|
|
61
60
|
def self.start(options, media, writable)
|
|
62
61
|
computer = build(options, media)
|
|
63
62
|
Media::TrueDrive.plug(computer) if options.true_drive?
|
|
@@ -68,60 +67,10 @@ module Badline
|
|
|
68
67
|
computer
|
|
69
68
|
end
|
|
70
69
|
|
|
70
|
+
# The machine the options name, built for `media`, which says the RAM
|
|
71
|
+
# expansion a VIC-20 gets for it.
|
|
71
72
|
def self.build(options, media)
|
|
72
|
-
|
|
73
|
-
when :vic20 then vic20(options, media)
|
|
74
|
-
when :c128 then c128(options, media)
|
|
75
|
-
else c64(options, media)
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def self.c64(options, media)
|
|
80
|
-
sid_model = options.sid_model || Media.sid_model(media, otherwise: nil)
|
|
81
|
-
Machine.build(:c64, model: options.model, sid_model:, reu: options.reu)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# A C128 in C128 mode. With --c64, or media only C64 mode runs, a .sid
|
|
85
|
-
# tune or a program that loads at the C64's BASIC start, C= is held
|
|
86
|
-
# through the reset, so the C128 KERNAL starts C64 mode.
|
|
87
|
-
def self.c128(options, media)
|
|
88
|
-
sid_model = options.sid_model || Media.sid_model(media, otherwise: nil)
|
|
89
|
-
machine = C128.new(model: options.model, sid_model:, mode: :c128)
|
|
90
|
-
machine.hold_commodore_key if options.c64_mode? || c64_media?(media)
|
|
91
|
-
machine
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def self.c64_media?(media)
|
|
95
|
-
return false if media.nil? || !File.file?(media)
|
|
96
|
-
|
|
97
|
-
extension = File.extname(media).downcase
|
|
98
|
-
return true if extension == ".sid"
|
|
99
|
-
return false unless %w[.prg .p00].include?(extension)
|
|
100
|
-
|
|
101
|
-
[Media::BASIC_START, Media::BASIC_START - 1].include?(Storage::P00.load_address(media))
|
|
73
|
+
Machine.for_options(options, media) { |line| puts line }
|
|
102
74
|
end
|
|
103
|
-
|
|
104
|
-
# A VIC-20 with the RAM --ram names, or else the RAM `media` needs
|
|
105
|
-
# (Media::Vic20Media.ram_for), which it says. Raises Unsupported for
|
|
106
|
-
# media a VIC-20 doesn't take.
|
|
107
|
-
def self.vic20(options, media)
|
|
108
|
-
ram = options.ram
|
|
109
|
-
unless media.nil?
|
|
110
|
-
unsupported = Media::Vic20Media::UNSUPPORTED.include?(File.extname(media).downcase)
|
|
111
|
-
raise Unsupported, "doesn't go in a VIC-20" if unsupported
|
|
112
|
-
|
|
113
|
-
ram ||= vic20_ram(media)
|
|
114
|
-
end
|
|
115
|
-
Machine.build(:vic20, model: options.model, ram:)
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def self.vic20_ram(media)
|
|
119
|
-
ram = Media::Vic20Media.ram_for(media)
|
|
120
|
-
puts "RAM expansion: #{ram}"
|
|
121
|
-
ram
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# Media the machine the options name doesn't take.
|
|
125
|
-
class Unsupported < ArgumentError; end
|
|
126
75
|
end
|
|
127
76
|
end
|
|
@@ -12,7 +12,7 @@ module Badline
|
|
|
12
12
|
# below $0334, into the zero page, the stack or the KERNAL vectors: the
|
|
13
13
|
# ROM's byte loop then loads it through the serial traps, and a loader
|
|
14
14
|
# that overwrites ISTOP takes over mid-load as it does on a real drive.
|
|
15
|
-
class Load <
|
|
15
|
+
class Load < FileRoutine
|
|
16
16
|
# ST bits at $90
|
|
17
17
|
EOI = 0x40
|
|
18
18
|
VERIFY_MISMATCH = 0x10
|
|
@@ -15,7 +15,7 @@ module Badline
|
|
|
15
15
|
# write: the drive stops listening, so ST reads DEVICE NOT PRESENT,
|
|
16
16
|
# and the ROM returns without an error. A "@" before the drive prefix
|
|
17
17
|
# writes over a file of the same name.
|
|
18
|
-
class Save <
|
|
18
|
+
class Save < FileRoutine
|
|
19
19
|
SECONDARY = 0x61
|
|
20
20
|
|
|
21
21
|
# ST bit at $90
|
data/lib/badline/kernal_trap.rb
CHANGED
|
@@ -4,7 +4,7 @@ require "badline/kernal_trap/layout"
|
|
|
4
4
|
require "badline/kernal_trap/routine"
|
|
5
5
|
require "badline/kernal_trap/channel"
|
|
6
6
|
require "badline/kernal_trap/drive"
|
|
7
|
-
require "badline/kernal_trap/
|
|
7
|
+
require "badline/kernal_trap/file_routine"
|
|
8
8
|
require "badline/kernal_trap/load"
|
|
9
9
|
require "badline/kernal_trap/save"
|
|
10
10
|
require "badline/kernal_trap/serial"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Machine
|
|
5
|
+
# Running a machine from outside its own #cycle!, and the handlers that
|
|
6
|
+
# wait for its KERNAL to boot. Computer, Vic20 and C128 each include it
|
|
7
|
+
# and answer #cycle!, #cycles and #init_threshold, the cycle at which
|
|
8
|
+
# #on_init's handlers run, and keep the handlers in @init_handlers.
|
|
9
|
+
module Clocking
|
|
10
|
+
# Runs `count` cycles, one #cycle! after another.
|
|
11
|
+
def run_cycles(count)
|
|
12
|
+
i = 0
|
|
13
|
+
while i < count
|
|
14
|
+
cycle!
|
|
15
|
+
i += 1
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Runs cycles until the block returns true or the cycle count passes
|
|
20
|
+
# `limit`, checking before each cycle.
|
|
21
|
+
def run_until(limit)
|
|
22
|
+
cycle! until yield || @cycles > limit
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Runs the block once the KERNAL has booted (see init_threshold), or
|
|
26
|
+
# now if it has.
|
|
27
|
+
def on_init(&block)
|
|
28
|
+
if booting?
|
|
29
|
+
@init_handlers << block
|
|
30
|
+
else
|
|
31
|
+
block.call
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def inspect
|
|
36
|
+
"#<#{self.class.name} cycles=#{@cycles} cpu=(#{@cpu.inspect})>"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def booting?
|
|
42
|
+
@cycles < init_threshold
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def handle_init
|
|
46
|
+
@init_handlers.each(&:call)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|