badline 0.11.1 → 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 +26 -0
- data/CLAUDE.md +8 -7
- data/CONTRIBUTING.md +7 -6
- data/README.md +12 -5
- data/doc/media.md +28 -1
- data/doc/pinned-behaviour.md +31 -1
- data/exe/badline-ruby +1 -1
- 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 +17 -52
- data/lib/badline/c128/bus_state.rb +5 -5
- data/lib/badline/c128/drives.rb +35 -22
- data/lib/badline/c128/fast_serial.rb +1 -0
- data/lib/badline/c128/io_pages.rb +35 -0
- data/lib/badline/c128/mmu.rb +9 -7
- data/lib/badline/c128/modes.rb +17 -4
- data/lib/badline/c128/saved_state.rb +22 -0
- data/lib/badline/c128/z80.rb +49 -0
- data/lib/badline/c128/z80_pages.rb +118 -0
- data/lib/badline/c128/z80_turns.rb +106 -0
- data/lib/badline/c128.rb +21 -83
- data/lib/badline/cartridge/action_replay.rb +2 -2
- data/lib/badline/cartridge/comal80.rb +1 -7
- data/lib/badline/cartridge/dinamic.rb +0 -8
- data/lib/badline/cartridge/final_cartridge3.rb +6 -6
- data/lib/badline/cartridge/fun_play.rb +0 -8
- data/lib/badline/cartridge/game_system.rb +0 -8
- data/lib/badline/cartridge/kcs_power.rb +10 -7
- data/lib/badline/cartridge/mach5.rb +2 -4
- data/lib/badline/cartridge/magic_desk.rb +0 -8
- data/lib/badline/cartridge/ocean.rb +0 -8
- data/lib/badline/cartridge/pagefox.rb +2 -2
- data/lib/badline/cartridge/retro_replay.rb +7 -6
- data/lib/badline/cartridge/rex_utility.rb +2 -6
- data/lib/badline/cartridge/rgcd.rb +2 -2
- data/lib/badline/cartridge/simons_basic.rb +0 -5
- data/lib/badline/cartridge/super_games.rb +4 -2
- data/lib/badline/cartridge/westermann.rb +2 -6
- data/lib/badline/cartridge.rb +37 -9
- data/lib/badline/cia/fast_forward.rb +21 -3
- data/lib/badline/cia.rb +8 -5
- data/lib/badline/computer/attachments.rb +14 -95
- data/lib/badline/computer/common.rb +34 -0
- data/lib/badline/computer/kernal_traps.rb +65 -5
- data/lib/badline/computer/true_drives.rb +69 -0
- data/lib/badline/computer.rb +9 -68
- data/lib/badline/core.rb +4 -2
- data/lib/badline/cpu_port.rb +112 -0
- data/lib/badline/drive/core.rb +8 -33
- data/lib/badline/drive/fast_serial.rb +73 -0
- data/lib/badline/drive/idle.rb +1 -11
- data/lib/badline/drive/orbit.rb +2 -5
- data/lib/badline/drive/sleep.rb +0 -2
- data/lib/badline/drive/vias.rb +84 -0
- data/lib/badline/drive.rb +2 -0
- data/lib/badline/drive1541/disk.rb +14 -17
- data/lib/badline/drive1541/disk_state.rb +7 -15
- data/lib/badline/drive1541.rb +1 -0
- data/lib/badline/drive1571/wd1770.rb +94 -8
- data/lib/badline/drive1571.rb +8 -58
- data/lib/badline/drive1581/bus.rb +137 -0
- data/lib/badline/drive1581/cpu.rb +11 -0
- data/lib/badline/drive1581/disk.rb +135 -0
- data/lib/badline/drive1581/disk_state.rb +87 -0
- data/lib/badline/drive1581/mechanism.rb +117 -0
- data/lib/badline/drive1581/serial_port.rb +52 -0
- data/lib/badline/drive1581/slot.rb +78 -0
- data/lib/badline/drive1581/track.rb +100 -0
- data/lib/badline/drive1581/track_bytes.rb +75 -0
- data/lib/badline/drive1581/wd1772/registers.rb +89 -0
- data/lib/badline/drive1581/wd1772/sector_writes.rb +73 -0
- data/lib/badline/drive1581/wd1772/sectors.rb +150 -0
- data/lib/badline/drive1581/wd1772/state.rb +66 -0
- data/lib/badline/drive1581/wd1772/stepping.rb +109 -0
- data/lib/badline/drive1581/wd1772/tracks.rb +125 -0
- data/lib/badline/drive1581/wd1772/turn.rb +73 -0
- data/lib/badline/drive1581/wd1772.rb +216 -0
- data/lib/badline/drive1581.rb +233 -0
- data/lib/badline/frontend/app.rb +1 -3
- data/lib/badline/frontend/audio_device.rb +67 -0
- data/lib/badline/frontend/audio_sink.rb +16 -50
- data/lib/badline/frontend/boot.rb +24 -67
- data/lib/badline/frontend/buttons.rb +7 -7
- data/lib/badline/frontend/keys.rb +33 -8
- data/lib/badline/frontend/media_slots.rb +42 -0
- data/lib/badline/frontend/{confirmation.rb → menu/confirmation.rb} +4 -4
- data/lib/badline/frontend/{file_browser.rb → menu/file_browser.rb} +18 -34
- data/lib/badline/frontend/{menu_dialogs.rb → menu/menu_dialogs.rb} +3 -7
- data/lib/badline/frontend/{menu_media.rb → menu/menu_media.rb} +26 -51
- data/lib/badline/frontend/{menu_pages.rb → menu/menu_pages.rb} +5 -8
- data/lib/badline/frontend/{name_field.rb → menu/name_field.rb} +11 -24
- data/lib/badline/frontend/{pause_menu.rb → menu/pause_menu.rb} +13 -28
- data/lib/badline/frontend/menu/theme.rb +18 -0
- data/lib/badline/frontend/{filter_view.rb → player/filter_view.rb} +4 -4
- data/lib/badline/frontend/{info_view.rb → player/info_view.rb} +3 -3
- data/lib/badline/frontend/{player_window.rb → player/player_controller.rb} +17 -25
- data/lib/badline/frontend/{player_footer.rb → player/player_footer.rb} +8 -8
- data/lib/badline/frontend/{player_header.rb → player/player_header.rb} +5 -5
- data/lib/badline/frontend/{player_screen.rb → player/player_screen.rb} +4 -5
- data/lib/badline/frontend/{scope.rb → player/scope.rb} +1 -1
- data/lib/badline/frontend/{sid_view.rb → player/sid_view.rb} +16 -20
- data/lib/badline/frontend/player/theme.rb +18 -0
- data/lib/badline/frontend/{visualizer_view.rb → player/visualizer_view.rb} +8 -8
- data/lib/badline/frontend/sound.rb +19 -38
- data/lib/badline/frontend/timeline.rb +3 -14
- data/lib/badline/frontend.rb +24 -20
- data/lib/badline/iec_bus.rb +14 -2
- 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 +63 -6
- data/lib/badline/media/boot_disk.rb +50 -0
- 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/not_disk.rb +8 -0
- data/lib/badline/media/program.rb +28 -0
- data/lib/badline/media/true_drive.rb +38 -17
- 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 +90 -81
- data/lib/badline/options/event.rb +4 -2
- data/lib/badline/options/family.rb +3 -9
- data/lib/badline/options/help.rb +2 -1
- data/lib/badline/roms/README +9 -0
- data/lib/badline/roms/dos1581.rom +0 -0
- data/lib/badline/sdl.rb +37 -20
- data/lib/badline/sid.rb +1 -1
- data/lib/badline/snapshot/machine_state.rb +9 -1
- data/lib/badline/snapshot/state.rb +6 -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/p00.rb +8 -0
- data/lib/badline/storage/t64.rb +1 -1
- data/lib/badline/time_of_day.rb +3 -3
- 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 +15 -71
- data/lib/badline/vic20/saved_state.rb +2 -30
- data/lib/badline/vic20.rb +4 -37
- data/lib/badline/z80/core.rb +2 -0
- data/lib/badline/z80/saved_state.rb +69 -0
- metadata +61 -23
- data/lib/badline/cycleable.rb +0 -38
- /data/lib/badline/frontend/{menu_ports.rb → menu/menu_ports.rb} +0 -0
- /data/lib/badline/frontend/{player_commands.rb → player/player_commands.rb} +0 -0
- /data/lib/badline/frontend/{player_state.rb → player/player_state.rb} +0 -0
- /data/lib/badline/frontend/{sid_history.rb → player/sid_history.rb} +0 -0
data/lib/badline/cia.rb
CHANGED
|
@@ -61,14 +61,16 @@ module Badline
|
|
|
61
61
|
@tb.latch = value
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
# +tod_hz+ is the rate of the pulses on the TOD pin, the region's mains
|
|
65
|
+
# unless a machine feeds it otherwise, and 0 for a pin held still.
|
|
66
|
+
def initialize(start: 0, peripheral: nil, model: :mos6526, region: Region::PAL, tod_hz: nil)
|
|
65
67
|
raise ArgumentError, "unknown CIA model #{model}" unless MODELS.include?(model)
|
|
66
68
|
|
|
67
69
|
addressable_at(start, length: 2**8)
|
|
68
70
|
|
|
69
71
|
@model = model
|
|
70
72
|
@clock_hz = region.clock_hz
|
|
71
|
-
@mains_hz = region.mains_hz
|
|
73
|
+
@mains_hz = tod_hz || region.mains_hz
|
|
72
74
|
|
|
73
75
|
@peripheral = peripheral
|
|
74
76
|
@port_b4_handler = nil
|
|
@@ -139,9 +141,10 @@ module Badline
|
|
|
139
141
|
|
|
140
142
|
# Port A as driven by the data/direction registers alone, without
|
|
141
143
|
# peripheral pulldown. Cheap path for the VIC bank lookup.
|
|
142
|
-
def port_a_lines
|
|
143
|
-
|
|
144
|
-
|
|
144
|
+
def port_a_lines = driven_lines(@data_port_a, @data_dir_a)
|
|
145
|
+
|
|
146
|
+
# Port B as driven by the data/direction registers alone.
|
|
147
|
+
def port_b_lines = driven_lines(@data_port_b, @data_dir_b)
|
|
145
148
|
|
|
146
149
|
def read_port_b
|
|
147
150
|
lines = driven_lines(@data_port_b, @data_dir_b)
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
class Computer
|
|
5
|
-
# What plugs into the machine besides its chips, the cartridge,
|
|
6
|
-
#
|
|
7
|
-
#
|
|
5
|
+
# What plugs into the machine besides its chips, the cartridge, its
|
|
6
|
+
# true drives (TrueDrives) and an REU, and their state for a snapshot.
|
|
7
|
+
# The disk device 8 serves through the traps is KernalTraps'.
|
|
8
8
|
module Attachments
|
|
9
|
+
include TrueDrives
|
|
10
|
+
|
|
9
11
|
# Puts the cartridge in the expansion port and wires its clock and NMI
|
|
10
12
|
# line to the machine, leaving the machine's state as it is. A
|
|
11
13
|
# restored snapshot's cartridge comes back through here.
|
|
@@ -15,71 +17,13 @@ module Badline
|
|
|
15
17
|
address_bus.attach_cartridge(cartridge)
|
|
16
18
|
end
|
|
17
19
|
|
|
18
|
-
# Puts the storage in device 8. Mounting again swaps the disk at any
|
|
19
|
-
# point while the machine runs: the drive keeps its RAM, which only a
|
|
20
|
-
# drive reset clears, and its status. The traps go on the KERNAL the
|
|
21
|
-
# machine runs (#trap_layout).
|
|
22
|
-
def mount(storage)
|
|
23
|
-
return @drive.insert(storage) if @drive
|
|
24
|
-
|
|
25
|
-
@drive = KernalTrap::Drive.new(storage)
|
|
26
|
-
install_kernal_traps(trap_layout)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Takes device 8's mounted storage out, and with it the LOAD, SAVE and
|
|
30
|
-
# serial traps, so the KERNAL's routines go out over the serial bus.
|
|
31
|
-
# Mounting again starts a new drive, with its RAM cleared.
|
|
32
|
-
def unmount
|
|
33
|
-
return unless @drive
|
|
34
|
-
|
|
35
|
-
remove_kernal_traps
|
|
36
|
-
@drive = nil
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Whether device 8 serves a disk or directory through the traps.
|
|
40
|
-
def mounted? = !@drive.nil?
|
|
41
|
-
|
|
42
20
|
# The serial bus, which CIA 2's port A drives through the C64's own
|
|
43
21
|
# inverters and reads back on PA6 and PA7, with or without a drive on
|
|
44
22
|
# it.
|
|
45
23
|
attr_reader :iec_bus
|
|
46
24
|
|
|
47
|
-
# Plugs in a Drive1541, which then runs alongside the C64 on its own
|
|
48
|
-
# clock and talks to it over the serial bus. The serial traps stop
|
|
49
|
-
# answering the drive's device number, so the KERNAL's TALK, LISTEN and
|
|
50
|
-
# byte transfers reach the drive. The LOAD and SAVE traps stay, and
|
|
51
|
-
# still serve a mounted image.
|
|
52
|
-
def attach_drive1541(drive)
|
|
53
|
-
@iec_bus.detach(@drive1541) if @drive1541
|
|
54
|
-
drive.host_clock_hz = region.clock_hz
|
|
55
|
-
@drive1541 = drive
|
|
56
|
-
drive.connect(iec_bus)
|
|
57
|
-
@serial_trap&.device = serial_trap_device
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# The true drive on the serial bus: the Drive1541, or nil.
|
|
61
|
-
def true_drive = @drive1541
|
|
62
|
-
|
|
63
|
-
# Plugs a Drive1541 in as device 8, and returns it.
|
|
64
|
-
def plug_true_drive = Drive1541.new.tap { |drive| attach_drive1541(drive) }
|
|
65
|
-
|
|
66
|
-
# Unplugs the Drive1541, leaving the serial bus with nothing on it.
|
|
67
|
-
def detach_drive1541
|
|
68
|
-
return unless @drive1541
|
|
69
|
-
|
|
70
|
-
@iec_bus.detach(@drive1541)
|
|
71
|
-
@drive1541 = nil
|
|
72
|
-
@serial_trap&.device = serial_trap_device
|
|
73
|
-
end
|
|
74
|
-
|
|
75
25
|
private
|
|
76
26
|
|
|
77
|
-
# The device number the serial traps answer: device 8, unless a true
|
|
78
|
-
# drive is on the bus as device 8.
|
|
79
|
-
def serial_trap_device
|
|
80
|
-
@drive1541&.device == KernalTrap::Routine::DEVICE ? nil : KernalTrap::Routine::DEVICE
|
|
81
|
-
end
|
|
82
|
-
|
|
83
27
|
# CIA 2's port A drives the serial bus, which reads CLK and DATA back
|
|
84
28
|
# into it.
|
|
85
29
|
def plug_serial_bus
|
|
@@ -110,46 +54,21 @@ module Badline
|
|
|
110
54
|
end
|
|
111
55
|
end
|
|
112
56
|
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
def save_trap_drive(out)
|
|
116
|
-
out.boolean(!@drive.nil?)
|
|
117
|
-
return unless @drive
|
|
118
|
-
|
|
119
|
-
@drive.save_state(out)
|
|
120
|
-
@serial_trap.save_state(out)
|
|
121
|
-
@save_trap.save_state(out)
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def load_trap_drive(input)
|
|
125
|
-
return unmount unless input.boolean?
|
|
126
|
-
|
|
127
|
-
storage = Storage.reopen(input)
|
|
128
|
-
unmount
|
|
129
|
-
mount(storage)
|
|
130
|
-
@drive.load_state(input)
|
|
131
|
-
@serial_trap.load_state(input)
|
|
132
|
-
@save_trap.load_state(input)
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# A true 1541 on the serial bus. The leading flag is the bus itself,
|
|
136
|
-
# which every machine now has, kept so older snapshots still load.
|
|
57
|
+
# A true 1541 on the serial bus, then a 1581. The leading flag is the
|
|
58
|
+
# bus itself, which every machine now has.
|
|
137
59
|
def save_serial_bus(out)
|
|
138
60
|
out.boolean(true).boolean(!@drive1541.nil?)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
61
|
+
if @drive1541
|
|
62
|
+
out.int(@drive1541.device)
|
|
63
|
+
@drive1541.save_state(out)
|
|
64
|
+
end
|
|
65
|
+
save_drive1581(out)
|
|
143
66
|
end
|
|
144
67
|
|
|
145
68
|
def load_serial_bus(input)
|
|
146
69
|
input.boolean?
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
device = input.int
|
|
150
|
-
detach_drive1541 if @drive1541 && @drive1541.device != device
|
|
151
|
-
attach_drive1541(Drive1541.new(device:)) unless @drive1541
|
|
152
|
-
@drive1541.load_state(input)
|
|
70
|
+
load_drive1541(input)
|
|
71
|
+
load_drive1581(input)
|
|
153
72
|
end
|
|
154
73
|
|
|
155
74
|
# The REU, which the setup says the machine has, and whether it has
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Computer
|
|
5
|
+
# What the C128 does as the C64 does: the NMI line, a PRG put into RAM
|
|
6
|
+
# and the region's timing. Computer and C128 each include it.
|
|
7
|
+
module Common
|
|
8
|
+
# The clock, the raster and the crop of the machine's region, a pixel
|
|
9
|
+
# to a window pixel.
|
|
10
|
+
def timing
|
|
11
|
+
region = address_bus.region
|
|
12
|
+
Timing.new(clock_hz: region.clock_hz, cycles_per_line: region.cycles_per_line,
|
|
13
|
+
lines_per_frame: region.lines_per_frame, crop: region.crop, pixel_width: 1)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def load_prg(data)
|
|
17
|
+
uint16(data[0], data[1]).tap do |load_addr|
|
|
18
|
+
ram.write(load_addr, data[2..])
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
# The NMI line is wired-OR between CIA 2, the cartridge and the RESTORE
|
|
25
|
+
# key, and the CPU takes an interrupt on its falling edge.
|
|
26
|
+
def drive_nmi
|
|
27
|
+
nmi = @cia2.interrupted? || @cartridge_nmi || @restore_pulse
|
|
28
|
+
@restore_pulse = false
|
|
29
|
+
@cpu.nmi = true if nmi && !@nmi_asserted
|
|
30
|
+
@nmi_asserted = nmi
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -2,18 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
class Computer
|
|
5
|
-
# The LOAD, SAVE and serial traps
|
|
6
|
-
#
|
|
5
|
+
# The disk device 8 serves through the LOAD, SAVE and serial traps, and
|
|
6
|
+
# CHROUT capture, on the KERNAL the machine runs. Computer, Vic20 and
|
|
7
|
+
# C128 each include it and answer two private methods: #trap_layout,
|
|
8
|
+
# the KernalTrap::Layout of that KERNAL, and #trap_bus, the bus the
|
|
9
|
+
# traps read and write through.
|
|
7
10
|
module KernalTraps
|
|
11
|
+
# Puts the storage in device 8. Mounting again swaps the disk at any
|
|
12
|
+
# point while the machine runs: the drive keeps its RAM, which only a
|
|
13
|
+
# drive reset clears, and its status. The traps go on the KERNAL the
|
|
14
|
+
# machine runs (#trap_layout).
|
|
15
|
+
def mount(storage)
|
|
16
|
+
return @drive.insert(storage) if @drive
|
|
17
|
+
|
|
18
|
+
@drive = KernalTrap::Drive.new(storage)
|
|
19
|
+
install_kernal_traps(trap_layout)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Takes device 8's mounted storage out, and with it the LOAD, SAVE and
|
|
23
|
+
# serial traps, so the KERNAL's routines go out over the serial bus.
|
|
24
|
+
# Mounting again starts a new drive, with its RAM cleared.
|
|
25
|
+
def unmount
|
|
26
|
+
return unless @drive
|
|
27
|
+
|
|
28
|
+
remove_kernal_traps
|
|
29
|
+
@drive = nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Whether device 8 serves a disk or directory through the traps.
|
|
33
|
+
def mounted? = !@drive.nil?
|
|
34
|
+
|
|
35
|
+
# The path of the disk or directory device 8 serves through the
|
|
36
|
+
# traps, or an empty one.
|
|
37
|
+
def mounted_path = @drive.nil? ? "" : @drive.path
|
|
38
|
+
|
|
39
|
+
# Records what the KERNAL prints through CHROUT (ChroutTrap).
|
|
40
|
+
def capture_output
|
|
41
|
+
@capture_output ||= ChroutTrap.new(cpu:, bus: trap_bus, layout: trap_layout).tap do |trap|
|
|
42
|
+
cpu.install_trap(ChroutTrap::ADDRESS) { trap.call }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
8
46
|
private
|
|
9
47
|
|
|
10
48
|
def install_kernal_traps(layout)
|
|
11
49
|
@trap_layout = layout
|
|
12
|
-
load_trap = KernalTrap::Load.new(cpu:, bus:
|
|
50
|
+
load_trap = KernalTrap::Load.new(cpu:, bus: trap_bus, layout:, drive: @drive)
|
|
13
51
|
cpu.install_trap(layout.load) { load_trap.call }
|
|
14
|
-
@serial_trap = KernalTrap::Serial.new(cpu:, bus:
|
|
52
|
+
@serial_trap = KernalTrap::Serial.new(cpu:, bus: trap_bus, layout:, drive: @drive,
|
|
15
53
|
device: serial_trap_device).install
|
|
16
|
-
save_trap = @save_trap = KernalTrap::Save.new(cpu:, bus:
|
|
54
|
+
save_trap = @save_trap = KernalTrap::Save.new(cpu:, bus: trap_bus, layout:, drive: @drive)
|
|
17
55
|
cpu.install_trap(layout.save) { save_trap.call }
|
|
18
56
|
end
|
|
19
57
|
|
|
@@ -24,6 +62,28 @@ module Badline
|
|
|
24
62
|
@serial_trap = nil
|
|
25
63
|
@save_trap = nil
|
|
26
64
|
end
|
|
65
|
+
|
|
66
|
+
# The disk device 8 serves through the traps, with the traps' own
|
|
67
|
+
# state: open channels, the drive's status and RAM, a SAVE under way.
|
|
68
|
+
def save_trap_drive(out)
|
|
69
|
+
out.boolean(!@drive.nil?)
|
|
70
|
+
return unless @drive
|
|
71
|
+
|
|
72
|
+
@drive.save_state(out)
|
|
73
|
+
@serial_trap.save_state(out)
|
|
74
|
+
@save_trap.save_state(out)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def load_trap_drive(input)
|
|
78
|
+
return unmount unless input.boolean?
|
|
79
|
+
|
|
80
|
+
storage = Storage.reopen(input)
|
|
81
|
+
unmount
|
|
82
|
+
mount(storage)
|
|
83
|
+
@drive.load_state(input)
|
|
84
|
+
@serial_trap.load_state(input)
|
|
85
|
+
@save_trap.load_state(input)
|
|
86
|
+
end
|
|
27
87
|
end
|
|
28
88
|
end
|
|
29
89
|
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Computer
|
|
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
|
+
module TrueDrives
|
|
9
|
+
include Drive1581::Slot
|
|
10
|
+
|
|
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
|
+
# answering the drive's device number, so the KERNAL's TALK, LISTEN and
|
|
14
|
+
# byte transfers reach the drive. The LOAD and SAVE traps stay, and
|
|
15
|
+
# still serve a mounted image.
|
|
16
|
+
def attach_drive1541(drive)
|
|
17
|
+
@iec_bus.detach(@drive1541) if @drive1541
|
|
18
|
+
drive.host_clock_hz = timing.clock_hz
|
|
19
|
+
@drive1541 = drive
|
|
20
|
+
drive.connect(@iec_bus)
|
|
21
|
+
@serial_trap&.device = serial_trap_device
|
|
22
|
+
drive1541_attached
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# The true drive on the serial bus: the Drive1581 ahead of the
|
|
26
|
+
# Drive1541, or nil.
|
|
27
|
+
def true_drive = @drive1581 || @drive1541
|
|
28
|
+
|
|
29
|
+
# Plugs a Drive1541 in as device 8, in place of a 1581 there, and
|
|
30
|
+
# returns it.
|
|
31
|
+
def plug_true_drive
|
|
32
|
+
detach_drive1581 if drive1581_on_device8?
|
|
33
|
+
Drive1541.new.tap { |drive| attach_drive1541(drive) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Unplugs the Drive1541, leaving the serial bus with nothing on it.
|
|
37
|
+
def detach_drive1541
|
|
38
|
+
return unless @drive1541
|
|
39
|
+
|
|
40
|
+
@iec_bus.detach(@drive1541)
|
|
41
|
+
@drive1541 = nil
|
|
42
|
+
@serial_trap&.device = serial_trap_device
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
# The device number the serial traps answer: device 8, unless a true
|
|
48
|
+
# drive is on the bus as device 8.
|
|
49
|
+
def serial_trap_device
|
|
50
|
+
device = KernalTrap::Routine::DEVICE
|
|
51
|
+
@drive1541&.device == device || drive1581_on_device8? ? nil : device
|
|
52
|
+
end
|
|
53
|
+
|
|
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
|
|
57
|
+
|
|
58
|
+
# A true 1541 on the serial bus, with its device number.
|
|
59
|
+
def load_drive1541(input)
|
|
60
|
+
return detach_drive1541 unless input.boolean?
|
|
61
|
+
|
|
62
|
+
device = input.int
|
|
63
|
+
detach_drive1541 if @drive1541 && @drive1541.device != device
|
|
64
|
+
attach_drive1541(Drive1541.new(device:)) unless @drive1541
|
|
65
|
+
@drive1541.load_state(input)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
data/lib/badline/computer.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "badline/computer/common"
|
|
4
|
+
require "badline/computer/true_drives"
|
|
3
5
|
require "badline/computer/attachments"
|
|
4
6
|
require "badline/computer/kernal_traps"
|
|
5
7
|
require "badline/computer/saved_state"
|
|
@@ -8,16 +10,14 @@ module Badline
|
|
|
8
10
|
class Computer
|
|
9
11
|
include IntegerHelper
|
|
10
12
|
include KeyboardBuffer
|
|
13
|
+
include Machine::Clocking
|
|
14
|
+
include Common
|
|
11
15
|
include Attachments
|
|
12
16
|
include KernalTraps
|
|
13
17
|
include SavedState
|
|
14
18
|
|
|
15
19
|
attr_reader :address_bus, :cpu, :cycles, :drive1541
|
|
16
20
|
|
|
17
|
-
# The path of the disk or directory device 8 serves through the traps
|
|
18
|
-
# (Attachments), or an empty one.
|
|
19
|
-
def mounted_path = @drive.nil? ? "" : @drive.path
|
|
20
|
-
|
|
21
21
|
def family = :c64
|
|
22
22
|
|
|
23
23
|
def region = address_bus.region
|
|
@@ -77,6 +77,7 @@ module Badline
|
|
|
77
77
|
@serial_trap = nil
|
|
78
78
|
@save_trap = nil
|
|
79
79
|
@drive1541 = nil
|
|
80
|
+
@drive1581 = nil
|
|
80
81
|
plug_serial_bus
|
|
81
82
|
@reu = reu ? plug_reu(reu) : nil
|
|
82
83
|
end
|
|
@@ -101,50 +102,22 @@ module Badline
|
|
|
101
102
|
watch_freeze if @freezing
|
|
102
103
|
clock_cpu
|
|
103
104
|
@drive1541&.host_cycle!
|
|
105
|
+
@drive1581&.host_cycle!
|
|
104
106
|
|
|
105
107
|
@cycles += 1
|
|
106
108
|
end
|
|
107
109
|
|
|
108
|
-
# Runs `count` cycles, one #cycle! after another.
|
|
109
|
-
def run_cycles(count)
|
|
110
|
-
i = 0
|
|
111
|
-
while i < count
|
|
112
|
-
cycle!
|
|
113
|
-
i += 1
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# Runs cycles until the block returns true or the cycle count passes
|
|
118
|
-
# `limit`, checking before each cycle.
|
|
119
|
-
def run_until(limit)
|
|
120
|
-
cycle! until yield || @cycles > limit
|
|
121
|
-
end
|
|
122
|
-
|
|
123
110
|
# The cycle at which #on_init's handlers run, once the KERNAL has booted.
|
|
124
111
|
def init_threshold
|
|
125
112
|
INIT_THRESHOLD
|
|
126
113
|
end
|
|
127
114
|
|
|
128
|
-
# The clock, the raster and the crop of the machine's region, a pixel
|
|
129
|
-
# to a window pixel.
|
|
130
|
-
def timing
|
|
131
|
-
region = address_bus.region
|
|
132
|
-
Timing.new(clock_hz: region.clock_hz, cycles_per_line: region.cycles_per_line,
|
|
133
|
-
lines_per_frame: region.lines_per_frame, crop: region.crop, pixel_width: 1)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
115
|
# The chip whose #display a front end shows.
|
|
137
116
|
def video = @vic
|
|
138
117
|
|
|
139
118
|
# The chip a front end records the machine's sound from.
|
|
140
119
|
def sound_source = @sid
|
|
141
120
|
|
|
142
|
-
def load_prg(data)
|
|
143
|
-
uint16(data[0], data[1]).tap do |load_addr|
|
|
144
|
-
ram.write(load_addr, data[2..])
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
|
|
148
121
|
def attach_cartridge(cartridge)
|
|
149
122
|
connect_cartridge(cartridge)
|
|
150
123
|
power_cycle!
|
|
@@ -174,6 +147,7 @@ module Badline
|
|
|
174
147
|
@dma = false
|
|
175
148
|
@drive&.reset!
|
|
176
149
|
@drive1541&.reset!
|
|
150
|
+
@drive1581&.reset!
|
|
177
151
|
@freezing = false
|
|
178
152
|
@nmi_asserted = false
|
|
179
153
|
cpu.reset!
|
|
@@ -204,37 +178,12 @@ module Badline
|
|
|
204
178
|
# The one-shot has pulsed already, so letting go does nothing.
|
|
205
179
|
def release_restore; end
|
|
206
180
|
|
|
207
|
-
def capture_output
|
|
208
|
-
@capture_output ||= ChroutTrap.new(cpu:, bus: address_bus, layout: KernalTrap::C64_LAYOUT).tap do |trap|
|
|
209
|
-
cpu.install_trap(ChroutTrap::ADDRESS) { trap.call }
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
def inspect
|
|
214
|
-
"#<#{self.class.name} cycles=#{@cycles} cpu=(#{@cpu.inspect})>"
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
def on_init(&block)
|
|
218
|
-
if booting?
|
|
219
|
-
@init_handlers << block
|
|
220
|
-
else
|
|
221
|
-
block.call
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
|
|
225
181
|
private
|
|
226
182
|
|
|
227
|
-
# The KERNAL the traps stand in for (
|
|
183
|
+
# The KERNAL the traps stand in for (KernalTraps#mount).
|
|
228
184
|
def trap_layout = KernalTrap::C64_LAYOUT
|
|
229
185
|
|
|
230
|
-
|
|
231
|
-
# key, and the CPU takes an interrupt on its falling edge.
|
|
232
|
-
def drive_nmi
|
|
233
|
-
nmi = @cia2.interrupted? || @cartridge_nmi || @restore_pulse
|
|
234
|
-
@restore_pulse = false
|
|
235
|
-
@cpu.nmi = true if nmi && !@nmi_asserted
|
|
236
|
-
@nmi_asserted = nmi
|
|
237
|
-
end
|
|
186
|
+
def trap_bus = address_bus
|
|
238
187
|
|
|
239
188
|
def plug_reu(size_kb)
|
|
240
189
|
reu = REU.new(size_kb, bus: @address_bus, vic: @vic)
|
|
@@ -274,13 +223,5 @@ module Badline
|
|
|
274
223
|
@freezing = false
|
|
275
224
|
address_bus.cartridge.freeze!
|
|
276
225
|
end
|
|
277
|
-
|
|
278
|
-
def booting?
|
|
279
|
-
@cycles < init_threshold
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
def handle_init
|
|
283
|
-
@init_handlers.each(&:call)
|
|
284
|
-
end
|
|
285
226
|
end
|
|
286
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"
|
|
@@ -38,8 +38,11 @@ require "badline/vic"
|
|
|
38
38
|
require "badline/keyboard_buffer"
|
|
39
39
|
require "badline/iec_bus"
|
|
40
40
|
require "badline/drive1541"
|
|
41
|
+
require "badline/drive1581"
|
|
42
|
+
require "badline/drive1581/slot"
|
|
41
43
|
require "badline/snapshot/state"
|
|
42
44
|
require "badline/snapshot/setup"
|
|
45
|
+
require "badline/machine"
|
|
43
46
|
require "badline/computer"
|
|
44
47
|
require "badline/vic20"
|
|
45
48
|
require "badline/checkpoint"
|
|
@@ -48,4 +51,3 @@ require "badline/cartridge"
|
|
|
48
51
|
require "badline/kernal_trap"
|
|
49
52
|
require "badline/chrout_trap"
|
|
50
53
|
require "badline/media"
|
|
51
|
-
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
|