badline 0.11.0 → 0.12.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 +26 -0
- data/CLAUDE.md +24 -6
- data/CONTRIBUTING.md +15 -9
- data/README.md +16 -5
- data/doc/media.md +28 -1
- data/doc/pinned-behaviour.md +31 -1
- data/exe/badline-ruby +1 -1
- data/lib/badline/c128/bus.rb +4 -17
- data/lib/badline/c128/drives.rb +108 -0
- data/lib/badline/c128/fast_serial.rb +63 -0
- data/lib/badline/c128/io_pages.rb +35 -0
- data/lib/badline/c128/mmu.rb +24 -7
- data/lib/badline/c128/modes.rb +17 -4
- data/lib/badline/c128/saved_state.rb +23 -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 +25 -26
- 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 +60 -0
- data/lib/badline/cia/serial.rb +12 -0
- data/lib/badline/cia/timer.rb +39 -0
- data/lib/badline/cia.rb +10 -5
- data/lib/badline/computer/attachments.rb +4 -50
- data/lib/badline/computer/true_drives.rb +80 -0
- data/lib/badline/computer.rb +4 -0
- data/lib/badline/core.rb +2 -0
- data/lib/badline/drive/core.rb +147 -0
- data/lib/badline/drive/fast_serial.rb +73 -0
- data/lib/badline/{drive1541 → drive}/idle.rb +18 -29
- data/lib/badline/drive/idle_cpu.rb +28 -0
- data/lib/badline/{drive1541 → drive}/orbit.rb +3 -5
- data/lib/badline/drive/serial_lines.rb +73 -0
- data/lib/badline/{drive1541 → drive}/sleep.rb +4 -5
- data/lib/badline/drive/vias.rb +84 -0
- data/lib/badline/drive/watch.rb +150 -0
- data/lib/badline/drive.rb +11 -0
- data/lib/badline/drive1541/bus.rb +5 -129
- data/lib/badline/drive1541/byte_ready.rb +48 -0
- data/lib/badline/drive1541/cpu.rb +1 -19
- data/lib/badline/drive1541/disk.rb +77 -20
- data/lib/badline/drive1541/disk_state.rb +7 -2
- data/lib/badline/drive1541/disk_via.rb +3 -0
- data/lib/badline/drive1541/mechanism.rb +48 -14
- data/lib/badline/drive1541/mechanism_state.rb +5 -4
- data/lib/badline/drive1541/rotation.rb +5 -5
- data/lib/badline/drive1541/serial_port.rb +4 -60
- data/lib/badline/drive1541.rb +14 -172
- data/lib/badline/drive1571/bus.rb +111 -0
- data/lib/badline/drive1571/cpu.rb +11 -0
- data/lib/badline/drive1571/serial_port.rb +37 -0
- data/lib/badline/drive1571/serial_via.rb +25 -0
- data/lib/badline/drive1571/wd1770.rb +120 -0
- data/lib/badline/drive1571.rb +175 -0
- 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 +2 -4
- data/lib/badline/frontend/audio_device.rb +67 -0
- data/lib/badline/frontend/audio_sink.rb +16 -50
- data/lib/badline/frontend/boot.rb +18 -10
- data/lib/badline/frontend/buttons.rb +7 -7
- data/lib/badline/frontend/drive_led.rb +1 -1
- 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} +7 -9
- 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 +68 -6
- data/lib/badline/machine.rb +9 -6
- data/lib/badline/media/boot_disk.rb +50 -0
- data/lib/badline/media/not_disk.rb +8 -0
- data/lib/badline/media/true_drive.rb +48 -25
- data/lib/badline/media.rb +9 -3
- data/lib/badline/options/event.rb +2 -2
- data/lib/badline/options/family.rb +3 -9
- data/lib/badline/options/help.rb +3 -1
- data/lib/badline/options/table.rb +1 -1
- data/lib/badline/roms/README +18 -0
- data/lib/badline/roms/dos1571.rom +0 -0
- data/lib/badline/roms/dos1581.rom +0 -0
- data/lib/badline/sdl.rb +37 -20
- data/lib/badline/snapshot/state.rb +5 -4
- data/lib/badline/storage/d64_image.rb +3 -0
- data/lib/badline/storage/d71_image.rb +9 -4
- data/lib/badline/storage/g64_image.rb +7 -2
- data/lib/badline/storage/p00.rb +8 -0
- data/lib/badline/time_of_day.rb +9 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/via/fast_forward.rb +3 -3
- data/lib/badline/vic20/attachments.rb +23 -3
- data/lib/badline/vic20/saved_state.rb +2 -0
- data/lib/badline/vic20.rb +3 -0
- data/lib/badline/z80/arithmetic.rb +117 -0
- data/lib/badline/z80/bit_page.rb +55 -0
- data/lib/badline/z80/block_io.rb +58 -0
- data/lib/badline/z80/block_transfers.rb +59 -0
- data/lib/badline/z80/bus_cycles.rb +111 -0
- data/lib/badline/z80/core.rb +113 -0
- data/lib/badline/z80/exchanges.rb +50 -0
- data/lib/badline/z80/extended_page.rb +89 -0
- data/lib/badline/z80/flags.rb +37 -0
- data/lib/badline/z80/interrupt_acceptance.rb +46 -0
- data/lib/badline/z80/jumps.rb +82 -0
- data/lib/badline/z80/loads.rb +98 -0
- data/lib/badline/z80/main_page.rb +134 -0
- data/lib/badline/z80/registers.rb +120 -0
- data/lib/badline/z80/saved_state.rb +69 -0
- data/lib/badline/z80/shifts.rb +64 -0
- data/lib/badline/z80.rb +11 -0
- metadata +89 -24
- /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/iec_bus.rb
CHANGED
|
@@ -15,7 +15,17 @@ module Badline
|
|
|
15
15
|
# A drive drives CLK and DATA from VIA 1's PB3 and PB1, the same way. It
|
|
16
16
|
# also pulls DATA while ATN IN differs from its ATN acknowledge on PB4
|
|
17
17
|
# (an XOR gate), which answers ATN in hardware before the DOS gets to it.
|
|
18
|
-
# SerialPort reads the lines back into VIA 1.
|
|
18
|
+
# SerialPort reads the lines back into VIA 1. A drive whose
|
|
19
|
+
# serial_output sets DRIVE_ATN_GATED pulls DATA that way only while
|
|
20
|
+
# ATN is low, as the 1581's NAND does.
|
|
21
|
+
#
|
|
22
|
+
# Fast serial adds SRQ, which the C64 leaves alone. A C128 and a 1571
|
|
23
|
+
# clock bytes over it from a CIA's CNT, with the bits on DATA from its
|
|
24
|
+
# SP, through buffers each side turns towards the bus to send. A pin low
|
|
25
|
+
# pulls its line low. Each side pushes the lines it pulls when they
|
|
26
|
+
# move: the host through host_fast_lines=, which tells the drives
|
|
27
|
+
# (fast_lines_moved), and a drive through drives_fast_moved!, which
|
|
28
|
+
# tells the host (on_fast_change).
|
|
19
29
|
class IECBus
|
|
20
30
|
# CIA 2 port A
|
|
21
31
|
HOST_ATN_OUT = 0x08
|
|
@@ -29,16 +39,60 @@ module Badline
|
|
|
29
39
|
DRIVE_CLK_OUT = 0x08
|
|
30
40
|
DRIVE_ATNA = 0x10
|
|
31
41
|
|
|
32
|
-
# The lines pulled low, as ATN, CLK and
|
|
42
|
+
# The lines pulled low, as ATN, CLK, DATA and SRQ bits
|
|
33
43
|
ATN = 0x01
|
|
34
44
|
CLK = 0x02
|
|
35
45
|
DATA = 0x04
|
|
46
|
+
SRQ = 0x08
|
|
47
|
+
|
|
48
|
+
# A drive's serial_output bits above VIA 1's port B: its fast serial
|
|
49
|
+
# pins pulling DATA and SRQ.
|
|
50
|
+
DRIVE_FAST_DATA = 0x100
|
|
51
|
+
DRIVE_FAST_SRQ = 0x200
|
|
52
|
+
|
|
53
|
+
# A drive's serial_output bit whose ATN acknowledge pulls DATA only
|
|
54
|
+
# while ATN is low.
|
|
55
|
+
DRIVE_ATN_GATED = 0x400
|
|
36
56
|
|
|
37
57
|
attr_reader :drives, :host_lines
|
|
38
58
|
|
|
59
|
+
# The lines the host's fast serial pins pull, as DATA and SRQ bits.
|
|
60
|
+
attr_reader :host_fast_lines
|
|
61
|
+
|
|
39
62
|
def initialize
|
|
40
63
|
@drives = []
|
|
41
64
|
@host_lines = 0
|
|
65
|
+
@host_fast_lines = 0
|
|
66
|
+
@drive_fast_lines = 0
|
|
67
|
+
@on_fast_change = nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# The host's fast serial pins moved: +low+ is the lines they pull, as
|
|
71
|
+
# DATA and SRQ bits.
|
|
72
|
+
def host_fast_lines=(low)
|
|
73
|
+
return if low == @host_fast_lines
|
|
74
|
+
|
|
75
|
+
@host_fast_lines = low
|
|
76
|
+
@drives.each(&:fast_lines_moved)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# A drive's fast serial pins moved (serial_output's fast bits).
|
|
80
|
+
def drives_fast_moved!
|
|
81
|
+
low = 0
|
|
82
|
+
@drives.each do |drive|
|
|
83
|
+
output = drive.serial_output
|
|
84
|
+
low |= DATA if output.anybits?(DRIVE_FAST_DATA)
|
|
85
|
+
low |= SRQ if output.anybits?(DRIVE_FAST_SRQ)
|
|
86
|
+
end
|
|
87
|
+
return if low == @drive_fast_lines
|
|
88
|
+
|
|
89
|
+
@drive_fast_lines = low
|
|
90
|
+
@on_fast_change&.call
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Calls the block whenever the drives move their fast serial pins.
|
|
94
|
+
def on_fast_change(&block)
|
|
95
|
+
@on_fast_change = block
|
|
42
96
|
end
|
|
43
97
|
|
|
44
98
|
# The machine pushes the lines it pulls, as port A bits, whenever they
|
|
@@ -58,18 +112,20 @@ module Badline
|
|
|
58
112
|
end
|
|
59
113
|
|
|
60
114
|
# One pass over everything on the bus, for a reader that wants more
|
|
61
|
-
# than one line. +host+ is the C64's port A lines as the
|
|
62
|
-
# them, which a drive takes a cycle late (see
|
|
115
|
+
# than one line, SRQ included. +host+ is the C64's port A lines as the
|
|
116
|
+
# reader sees them, which a drive takes a cycle late (see
|
|
117
|
+
# Drive::SerialLines).
|
|
63
118
|
def low_lines(host = @host_lines)
|
|
64
119
|
atn = host.anybits?(HOST_ATN_OUT)
|
|
65
|
-
low =
|
|
120
|
+
low = @host_fast_lines | @drive_fast_lines
|
|
121
|
+
low |= ATN if atn
|
|
66
122
|
low |= CLK if host.anybits?(HOST_CLK_OUT)
|
|
67
123
|
low |= DATA if host.anybits?(HOST_DATA_OUT)
|
|
68
124
|
i = 0
|
|
69
125
|
while i < @drives.length
|
|
70
126
|
drive = @drives[i].serial_output
|
|
71
127
|
low |= CLK if drive.anybits?(DRIVE_CLK_OUT)
|
|
72
|
-
low |= DATA if drive.anybits?(DRIVE_DATA_OUT) ||
|
|
128
|
+
low |= DATA if drive.anybits?(DRIVE_DATA_OUT) || acknowledging?(drive, atn)
|
|
73
129
|
i += 1
|
|
74
130
|
end
|
|
75
131
|
low
|
|
@@ -77,6 +133,12 @@ module Badline
|
|
|
77
133
|
|
|
78
134
|
def atn_low?(host = @host_lines) = host.anybits?(HOST_ATN_OUT)
|
|
79
135
|
|
|
136
|
+
# Whether a drive's ATN acknowledge pulls DATA, with its serial_output
|
|
137
|
+
# +drive+, while ATN is +atn+.
|
|
138
|
+
def acknowledging?(drive, atn)
|
|
139
|
+
atn != drive.anybits?(DRIVE_ATNA) && (atn || drive.nobits?(DRIVE_ATN_GATED))
|
|
140
|
+
end
|
|
141
|
+
|
|
80
142
|
def clk_low? = low_lines.anybits?(CLK)
|
|
81
143
|
|
|
82
144
|
def data_low? = low_lines.anybits?(DATA)
|
data/lib/badline/machine.rb
CHANGED
|
@@ -24,8 +24,10 @@ module Badline
|
|
|
24
24
|
# - Media: +ram+, +load_prg+, +type_text+, +mount+, +unmount+,
|
|
25
25
|
# +mounted?+, +datasette+, +attach_cartridge+,
|
|
26
26
|
# +press_cartridge_button+, +release_cartridge_button+,
|
|
27
|
-
# +attach_drive1541+, +drive1541+, and +
|
|
28
|
-
#
|
|
27
|
+
# +attach_drive1541+, +drive1541+, +true_drive+ and +plug_true_drive+
|
|
28
|
+
# for the drive Media::TrueDrive plugs in, +attach_drive1581+,
|
|
29
|
+
# +drive1581+ and +plug_drive1581+ for a 1581 (Drive1581::Slot), and
|
|
30
|
+
# +address_bus+ for its +cartridge+ and +detach_cartridge+.
|
|
29
31
|
# - Snapshots and tests: +snapshot+, +restore+, +save_snapshot+,
|
|
30
32
|
# +install_debug_register+ and +capture_output+.
|
|
31
33
|
#
|
|
@@ -33,14 +35,15 @@ module Badline
|
|
|
33
35
|
# input parts, with its one joystick as both +joystick1+ and +joystick2+
|
|
34
36
|
# and nil for +control_ports+, and of the rest +ram+, +load_prg+,
|
|
35
37
|
# +type_text+, +mount+, +unmount+, +mounted?+, +datasette+, +attach_cartridge+,
|
|
36
|
-
# +attach_drive1541+, +drive1541+, +
|
|
37
|
-
# +capture_output+.
|
|
38
|
+
# +attach_drive1541+, +drive1541+, +true_drive+, +plug_true_drive+, the
|
|
39
|
+
# 1581's, +install_debug_register+ and +capture_output+.
|
|
38
40
|
#
|
|
39
41
|
# The C128 answers all but the cartridge button, and besides +mode+,
|
|
40
42
|
# :c64, as Machine.build builds it, or :c128, +vdc+, the VDC a front
|
|
41
43
|
# end can show in place of +video+ once +vdc_shown=+ renders it,
|
|
42
|
-
# +press_caps_lock+, +release_caps_lock+, +press_display_key
|
|
43
|
-
# +release_display_key
|
|
44
|
+
# +press_caps_lock+, +release_caps_lock+, +press_display_key+,
|
|
45
|
+
# +release_display_key+, and +attach_drive1571+ and +drive1571+ for the
|
|
46
|
+
# C128D's 1571, its true drive.
|
|
44
47
|
module Machine
|
|
45
48
|
# A new machine of `family`, :c64, :vic20 or :c128, the model of it
|
|
46
49
|
# `model` names. A C64's model is one of Model::ALL, with its SID
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Media
|
|
5
|
+
# A C128 boot disk. At power-on the C128 KERNAL reads track 1, sector 0
|
|
6
|
+
# of the disk in device 8 and runs it when it starts with "CBM", as boot
|
|
7
|
+
# games have it. The 1581's DOS first loads and runs a USR file named
|
|
8
|
+
# COPYRIGHT CBM 86 when the disk has one, which is how CP/M's 1581 disks
|
|
9
|
+
# hand the KERNAL their boot sector, so a .d81 with that file boots too.
|
|
10
|
+
#
|
|
11
|
+
# On a C128 in C128 mode a boot disk goes in a true drive, a 1581 for a
|
|
12
|
+
# .d81 and the C128D's 1571 otherwise, and nothing is typed, so the
|
|
13
|
+
# KERNAL boots it. Other disks, and other machines, keep the traps and
|
|
14
|
+
# the autostart.
|
|
15
|
+
module BootDisk
|
|
16
|
+
SIGNATURE = "CBM".bytes.freeze
|
|
17
|
+
AUTO_BOOT = "copyright cbm 86"
|
|
18
|
+
TYPES = %w[.d64 .d71 .d81].freeze
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
def takes?(computer, path)
|
|
22
|
+
computer.family == :c128 && computer.mode == :c128 && boot?(path)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Puts a disk that boots on the computer in a true drive as device
|
|
26
|
+
# 8, write-protected with `disk: { read_only: true }`, for the KERNAL
|
|
27
|
+
# to boot at power-on, and says so. Nil for any other disk.
|
|
28
|
+
def attach(computer, path, disk: {}, **)
|
|
29
|
+
return unless takes?(computer, path)
|
|
30
|
+
|
|
31
|
+
read_only = disk.fetch(:read_only, false)
|
|
32
|
+
"#{TrueDrive.insert(computer, path, read_only:)}, to boot at power-on"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Whether the disk image at path boots: its boot sector, or on a
|
|
36
|
+
# .d81 the 1581's auto-boot file.
|
|
37
|
+
def boot?(path)
|
|
38
|
+
type = File.extname(path).downcase
|
|
39
|
+
return false unless TYPES.include?(type) && File.file?(path)
|
|
40
|
+
|
|
41
|
+
image = MOUNT_TYPES.fetch(type).new(path, read_only: true)
|
|
42
|
+
block = image.read_block(1, 0)
|
|
43
|
+
return true if block && block[0, 3] == SIGNATURE
|
|
44
|
+
|
|
45
|
+
type == ".d81" && !image.read_file(AUTO_BOOT, type: :usr).nil?
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -2,51 +2,74 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
module Media
|
|
5
|
-
# Device 8 as a true
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
5
|
+
# Device 8 as a true drive instead of the KERNAL traps: a 1541, or on
|
|
6
|
+
# the C128 its 1571, or a 1581. Once a true drive is plugged in as
|
|
7
|
+
# device 8, disk media go into it rather than into the traps, so a LOAD
|
|
8
|
+
# runs the DOS and every byte crosses the serial bus. A .g64 holds the
|
|
9
|
+
# disk's raw GCR, which only a true drive reads, so it plugs one in
|
|
10
|
+
# when there's none yet. The 1541 reads only .d64 and .g64 images, the
|
|
11
|
+
# 1571 .d71 and .g71 as well, and the 1581 only .d81, so a .d81 swaps
|
|
12
|
+
# the drive on device 8 for a 1581 (Drive1581::Slot), and another disk
|
|
13
|
+
# image swaps a 1581 back for the machine's own drive. The traps' other
|
|
14
|
+
# disk media, a .t64 or a host directory, raise Error.
|
|
12
15
|
module TrueDrive
|
|
13
16
|
class Error < ArgumentError; end
|
|
14
17
|
|
|
15
18
|
class << self
|
|
16
|
-
# Plugs
|
|
17
|
-
# returns it.
|
|
19
|
+
# Plugs the machine's true drive in as device 8, unless one is there
|
|
20
|
+
# already, and returns it.
|
|
18
21
|
def plug(computer)
|
|
19
|
-
drive(computer) ||
|
|
22
|
+
drive(computer) || computer.plug_true_drive
|
|
20
23
|
end
|
|
21
24
|
|
|
22
|
-
# The
|
|
25
|
+
# The true drive on device 8, or nil.
|
|
23
26
|
def drive(computer)
|
|
24
|
-
drive = computer.
|
|
27
|
+
drive = computer.true_drive
|
|
25
28
|
drive if drive && drive.device == KernalTrap::Routine::DEVICE
|
|
26
29
|
end
|
|
27
30
|
|
|
28
|
-
# Whether the path goes in a true drive: a .g64 always, and
|
|
29
|
-
# disk media, which the traps would mount, when a true drive
|
|
30
|
-
# device 8.
|
|
31
|
+
# Whether the path goes in a true drive: a .g64 or .g71 always, and
|
|
32
|
+
# other disk media, which the traps would mount, when a true drive
|
|
33
|
+
# is device 8.
|
|
31
34
|
def takes?(computer, path)
|
|
32
|
-
return true if File.extname(path).
|
|
35
|
+
return true if %w[.g64 .g71].include?(File.extname(path).downcase)
|
|
33
36
|
|
|
34
37
|
!drive(computer).nil? && (File.directory?(path) || MOUNT_TYPES.key?(File.extname(path).downcase))
|
|
35
38
|
end
|
|
36
39
|
|
|
37
|
-
# Puts the
|
|
38
|
-
# when there'
|
|
39
|
-
# traps, so LOAD and SAVE reach the drive too.
|
|
40
|
-
# in write-protected.
|
|
40
|
+
# Puts the image in the drive on device 8, plugging one in that
|
|
41
|
+
# reads it when the drive there doesn't, and takes out what was
|
|
42
|
+
# mounted through the traps, so LOAD and SAVE reach the drive too.
|
|
43
|
+
# `read_only` puts it in write-protected.
|
|
41
44
|
def insert(computer, path, read_only: false)
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
return insert_d81(computer, path, read_only) if File.extname(path).casecmp?(".d81")
|
|
46
|
+
|
|
47
|
+
drive = own_drive(computer)
|
|
48
|
+
name = drive&.model_name || (computer.family == :c128 ? "1571" : "1541")
|
|
49
|
+
types = name == "1571" ? %w[.d64 .g64 .d71 .g71] : %w[.d64 .g64]
|
|
50
|
+
unless types.include?(File.extname(path).downcase)
|
|
51
|
+
raise Error, "#{path} is not a #{types.join(', ')} or .d81 image, which the #{name} and the 1581 read"
|
|
44
52
|
end
|
|
45
53
|
|
|
46
|
-
|
|
54
|
+
(drive || computer.plug_true_drive).insert_image(path, read_only:)
|
|
55
|
+
computer.unmount
|
|
56
|
+
"Inserted #{path} in the #{name} as device 8"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
# The drive on device 8 unless it's a 1581.
|
|
62
|
+
def own_drive(computer)
|
|
63
|
+
drive = drive(computer)
|
|
64
|
+
drive unless drive&.model_name == "1581"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def insert_d81(computer, path, read_only)
|
|
68
|
+
drive = drive(computer)
|
|
69
|
+
drive = computer.plug_drive1581 unless drive&.model_name == "1581"
|
|
70
|
+
drive.insert_image(path, read_only:)
|
|
47
71
|
computer.unmount
|
|
48
|
-
|
|
49
|
-
"Inserted #{path} in the 1541 as device 8"
|
|
72
|
+
"Inserted #{path} in the 1581 as device 8"
|
|
50
73
|
end
|
|
51
74
|
end
|
|
52
75
|
end
|
data/lib/badline/media.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "badline/media/true_drive"
|
|
4
|
+
require "badline/media/boot_disk"
|
|
5
|
+
require "badline/media/not_disk"
|
|
4
6
|
require "badline/media/queue"
|
|
5
7
|
require "badline/media/disk_set"
|
|
6
8
|
require "badline/media/disk_list"
|
|
@@ -32,6 +34,9 @@ module Badline
|
|
|
32
34
|
# there, a .d64 goes into it instead of the KERNAL traps too. The
|
|
33
35
|
# autostart then loads through it.
|
|
34
36
|
#
|
|
37
|
+
# On a C128 in C128 mode, a boot disk goes in a true drive for the
|
|
38
|
+
# KERNAL to boot at power-on, with nothing typed (BootDisk).
|
|
39
|
+
#
|
|
35
40
|
# An .m3u or .vfl list of disks attaches the first disk it lists
|
|
36
41
|
# (DiskList). The VIC-20 takes its own media (Vic20Media), and the
|
|
37
42
|
# C128 in C64 mode a C64's.
|
|
@@ -39,7 +44,7 @@ module Badline
|
|
|
39
44
|
path = DiskList.disk(path)
|
|
40
45
|
return Vic20Media.attach(computer, path, autostart:, **) if computer.family == :vic20
|
|
41
46
|
|
|
42
|
-
attach_c64(computer, path, autostart:, subtune:, **)
|
|
47
|
+
BootDisk.attach(computer, path, **) || attach_c64(computer, path, autostart:, subtune:, **)
|
|
43
48
|
end
|
|
44
49
|
|
|
45
50
|
# Swaps the disk in device 8 for a disk image or a host directory,
|
|
@@ -50,11 +55,12 @@ module Badline
|
|
|
50
55
|
# A .g64 goes in a true 1541, which is plugged in as device 8 when
|
|
51
56
|
# there's none yet (TrueDrive). Once a true 1541 is device 8, a .d64
|
|
52
57
|
# goes in its drive as well, and other disks, which a 1541 can't
|
|
53
|
-
# read, raise TrueDrive::Error, an ArgumentError.
|
|
58
|
+
# read, raise TrueDrive::Error, an ArgumentError. Anything else
|
|
59
|
+
# raises NotDisk.
|
|
54
60
|
def insert_disk(computer, path, read_only: false)
|
|
55
61
|
return TrueDrive.insert(computer, path, read_only:) if TrueDrive.takes?(computer, path)
|
|
56
62
|
|
|
57
|
-
raise
|
|
63
|
+
raise NotDisk, "#{path} is not a disk image or a directory" unless disk?(path)
|
|
58
64
|
|
|
59
65
|
computer.mount(open_storage(path, { read_only: }))
|
|
60
66
|
"Inserted #{path} in device 8"
|
|
@@ -23,7 +23,7 @@ module Badline
|
|
|
23
23
|
|
|
24
24
|
# What insert takes: disk images, tapes and cartridges, or an .m3u
|
|
25
25
|
# or .vfl list of disks or a directory to mount as device 8.
|
|
26
|
-
INSERTABLE = %w[.d64 .d71 .d81 .g64 .t64 .tap .crt].freeze
|
|
26
|
+
INSERTABLE = %w[.d64 .d71 .d81 .g64 .t64 .tap .crt .m3u .vfl].freeze
|
|
27
27
|
|
|
28
28
|
attr_reader :frame, :action, :argument
|
|
29
29
|
|
|
@@ -95,7 +95,7 @@ module Badline
|
|
|
95
95
|
def self.insertable?(path)
|
|
96
96
|
return true if File.directory?(path)
|
|
97
97
|
|
|
98
|
-
File.exist?(path) &&
|
|
98
|
+
File.exist?(path) && INSERTABLE.include?(File.extname(path).downcase)
|
|
99
99
|
end
|
|
100
100
|
end
|
|
101
101
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "badline/storage/p00"
|
|
4
|
+
|
|
3
5
|
module Badline
|
|
4
6
|
class Options
|
|
5
7
|
# The machine family: the subcommand that names it, or else the media,
|
|
@@ -37,15 +39,7 @@ module Badline
|
|
|
37
39
|
return if @family_named || @sid_command || @media_path.nil?
|
|
38
40
|
return unless %w[.prg .p00].include?(File.extname(@media_path).downcase) && File.file?(@media_path)
|
|
39
41
|
|
|
40
|
-
@family = :c128 if load_address(@media_path) == C128_BASIC_START
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# The load address of a .prg, or of the program a .p00 wraps after its
|
|
44
|
-
# 26-byte header.
|
|
45
|
-
def load_address(path)
|
|
46
|
-
offset = File.extname(path).casecmp?(".p00") ? 26 : 0
|
|
47
|
-
bytes = File.binread(path).bytes
|
|
48
|
-
bytes.size >= offset + 2 ? bytes[offset] | (bytes[offset + 1] << 8) : -1
|
|
42
|
+
@family = :c128 if Storage::P00.load_address(@media_path) == C128_BASIC_START
|
|
49
43
|
end
|
|
50
44
|
end
|
|
51
45
|
end
|
data/lib/badline/options/help.rb
CHANGED
|
@@ -60,7 +60,9 @@ module Badline
|
|
|
60
60
|
window also autosaves every two minutes of play, which only the
|
|
61
61
|
pause menu loads. Device 8 answers through traps on the KERNAL's
|
|
62
62
|
disk routines, unless --true-drive puts an emulated 1541 there,
|
|
63
|
-
which runs its own DOS and reads .d64 and .g64 images only
|
|
63
|
+
which runs its own DOS and reads .d64 and .g64 images only, or on
|
|
64
|
+
the C128 the C128D's 1571, which reads .d71 and .g71 too. A .d81
|
|
65
|
+
then goes in an emulated 1581 in its place. A .sid
|
|
64
66
|
tune opens in the SID player, as `sid` plays it, unless an option
|
|
65
67
|
of the emulator's window asks for the machine.
|
|
66
68
|
|
|
@@ -35,7 +35,7 @@ module Badline
|
|
|
35
35
|
Option.new("--sound", "Play the SID through the host's audio device (F10 mutes)",
|
|
36
36
|
section: "Window options", needs: :window),
|
|
37
37
|
Option.new("--no-sound", "Don't play the SID", section: "Window options", needs: :window, build: :native),
|
|
38
|
-
Option.new("--true-drive", "Put a true 1541 on device 8 instead of the KERNAL traps",
|
|
38
|
+
Option.new("--true-drive", "Put a true 1541 on device 8 instead of the KERNAL traps, a 1571 on the C128",
|
|
39
39
|
section: "Window options", needs: :window),
|
|
40
40
|
Option.new("--reu SIZE", "Plug in an REU of SIZE K: 128, 256, 512 (a 1750) or up to 16384",
|
|
41
41
|
section: "Window options", needs: :window),
|
data/lib/badline/roms/README
CHANGED
|
@@ -61,6 +61,24 @@ https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/new/1541/
|
|
|
61
61
|
(1541-c000.325302-01.bin and 1541-e000.901229-05.bin) and concatenated.
|
|
62
62
|
SHA-1 ab16f56989b27d89babe5f89c5a8cb3da71a82f0.
|
|
63
63
|
|
|
64
|
+
dos1571.rom
|
|
65
|
+
-----------
|
|
66
|
+
The 1571 disk drive's DOS 3.0, 310654-05, 32 KB for $8000-$FFFF.
|
|
67
|
+
Drive1571 loads it.
|
|
68
|
+
|
|
69
|
+
Downloaded from
|
|
70
|
+
https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/new/1571/1571-rom.310654-05.bin
|
|
71
|
+
SHA-1 f1be619c106641a685f6609e4d43d6fc9eac1e70.
|
|
72
|
+
|
|
73
|
+
dos1581.rom
|
|
74
|
+
-----------
|
|
75
|
+
The 1581 disk drive's DOS, 318045-02, 32 KB for $8000-$FFFF.
|
|
76
|
+
Drive1581 loads it.
|
|
77
|
+
|
|
78
|
+
Downloaded from
|
|
79
|
+
https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/new/1581/1581-rom.318045-02.bin
|
|
80
|
+
SHA-1 01228eae6f066bd9b7b2b6a7fa3f667e41dad393.
|
|
81
|
+
|
|
64
82
|
Commodore VIC-20 ROMs
|
|
65
83
|
=====================
|
|
66
84
|
|
|
Binary file
|
|
Binary file
|
data/lib/badline/sdl.rb
CHANGED
|
@@ -59,23 +59,6 @@ module Badline
|
|
|
59
59
|
ffi_func :SDL_PushEvent, [:ptr], :int
|
|
60
60
|
ffi_func :SDL_GetKeyName, [:int], :str
|
|
61
61
|
|
|
62
|
-
ffi_buffer :event, 56
|
|
63
|
-
ffi_read_u32 :event_type, 0
|
|
64
|
-
ffi_read_u8 :event_repeat, 13
|
|
65
|
-
ffi_read_i32 :event_scancode, 16
|
|
66
|
-
ffi_read_i32 :event_sym, 20
|
|
67
|
-
ffi_read_u16 :event_mod, 24
|
|
68
|
-
# SDL_MouseMotionEvent's xrel and yrel, and SDL_MouseButtonEvent's
|
|
69
|
-
# button.
|
|
70
|
-
ffi_read_i32 :event_xrel, 28
|
|
71
|
-
ffi_read_i32 :event_yrel, 32
|
|
72
|
-
ffi_read_u8 :event_button, 16
|
|
73
|
-
# Where the pointer is, in a motion or button event.
|
|
74
|
-
ffi_read_i32 :event_x, 20
|
|
75
|
-
ffi_read_i32 :event_y, 24
|
|
76
|
-
# SDL_DropEvent's file, which the receiver frees.
|
|
77
|
-
ffi_read_ptr :event_file, 8
|
|
78
|
-
|
|
79
62
|
ffi_buffer :rect, 16
|
|
80
63
|
ffi_write_i32 :rect_x, 0
|
|
81
64
|
ffi_write_i32 :rect_y, 4
|
|
@@ -115,19 +98,53 @@ module Badline
|
|
|
115
98
|
ffi_const :PIXELFORMAT_ARGB8888, 0x16362004
|
|
116
99
|
ffi_const :BLENDMODE_BLEND, 0x01
|
|
117
100
|
ffi_const :TEXTUREACCESS_STREAMING, 1
|
|
101
|
+
ffi_const :AUDIO_S16LSB, 0x8010
|
|
102
|
+
ffi_const :ALLOW_FREQUENCY_CHANGE, 0x01
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# The events: the SDL_Event that SDL_PollEvent fills, the fields the
|
|
106
|
+
# front end reads from it, the event types, the key modifier and
|
|
107
|
+
# keycodes, and the dropped file's path.
|
|
108
|
+
module SDL
|
|
109
|
+
ffi_buffer :event, 56
|
|
110
|
+
ffi_read_u32 :event_type, 0
|
|
111
|
+
ffi_read_u8 :event_repeat, 13
|
|
112
|
+
ffi_read_i32 :event_scancode, 16
|
|
113
|
+
ffi_read_i32 :event_sym, 20
|
|
114
|
+
ffi_read_u16 :event_mod, 24
|
|
115
|
+
# SDL_MouseMotionEvent's xrel and yrel, and SDL_MouseButtonEvent's
|
|
116
|
+
# button.
|
|
117
|
+
ffi_read_i32 :event_xrel, 28
|
|
118
|
+
ffi_read_i32 :event_yrel, 32
|
|
119
|
+
ffi_read_u8 :event_button, 16
|
|
120
|
+
# Where the pointer is, in a motion or button event.
|
|
121
|
+
ffi_read_i32 :event_x, 20
|
|
122
|
+
ffi_read_i32 :event_y, 24
|
|
123
|
+
# SDL_DropEvent's file, which the receiver frees.
|
|
124
|
+
ffi_read_ptr :event_file, 8
|
|
125
|
+
|
|
118
126
|
ffi_const :QUIT, 0x100
|
|
119
127
|
ffi_const :KEYDOWN, 0x300
|
|
120
128
|
ffi_const :KEYUP, 0x301
|
|
121
129
|
ffi_const :MOUSEMOTION, 0x400
|
|
122
130
|
ffi_const :MOUSEBUTTONDOWN, 0x401
|
|
123
131
|
ffi_const :MOUSEBUTTONUP, 0x402
|
|
124
|
-
ffi_const :
|
|
132
|
+
ffi_const :MOUSEWHEEL, 0x403
|
|
125
133
|
ffi_const :CONTROLLERDEVICEADDED, 0x653
|
|
126
134
|
ffi_const :CONTROLLERDEVICEREMOVED, 0x654
|
|
127
|
-
ffi_const :
|
|
128
|
-
ffi_const :
|
|
135
|
+
ffi_const :DROPFILE, 0x1000
|
|
136
|
+
ffi_const :KMOD_SHIFT, 0x0003
|
|
129
137
|
ffi_const :KEY_TAB, 0x09
|
|
130
138
|
ffi_const :KEY_F10, 0x4000_0043
|
|
139
|
+
|
|
140
|
+
# The path of the file the DROPFILE event in SDL.event carries, as a
|
|
141
|
+
# String, freeing SDL's copy.
|
|
142
|
+
def self.dropped_file
|
|
143
|
+
file = SDL.event_file(SDL.event)
|
|
144
|
+
path = LibC.strstr(file, "")
|
|
145
|
+
LibC.free(file)
|
|
146
|
+
path
|
|
147
|
+
end
|
|
131
148
|
end
|
|
132
149
|
|
|
133
150
|
# The few C library calls the native front end makes.
|
|
@@ -15,10 +15,11 @@ module Badline
|
|
|
15
15
|
# save_state writes something else, so a State in another layout
|
|
16
16
|
# fails before anything is read into a machine. Layout 3, without the
|
|
17
17
|
# KERNAL and datasette in the setup, layout 4, without the VIA shift
|
|
18
|
-
# register's clock delay,
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
# register's clock delay, layout 5, without the board in the setup,
|
|
19
|
+
# layout 6, without the C128's Z80, and layout 7, without a 1581 on the
|
|
20
|
+
# serial bus, still read.
|
|
21
|
+
SCHEMA = 8
|
|
22
|
+
READABLE = [3, 4, 5, 6, 7, SCHEMA].freeze
|
|
22
23
|
|
|
23
24
|
attr_reader :values, :strings
|
|
24
25
|
|
|
@@ -7,6 +7,15 @@ module Badline
|
|
|
7
7
|
|
|
8
8
|
def storage_kind = D71
|
|
9
9
|
|
|
10
|
+
# Tracks 1-35 on the first side and 36-70 on the second.
|
|
11
|
+
def track_count = 70
|
|
12
|
+
|
|
13
|
+
def sides = 2
|
|
14
|
+
|
|
15
|
+
def sectors_in(track)
|
|
16
|
+
track > 35 ? super(track - 35) : super
|
|
17
|
+
end
|
|
18
|
+
|
|
10
19
|
private
|
|
11
20
|
|
|
12
21
|
def error_tables = ERROR_TABLES
|
|
@@ -22,10 +31,6 @@ module Badline
|
|
|
22
31
|
def bam_bitmap(track) = track > 35 ? [53, 0, 3 * (track - 36)] : super
|
|
23
32
|
|
|
24
33
|
def double_sided? = @bytes[sector_offset(18, 0) + 3].anybits?(0x80)
|
|
25
|
-
|
|
26
|
-
def sectors_in(track)
|
|
27
|
-
track > 35 ? super(track - 35) : super
|
|
28
|
-
end
|
|
29
34
|
end
|
|
30
35
|
end
|
|
31
36
|
end
|
|
@@ -5,7 +5,9 @@ module Badline
|
|
|
5
5
|
# A .g64 image: a 1541 disk's flux as raw GCR, track by track, for the
|
|
6
6
|
# true drive to read the way it reads a real disk, copy protection and
|
|
7
7
|
# all. It has no file system of its own, so only the true drive
|
|
8
|
-
# (Drive1541::Disk.from_g64) can use it.
|
|
8
|
+
# (Drive1541::Disk.from_g64) can use it. A .g71 holds a 1571 disk the
|
|
9
|
+
# same way, with "GCR-1571" for its signature and 168 half tracks,
|
|
10
|
+
# the first side's 84 and then the second's.
|
|
9
11
|
#
|
|
10
12
|
# The header is the signature "GCR-1541", a version byte ($00), the
|
|
11
13
|
# number of half tracks the tables cover (84, tracks 1 to 42.5) and
|
|
@@ -36,6 +38,7 @@ module Badline
|
|
|
36
38
|
class FormatError < StandardError; end
|
|
37
39
|
|
|
38
40
|
SIGNATURE = "GCR-1541"
|
|
41
|
+
SIGNATURE_1571 = "GCR-1571"
|
|
39
42
|
HEADER_SIZE = 12
|
|
40
43
|
HALF_TRACKS = 84
|
|
41
44
|
MAX_TRACK_SIZE = 7928
|
|
@@ -124,7 +127,9 @@ module Badline
|
|
|
124
127
|
private
|
|
125
128
|
|
|
126
129
|
def parse
|
|
127
|
-
|
|
130
|
+
unless @data.start_with?(SIGNATURE) || @data.start_with?(SIGNATURE_1571)
|
|
131
|
+
raise FormatError, "Missing G64 signature"
|
|
132
|
+
end
|
|
128
133
|
raise FormatError, "G64 header is cut short" if @data.bytesize < HEADER_SIZE
|
|
129
134
|
|
|
130
135
|
version, @half_tracks, @max_track_size = @data.byteslice(8, 4).unpack("CCv")
|
data/lib/badline/storage/p00.rb
CHANGED
|
@@ -18,6 +18,14 @@ module Badline
|
|
|
18
18
|
def data(bytes)
|
|
19
19
|
bytes[HEADER_SIZE..]
|
|
20
20
|
end
|
|
21
|
+
|
|
22
|
+
# The load address of the program in a .prg file, or wrapped in a
|
|
23
|
+
# .p00 one, or -1 when the file is too short to have one.
|
|
24
|
+
def load_address(path)
|
|
25
|
+
bytes = File.binread(path).bytes
|
|
26
|
+
offset = wraps?(bytes) ? HEADER_SIZE : 0
|
|
27
|
+
bytes.size >= offset + 2 ? bytes[offset] | (bytes[offset + 1] << 8) : -1
|
|
28
|
+
end
|
|
21
29
|
end
|
|
22
30
|
end
|
|
23
31
|
end
|
data/lib/badline/time_of_day.rb
CHANGED
|
@@ -52,6 +52,15 @@ module Badline
|
|
|
52
52
|
@latch = input.boolean? ? FIELDS.to_h { |field| [field, input.int] } : nil
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
# Whether the clock is stopped, or gets no pulses on its pin, with no
|
|
56
|
+
# alarm to raise, so its cycles move only the divider's phase.
|
|
57
|
+
def quiet? = (@stopped || @mains_hz.zero?) && !@alarm_pending
|
|
58
|
+
|
|
59
|
+
# Runs +cycles+ quiet cycles at once.
|
|
60
|
+
def fast_forward(cycles)
|
|
61
|
+
@accumulator = (@accumulator + (cycles * @mains_hz)) % @cycles_per_pulse
|
|
62
|
+
end
|
|
63
|
+
|
|
55
64
|
def cycle!
|
|
56
65
|
@accumulator += @mains_hz
|
|
57
66
|
pulse! if @accumulator >= @cycles_per_pulse
|
data/lib/badline/version.rb
CHANGED