badline 0.15.0 → 0.16.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 +102 -0
- data/CLAUDE.md +15 -7
- data/README.md +142 -16
- data/doc/media.md +19 -0
- data/doc/pinned-behaviour.md +67 -0
- data/doc/snapshot-format.md +274 -31
- data/doc/snapshots.md +14 -0
- data/exe/badline-ruby +1 -1
- data/lib/badline/audio/machine_player.rb +2 -14
- data/lib/badline/c64/common.rb +1 -1
- data/lib/badline/c64/true_drives.rb +13 -1
- data/lib/badline/c64.rb +6 -0
- data/lib/badline/chips/crtc/keyed_state.rb +46 -0
- data/lib/badline/chips/crtc.rb +163 -0
- data/lib/badline/chips/pia/control_lines.rb +138 -0
- data/lib/badline/chips/pia/keyed_parts.rb +56 -0
- data/lib/badline/chips/pia.rb +138 -0
- data/lib/badline/chips/tia.rb +16 -0
- data/lib/badline/core.rb +3 -0
- data/lib/badline/drives/datasette/keyed_state.rb +8 -3
- data/lib/badline/drives/engine/core.rb +6 -14
- data/lib/badline/drives/engine/idle.rb +42 -4
- data/lib/badline/drives/engine/idle_cpu.rb +18 -0
- data/lib/badline/drives/engine/serial_connection.rb +25 -0
- data/lib/badline/drives/engine/sleep.rb +1 -0
- data/lib/badline/drives/engine.rb +1 -0
- data/lib/badline/drives/ieee_bus.rb +28 -0
- data/lib/badline/drives/model1541/byte_ready.rb +19 -3
- data/lib/badline/drives/model1541/mechanism.rb +10 -1
- data/lib/badline/drives/model1541.rb +1 -0
- data/lib/badline/drives/model1551/bus.rb +138 -0
- data/lib/badline/drives/model1551/cable.rb +107 -0
- data/lib/badline/drives/model1551/cpu.rb +14 -0
- data/lib/badline/drives/model1551/keyed_state.rb +65 -0
- data/lib/badline/drives/model1551/port.rb +74 -0
- data/lib/badline/drives/model1551/tia.rb +71 -0
- data/lib/badline/drives/model1551.rb +192 -0
- data/lib/badline/drives/model1571.rb +1 -0
- data/lib/badline/drives/model1581.rb +1 -0
- data/lib/badline/drives.rb +1 -0
- data/lib/badline/frontend/app.rb +1 -1
- data/lib/badline/frontend/boot.rb +20 -1
- data/lib/badline/frontend/controls.rb +23 -3
- data/lib/badline/frontend/gamepads.rb +10 -3
- data/lib/badline/frontend/keys.rb +88 -0
- data/lib/badline/frontend/media_slots.rb +1 -0
- data/lib/badline/frontend/menu/menu_dialogs.rb +15 -6
- data/lib/badline/frontend/menu/menu_media.rb +96 -19
- data/lib/badline/frontend/menu/menu_pages.rb +25 -7
- data/lib/badline/frontend/menu/menu_ports.rb +22 -1
- data/lib/badline/frontend/menu/pause_menu.rb +1 -0
- data/lib/badline/frontend/pet_keyboard.rb +124 -0
- data/lib/badline/frontend/player/player_controller.rb +11 -4
- data/lib/badline/frontend/screens.rb +2 -1
- data/lib/badline/frontend/timeline.rb +16 -9
- data/lib/badline/frontend.rb +1 -0
- data/lib/badline/kernal_trap/dos/writes.rb +3 -1
- data/lib/badline/kernal_trap/dos.rb +6 -1
- data/lib/badline/kernal_trap/frames.rb +117 -0
- data/lib/badline/kernal_trap/ieee.rb +148 -0
- data/lib/badline/kernal_trap/ieee_layout.rb +113 -0
- data/lib/badline/kernal_trap/keyed_state.rb +4 -4
- data/lib/badline/kernal_trap/routine.rb +3 -53
- data/lib/badline/kernal_trap/serial.rb +4 -102
- data/lib/badline/kernal_trap/steps.rb +64 -0
- data/lib/badline/kernal_trap.rb +4 -0
- data/lib/badline/machine/keyboard_buffer.rb +8 -0
- data/lib/badline/machine/settling.rb +1 -1
- data/lib/badline/machine.rb +37 -11
- data/lib/badline/media/extensions.rb +5 -2
- data/lib/badline/media/pet_media.rb +40 -0
- data/lib/badline/media/program.rb +14 -2
- data/lib/badline/media/true_drive.rb +7 -5
- data/lib/badline/media/vic20_media.rb +2 -2
- data/lib/badline/media.rb +36 -16
- data/lib/badline/options/family.rb +17 -6
- data/lib/badline/options/help.rb +23 -5
- data/lib/badline/options/option.rb +2 -1
- data/lib/badline/options/table.rb +28 -4
- data/lib/badline/options/validation.rb +117 -14
- data/lib/badline/options.rb +65 -21
- data/lib/badline/pet/bus/expansion.rb +106 -0
- data/lib/badline/pet/bus.rb +204 -0
- data/lib/badline/pet/canvas.rb +97 -0
- data/lib/badline/pet/cassettes.rb +63 -0
- data/lib/badline/pet/characters.rb +26 -0
- data/lib/badline/pet/cpu.rb +10 -0
- data/lib/badline/pet/crtc_video.rb +142 -0
- data/lib/badline/pet/discrete_video.rb +83 -0
- data/lib/badline/pet/ieee_ports.rb +66 -0
- data/lib/badline/pet/ieee_traps.rb +75 -0
- data/lib/badline/pet/keyboard_ports.rb +74 -0
- data/lib/badline/pet/keyed_modules.rb +85 -0
- data/lib/badline/pet/keyed_parts.rb +123 -0
- data/lib/badline/pet/keyed_state.rb +42 -0
- data/lib/badline/pet/model.rb +113 -0
- data/lib/badline/pet/rom_sets.rb +65 -0
- data/lib/badline/pet/sound.rb +128 -0
- data/lib/badline/pet/via.rb +10 -0
- data/lib/badline/pet/via_ports.rb +19 -0
- data/lib/badline/pet.rb +226 -0
- data/lib/badline/plus4/acia/frames.rb +55 -0
- data/lib/badline/plus4/acia.rb +276 -16
- data/lib/badline/plus4/bus.rb +181 -45
- data/lib/badline/plus4/keyed_modules.rb +22 -5
- data/lib/badline/plus4/keyed_parts.rb +33 -4
- data/lib/badline/plus4/keyed_state.rb +10 -5
- data/lib/badline/plus4/ram_expansion.rb +145 -0
- data/lib/badline/plus4/sid_card.rb +95 -0
- data/lib/badline/plus4/sound_mix.rb +51 -0
- data/lib/badline/plus4/tcbm_drive.rb +134 -0
- data/lib/badline/plus4/user_port.rb +11 -2
- data/lib/badline/plus4.rb +104 -22
- data/lib/badline/roms/README +146 -0
- data/lib/badline/roms/dos1551.rom +0 -0
- data/lib/badline/roms/pet/basic1.rom +0 -0
- data/lib/badline/roms/pet/basic2.rom +0 -0
- data/lib/badline/roms/pet/basic4.rom +0 -0
- data/lib/badline/roms/pet/characters1.rom +0 -0
- data/lib/badline/roms/pet/characters2.rom +0 -0
- data/lib/badline/roms/pet/editor1.rom +0 -0
- data/lib/badline/roms/pet/editor2-b.rom +0 -0
- data/lib/badline/roms/pet/editor2-n.rom +0 -0
- data/lib/badline/roms/pet/editor4-40-n-50hz.rom +0 -0
- data/lib/badline/roms/pet/editor4-40-n-60hz.rom +0 -0
- data/lib/badline/roms/pet/editor4-80-b-50hz-0283.rom +0 -0
- data/lib/badline/roms/pet/editor4-80-b-50hz.rom +0 -0
- data/lib/badline/roms/pet/editor4-80-b-60hz.rom +0 -0
- data/lib/badline/roms/pet/editor4-b.rom +0 -0
- data/lib/badline/roms/pet/editor4-n.rom +0 -0
- data/lib/badline/roms/pet/kernal1.rom +0 -0
- data/lib/badline/roms/pet/kernal2.rom +0 -0
- data/lib/badline/roms/pet/kernal4.rom +0 -0
- data/lib/badline/snapshot/container.rb +2 -0
- data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
- data/lib/badline/snapshot/field_doc.rb +21 -6
- data/lib/badline/snapshot/image.rb +14 -7
- data/lib/badline/snapshot/pet_setup.rb +71 -0
- data/lib/badline/snapshot/plus4_setup.rb +52 -10
- data/lib/badline/snapshot/storage_setup.rb +7 -1
- data/lib/badline/snapshot.rb +9 -5
- data/lib/badline/storage/d80_image.rb +48 -0
- data/lib/badline/storage/d82_image.rb +21 -0
- data/lib/badline/storage/g64_image.rb +1 -1
- data/lib/badline/storage/tap.rb +16 -3
- data/lib/badline/storage.rb +2 -0
- data/lib/badline/support/pet_checkpoint.rb +38 -0
- data/lib/badline/timing.rb +5 -4
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic20/attachments.rb +7 -2
- data/lib/badline/vic20/bus/keyed_state.rb +5 -4
- data/lib/badline/vic20/bus.rb +5 -0
- data/lib/badline/vic20.rb +1 -1
- metadata +69 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model1551
|
|
6
|
+
# The drive's 6523 TIA at $4000-$7FFF, its eight registers mirrored
|
|
7
|
+
# (zimmers.net's 1551 memory map, schematic 251860):
|
|
8
|
+
#
|
|
9
|
+
# - Port A carries the cable's eight data lines, pulled up.
|
|
10
|
+
# - Port B reads the GCR byte the gate array latched at BYTE READY
|
|
11
|
+
# and drives the byte the head writes.
|
|
12
|
+
# - Port C drives STATUS0 and STATUS1 (PC0-1), DEV (PC2), which picks
|
|
13
|
+
# where the computer's TIA answers, ACK (PC3) and the head's MODE
|
|
14
|
+
# (PC4, low to write), and reads the device number jumper (PC5,
|
|
15
|
+
# high for device 9), SYNC (PC6, low in a SYNC mark) and DAV (PC7).
|
|
16
|
+
#
|
|
17
|
+
# The DOS sets port C's directions to $1F. The cable's lines are
|
|
18
|
+
# pulled up, so an input nothing drives reads 1.
|
|
19
|
+
#
|
|
20
|
+
# Any access to the TIA clears BYTE LATCHED.
|
|
21
|
+
class TIA < Chips::TIA
|
|
22
|
+
DAV = 0x80
|
|
23
|
+
SYNC = 0x40
|
|
24
|
+
JUMPER = 0x20
|
|
25
|
+
MODE = 0x10
|
|
26
|
+
|
|
27
|
+
def initialize(mechanism, device:)
|
|
28
|
+
@mechanism = mechanism
|
|
29
|
+
@jumper = device == 9 ? JUMPER : 0
|
|
30
|
+
mechanism.tia = self
|
|
31
|
+
super()
|
|
32
|
+
@inputs[0] = 0xff
|
|
33
|
+
@inputs[1] = 0xff
|
|
34
|
+
@inputs[2] = 0xff
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def reset!
|
|
38
|
+
super
|
|
39
|
+
mode_written
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def peek(addr)
|
|
43
|
+
mechanism = @mechanism
|
|
44
|
+
mechanism.release_byte_latch
|
|
45
|
+
case addr & 0x07
|
|
46
|
+
when 1 then @inputs[1] = mechanism.latched_byte
|
|
47
|
+
when 2 then @inputs[2] = (@inputs[2] & DAV) | (mechanism.sync? ? 0 : SYNC) | @jumper | 0x1f
|
|
48
|
+
end
|
|
49
|
+
super
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def poke(addr, value)
|
|
53
|
+
@mechanism.release_byte_latch
|
|
54
|
+
super
|
|
55
|
+
register = addr & 0x07
|
|
56
|
+
mode_written if [2, 5].include?(register)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# What port B drives onto the head's write shift register.
|
|
60
|
+
def port_b_output = output(1)
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
# MODE selects reading while high, as the 1541's CB2 does.
|
|
65
|
+
def mode_written
|
|
66
|
+
@mechanism.cb2_written(output(2).anybits?(MODE))
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/chips/tia"
|
|
4
|
+
require "badline/drives/model1541"
|
|
5
|
+
require "badline/drives/model1551/cpu"
|
|
6
|
+
require "badline/drives/model1551/port"
|
|
7
|
+
require "badline/drives/model1551/tia"
|
|
8
|
+
require "badline/drives/model1551/bus"
|
|
9
|
+
require "badline/drives/model1551/cable"
|
|
10
|
+
require "badline/drives/model1551/keyed_state"
|
|
11
|
+
|
|
12
|
+
module Badline
|
|
13
|
+
module Drives
|
|
14
|
+
# The 1551 disk drive of the 264 series: a 6510T at 2 MHz running DOS
|
|
15
|
+
# 2.6 from 16 KB of ROM, with 2 KB of RAM, the 1541's GCR mechanism
|
|
16
|
+
# behind a gate array, and a 6523 TIA facing the computer over TCBM, a
|
|
17
|
+
# parallel cable to a second 6523 in the computer's expansion port
|
|
18
|
+
# (Commodore schematic 251860; zimmers.net's 1551 memory map).
|
|
19
|
+
#
|
|
20
|
+
# The 6510T's port drives the mechanism as the 1541's VIA 2 port B does
|
|
21
|
+
# and reads the write-protect sensor and the gate array's BYTE LATCHED
|
|
22
|
+
# (Port). The TIA's port B reads the GCR byte the gate array latched and
|
|
23
|
+
# drives the byte to write, and its port C selects reading or writing
|
|
24
|
+
# and reads SYNC (TIA).
|
|
25
|
+
#
|
|
26
|
+
# The drive comes with its interface for the computer's expansion port,
|
|
27
|
+
# a second 6523 (#host_tia), which the cable joins to the drive's
|
|
28
|
+
# (Cable). The computer puts it at $FEE0 for device 8 and at $FEC0 for
|
|
29
|
+
# device 9, as the drive's DEV line (PC2) says: high for device 8 and
|
|
30
|
+
# low for device 9. The DOS sets DEV from the jumper at reset, and again
|
|
31
|
+
# when a command renumbers the drive. The device number follows each
|
|
32
|
+
# change on the line (#device).
|
|
33
|
+
#
|
|
34
|
+
# A 555 timer pulls IRQ low for about 7 µs every 8.3 ms, its R1 120K,
|
|
35
|
+
# R2 100R and C1 0.1 µF, which runs the DOS's job loop. Nothing
|
|
36
|
+
# acknowledges it: the line lets go on its own.
|
|
37
|
+
#
|
|
38
|
+
# Drives::Engine::Core clocks it against the host and sleeps through
|
|
39
|
+
# the DOS's idle loop, which counts its passes in $61 (Engine::Idle's
|
|
40
|
+
# pass counter), until the 555 is due to pull IRQ, the count wraps or
|
|
41
|
+
# the computer moves the cable's lines.
|
|
42
|
+
class Model1551
|
|
43
|
+
include Engine::Core
|
|
44
|
+
|
|
45
|
+
CLOCK_HZ = 2_000_000
|
|
46
|
+
|
|
47
|
+
# The 555's cycle in drive cycles: high for 0.693 (R1 + R2) C and low,
|
|
48
|
+
# pulling IRQ, for 0.693 R2 C.
|
|
49
|
+
IRQ_PERIOD = 16_660
|
|
50
|
+
IRQ_LOW = 14
|
|
51
|
+
|
|
52
|
+
# The host cycles, at about 1 MHz, by which a 1551 switched on with the
|
|
53
|
+
# machine has run its DOS's reset, the RAM test and the ROM checksum,
|
|
54
|
+
# and answers TCBM: half a second, with a margin.
|
|
55
|
+
BOOT_CYCLES = 600_000
|
|
56
|
+
|
|
57
|
+
# Where the DOS's idle loop starts over, polling the TIA's port A for
|
|
58
|
+
# a command from the computer.
|
|
59
|
+
IDLE_LOOP = 0xeabd
|
|
60
|
+
|
|
61
|
+
# The byte the idle loop increments on each pass.
|
|
62
|
+
PASS_COUNTER = 0x61
|
|
63
|
+
|
|
64
|
+
def port
|
|
65
|
+
settle!
|
|
66
|
+
@port
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def tia
|
|
70
|
+
settle!
|
|
71
|
+
@tia
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# The TIA in the computer's expansion port, which the computer reads
|
|
75
|
+
# and writes without waking the drive.
|
|
76
|
+
attr_reader :host_tia
|
|
77
|
+
|
|
78
|
+
# The cable between the two TIAs.
|
|
79
|
+
def cable
|
|
80
|
+
settle!
|
|
81
|
+
@cable
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def model_name = "1551"
|
|
85
|
+
|
|
86
|
+
# +rom+ covers $C000-$FFFF, and defaults to the DOS image in the ROM
|
|
87
|
+
# path. +device+ is 8 or 9, as the jumper on the TIA's PC5 sets it.
|
|
88
|
+
def initialize(rom: nil, host_clock_hz: CLOCK_HZ, device: 8, debug: false)
|
|
89
|
+
raise ArgumentError, "a 1551 is device 8 or 9, not #{device}" unless [8, 9].include?(device)
|
|
90
|
+
|
|
91
|
+
@device = device
|
|
92
|
+
@clock_hz = CLOCK_HZ
|
|
93
|
+
@mechanism = Model1541::Mechanism.new
|
|
94
|
+
@mechanism.clock_hz = CLOCK_HZ
|
|
95
|
+
@port = Port.new(@mechanism)
|
|
96
|
+
@tia = TIA.new(@mechanism, device:)
|
|
97
|
+
@host_tia = Chips::TIA.new
|
|
98
|
+
@bus = Bus.new(rom: rom || ROM.load("dos1551.rom", 0xc000), port: @port, tia: @tia)
|
|
99
|
+
@cpu = CPU.new(@bus, debug:)
|
|
100
|
+
@idle_loop = IDLE_LOOP
|
|
101
|
+
@host_clock_hz = host_clock_hz
|
|
102
|
+
@phase = 0
|
|
103
|
+
@cycles = 0
|
|
104
|
+
@irq_timer = IRQ_PERIOD
|
|
105
|
+
init_idle(debug)
|
|
106
|
+
@pass_counter = PASS_COUNTER
|
|
107
|
+
@dev_high = true
|
|
108
|
+
@on_renumber = nil
|
|
109
|
+
@cable = Cable.new(@host_tia, @tia, self)
|
|
110
|
+
@serial_port = @cable
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Calls the block with the old device number each time the DEV line
|
|
114
|
+
# moves the drive to the other one.
|
|
115
|
+
def on_renumber(&block)
|
|
116
|
+
@on_renumber = block
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# The cable's DEV line, high or not. A change moves the drive to
|
|
120
|
+
# device 8 while it's high, and to device 9 while it's low.
|
|
121
|
+
def dev_line(high)
|
|
122
|
+
return if high == @dev_high
|
|
123
|
+
|
|
124
|
+
@dev_high = high
|
|
125
|
+
old = @device
|
|
126
|
+
@device = high ? 8 : 9
|
|
127
|
+
@on_renumber&.call(old) unless @device == old
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Puts in a Disk from the .d64 or .g64 image at +path+, write-protected
|
|
131
|
+
# with `read_only`.
|
|
132
|
+
def insert_image(path, read_only: false)
|
|
133
|
+
insert(Model1541::Disk.open(path, read_only:))
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# The computer's RESET line reaches the interface's TIA and, through
|
|
137
|
+
# the cable, the CPU, its port and the drive's TIA. RAM keeps its
|
|
138
|
+
# contents, and the 555 runs on.
|
|
139
|
+
def reset!
|
|
140
|
+
settle!
|
|
141
|
+
@host_tia.reset!
|
|
142
|
+
@port.reset!
|
|
143
|
+
@tia.reset!
|
|
144
|
+
@cpu.reset!
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
# One drive cycle: the mechanism turns the disk ahead of the CPU, and
|
|
150
|
+
# the 555 pulls IRQ while its output is low.
|
|
151
|
+
def step
|
|
152
|
+
@mechanism.cycle!
|
|
153
|
+
timer = @irq_timer - 1
|
|
154
|
+
timer = IRQ_PERIOD if timer.zero?
|
|
155
|
+
@irq_timer = timer
|
|
156
|
+
@cpu.irq = timer <= IRQ_LOW
|
|
157
|
+
@cpu.cycle!
|
|
158
|
+
@cycles += 1
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# A pass of the idle loop reads the TIA's port A, which holds still
|
|
162
|
+
# until the computer moves the cable's lines, and RAM. It repeats
|
|
163
|
+
# while the motor is off and the 555 leaves IRQ alone, for as long as
|
|
164
|
+
# the 555 leaves room (see Engine::Idle).
|
|
165
|
+
def busy_for_pass? = @mechanism.motor_on? || @irq_timer <= IRQ_LOW
|
|
166
|
+
|
|
167
|
+
def quiet_cycles
|
|
168
|
+
quiet = @irq_timer - IRQ_LOW - 1
|
|
169
|
+
quiet.negative? ? 0 : quiet
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def idle_state
|
|
173
|
+
tia = @tia
|
|
174
|
+
[*@cpu.counting_state, @bus.data, *@mechanism.idle_state, *tia.ports, *tia.directions, *tia.inputs,
|
|
175
|
+
@port.ddr, @port.data]
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def fast_forward_chips(cycles)
|
|
179
|
+
@irq_timer -= cycles
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Every 555 pulse runs the job loop, which reads the port and the TIA,
|
|
183
|
+
# so no orbit (Engine::Orbit) comes round, and the drive keeps no
|
|
184
|
+
# anchors.
|
|
185
|
+
def anchor = nil
|
|
186
|
+
|
|
187
|
+
def counter_state(_touched) = []
|
|
188
|
+
|
|
189
|
+
def skip_chip_orbits(_cycles, _touched) = nil
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
data/lib/badline/drives.rb
CHANGED
data/lib/badline/frontend/app.rb
CHANGED
|
@@ -112,7 +112,7 @@ module Badline
|
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
def handle_key(scancode, down)
|
|
115
|
-
if [Keys::
|
|
115
|
+
if [Keys::F8, Keys::F9, Keys::F10].include?(scancode) || (scancode == Keys::TAB && @controls.tab_toggles?)
|
|
116
116
|
handle_toggle(scancode) if down
|
|
117
117
|
elsif down && @snapshots.key(scancode)
|
|
118
118
|
swap(@snapshots.computer)
|
|
@@ -17,7 +17,11 @@ module Badline
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
computer = nil
|
|
20
|
-
problem =
|
|
20
|
+
problem = if cartridge?(options)
|
|
21
|
+
cartridge_problem { computer = boot(options) }
|
|
22
|
+
else
|
|
23
|
+
media_problem { computer = boot(options) }
|
|
24
|
+
end
|
|
21
25
|
unless problem.empty?
|
|
22
26
|
warn "#{options.program}: #{options.media_path}: #{problem}"
|
|
23
27
|
return 1
|
|
@@ -40,6 +44,21 @@ module Badline
|
|
|
40
44
|
e.message
|
|
41
45
|
end
|
|
42
46
|
|
|
47
|
+
# Runs the block that puts a cartridge in, as media_problem does, and
|
|
48
|
+
# also returns the message of the ArgumentError a machine raises over a
|
|
49
|
+
# cartridge with a chip in a bank or slot it doesn't map.
|
|
50
|
+
def self.cartridge_problem(&)
|
|
51
|
+
media_problem(&)
|
|
52
|
+
rescue ArgumentError => e
|
|
53
|
+
e.message
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Whether the media the options start is a .crt.
|
|
57
|
+
def self.cartridge?(options)
|
|
58
|
+
media = options.media_path
|
|
59
|
+
!media.nil? && Media::Extensions.kind(media) == :cartridge
|
|
60
|
+
end
|
|
61
|
+
|
|
43
62
|
# The machine a .vsf snapshot holds, built as the saved one was, or a
|
|
44
63
|
# new one built from the options with the media attached.
|
|
45
64
|
def self.boot(options)
|
|
@@ -7,7 +7,9 @@ module Badline
|
|
|
7
7
|
# - The keys: every host key goes to the machine's keyboard, or, as Tab
|
|
8
8
|
# switches, the arrow cluster and space drive one joystick and WASD
|
|
9
9
|
# and left shift the other. The arrows start on joystick 2, and
|
|
10
|
-
# #swap_ports swaps them. The VIC-20's one joystick takes both.
|
|
10
|
+
# #swap_ports swaps them. The VIC-20's one joystick takes both. The
|
|
11
|
+
# PET has no joystick, and its keys go by the character they type
|
|
12
|
+
# (PETKeyboard).
|
|
11
13
|
# - A pot device the pause menu plugs into a C64 control port: a 1351
|
|
12
14
|
# mouse or a pair of paddles, which the host mouse drives. Motion
|
|
13
15
|
# turns the paddles or moves the mouse, and the host's left and right
|
|
@@ -30,6 +32,7 @@ module Badline
|
|
|
30
32
|
@mouse = Input::Mouse1351.new
|
|
31
33
|
@paddles = Input::Paddles.new
|
|
32
34
|
@held = {}
|
|
35
|
+
@pet_keyboard = pet_keyboard(computer)
|
|
33
36
|
end
|
|
34
37
|
|
|
35
38
|
# Routes the input to another machine, the pot device going into its
|
|
@@ -37,6 +40,7 @@ module Badline
|
|
|
37
40
|
def computer=(computer)
|
|
38
41
|
release_all
|
|
39
42
|
@computer = computer
|
|
43
|
+
@pet_keyboard = pet_keyboard(computer)
|
|
40
44
|
attach_pot_device
|
|
41
45
|
end
|
|
42
46
|
|
|
@@ -48,10 +52,16 @@ module Badline
|
|
|
48
52
|
release_all
|
|
49
53
|
end
|
|
50
54
|
|
|
55
|
+
# Switches between the keyboard and the joysticks, on a machine with
|
|
56
|
+
# joysticks.
|
|
51
57
|
def toggle_keys
|
|
52
|
-
self.joystick_mode = !@joystick
|
|
58
|
+
self.joystick_mode = !@joystick if tab_toggles?
|
|
53
59
|
end
|
|
54
60
|
|
|
61
|
+
# Whether Tab switches the keys to the joysticks, or on the PET, which
|
|
62
|
+
# has none, is a key of its own.
|
|
63
|
+
def tab_toggles? = @computer.family != :pet
|
|
64
|
+
|
|
55
65
|
# Plugs in one of POTS, in place of the one before.
|
|
56
66
|
def plug(pot)
|
|
57
67
|
@pot = pot
|
|
@@ -106,6 +116,8 @@ module Badline
|
|
|
106
116
|
# A host key lets go of the key it pressed, though the C128 has
|
|
107
117
|
# switched between C64 and C128 mode since and maps it elsewhere.
|
|
108
118
|
def machine_key(scancode, down)
|
|
119
|
+
return @pet_keyboard.key(scancode, down) if @computer.family == :pet
|
|
120
|
+
|
|
109
121
|
@held[scancode] = host_key(scancode) if down || !@held.key?(scancode)
|
|
110
122
|
key = @held[scancode]
|
|
111
123
|
@held.delete(scancode) unless down
|
|
@@ -147,7 +159,7 @@ module Badline
|
|
|
147
159
|
# A fresh pot device on its port, and none on the other. A machine
|
|
148
160
|
# without the ports takes none.
|
|
149
161
|
def attach_pot_device
|
|
150
|
-
ports = @computer.control_ports
|
|
162
|
+
ports = @computer.family == :pet ? nil : @computer.control_ports
|
|
151
163
|
if ports.nil?
|
|
152
164
|
@pot = :none
|
|
153
165
|
return
|
|
@@ -199,7 +211,15 @@ module Badline
|
|
|
199
211
|
down ? joystick.press(direction) : joystick.release(direction)
|
|
200
212
|
end
|
|
201
213
|
|
|
214
|
+
# The PET's keyboard for a PET, and for any other machine one it never
|
|
215
|
+
# uses.
|
|
216
|
+
def pet_keyboard(computer)
|
|
217
|
+
PETKeyboard.new(computer.keyboard, computer.family == :pet ? computer.model.keyboard : :graphics)
|
|
218
|
+
end
|
|
219
|
+
|
|
202
220
|
def release_all
|
|
221
|
+
return @pet_keyboard.release_all if @computer.family == :pet
|
|
222
|
+
|
|
203
223
|
Keys::ARROWS.each_key { |scancode| @computer.keyboard.release(host_key(scancode)) }
|
|
204
224
|
Keys::WASD.each_key { |scancode| @computer.keyboard.release(host_key(scancode)) }
|
|
205
225
|
Keys::ARROWS.each_value do |direction|
|
|
@@ -7,14 +7,15 @@ module Badline
|
|
|
7
7
|
# second one joystick 1. PadPort holds the mapping.
|
|
8
8
|
#
|
|
9
9
|
# The controllers are polled every frame, and rescanned whenever SDL
|
|
10
|
-
# reports one added or removed.
|
|
10
|
+
# reports one added or removed. The PET has no joysticks, and the
|
|
11
|
+
# controllers steer two that nothing reads.
|
|
11
12
|
class Gamepads
|
|
12
13
|
PORTS = [2, 1].freeze
|
|
13
14
|
|
|
14
15
|
def initialize(computer, verbose)
|
|
15
16
|
@verbose = verbose
|
|
16
17
|
@controllers = []
|
|
17
|
-
@ports = [PadPort.new(computer
|
|
18
|
+
@ports = [PadPort.new(joystick(computer, 2)), PadPort.new(joystick(computer, 1))]
|
|
18
19
|
@buttons = Array.new(PadPort::BUTTON_COUNT, 0)
|
|
19
20
|
@available = SDL.SDL_InitSubSystem(SDL::INIT_GAMECONTROLLER).zero?
|
|
20
21
|
puts "No game controllers: #{SDL.SDL_GetError}" if @verbose && !@available
|
|
@@ -24,7 +25,7 @@ module Badline
|
|
|
24
25
|
# what they hold on the next poll.
|
|
25
26
|
def computer=(computer)
|
|
26
27
|
@ports.each(&:release)
|
|
27
|
-
@ports = [PadPort.new(computer
|
|
28
|
+
@ports = [PadPort.new(joystick(computer, 2)), PadPort.new(joystick(computer, 1))]
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
def rescan
|
|
@@ -51,6 +52,12 @@ module Badline
|
|
|
51
52
|
|
|
52
53
|
private
|
|
53
54
|
|
|
55
|
+
def joystick(computer, port)
|
|
56
|
+
return Input::Joystick.new if computer.family == :pet
|
|
57
|
+
|
|
58
|
+
port == 1 ? computer.joystick1 : computer.joystick2
|
|
59
|
+
end
|
|
60
|
+
|
|
54
61
|
def open_controller(index)
|
|
55
62
|
controller = SDL.SDL_GameControllerOpen(index)
|
|
56
63
|
# SDL returns NULL when the controller won't open, which Spinel's FFI
|
|
@@ -65,6 +65,59 @@ module Badline
|
|
|
65
65
|
79 => :crsr_right, 80 => :crsr_left, 81 => :crsr_down, 82 => :crsr_up, 229 => :lshift, 75 => :none
|
|
66
66
|
}.freeze
|
|
67
67
|
|
|
68
|
+
# The characters the keys other than the letters type on a US
|
|
69
|
+
# layout, unshifted and then shifted, and the keypad's.
|
|
70
|
+
US_CHARACTERS = {
|
|
71
|
+
30 => "1!", 31 => "2@", 32 => "3#", 33 => "4$", 34 => "5%", 35 => "6^", 36 => "7&", 37 => "8*", 38 => "9(",
|
|
72
|
+
39 => "0)", 45 => "-_", 46 => "=+", 47 => "[{", 48 => "]}", 49 => "\\|", 51 => ";:", 52 => "'\"", 53 => "`~",
|
|
73
|
+
54 => ",<", 55 => ".>", 56 => "/?", 84 => "//", 85 => "**", 86 => "--", 87 => "++", 89 => "11", 90 => "22",
|
|
74
|
+
91 => "33", 92 => "44", 93 => "55", 94 => "66", 95 => "77", 96 => "88", 97 => "99", 98 => "00", 99 => "..",
|
|
75
|
+
103 => "=="
|
|
76
|
+
}.freeze
|
|
77
|
+
|
|
78
|
+
# The PET's keys for the characters a host key types, on either
|
|
79
|
+
# keyboard without SHIFT: each has a key of its own on the graphics
|
|
80
|
+
# keyboard, where SHIFT turns it into a graphics character. ↑ and ←
|
|
81
|
+
# are on ^, and on _ and `.
|
|
82
|
+
PET_GRAPHICS_CHARACTERS = {
|
|
83
|
+
"!" => :!, "\"" => :'"', "#" => :"#", "$" => :"$", "%" => :%, "&" => :&, "'" => :"'", "(" => :"(",
|
|
84
|
+
")" => :")", "\\" => :"\\", "^" => :up, "_" => :left, "`" => :left, "/" => :/, ":" => :":", "*" => :*,
|
|
85
|
+
";" => :";", "?" => :"?", "," => :",", "+" => :+, "@" => :"@", "[" => :"[", "]" => :"]", "<" => :<,
|
|
86
|
+
">" => :>, "-" => :-, "=" => :"=", "." => :".", "1" => :"1", "2" => :"2", "3" => :"3", "4" => :"4",
|
|
87
|
+
"5" => :"5", "6" => :"6", "7" => :"7", "8" => :"8", "9" => :"9", "0" => :"0"
|
|
88
|
+
}.freeze
|
|
89
|
+
|
|
90
|
+
# The business keyboard's: the characters it types without SHIFT,
|
|
91
|
+
# then those it types with it.
|
|
92
|
+
PET_BUSINESS_CHARACTERS = {
|
|
93
|
+
"1" => :"1", "2" => :"2", "3" => :"3", "4" => :"4", "5" => :"5", "6" => :"6", "7" => :"7", "8" => :"8",
|
|
94
|
+
"9" => :"9", "0" => :"0", "-" => :-, ":" => :":", ";" => :";", "," => :",", "." => :".", "/" => :/,
|
|
95
|
+
"@" => :"@", "[" => :"[", "]" => :"]", "\\" => :"\\", "^" => :up, "_" => :left, "`" => :left
|
|
96
|
+
}.freeze
|
|
97
|
+
|
|
98
|
+
PET_BUSINESS_SHIFTED = {
|
|
99
|
+
"!" => :"1", "\"" => :"2", "#" => :"3", "$" => :"4", "%" => :"5", "&" => :"6", "'" => :"7", "(" => :"8",
|
|
100
|
+
")" => :"9", "=" => :-, "*" => :":", "+" => :";", "<" => :",", ">" => :".", "?" => :/
|
|
101
|
+
}.freeze
|
|
102
|
+
|
|
103
|
+
# The PET's keys that don't type a character, which keep the host's
|
|
104
|
+
# SHIFT: RETURN, INST/DEL on Backspace, CLR/HOME on Home, RUN/STOP on
|
|
105
|
+
# Page Down, and RVS on Control. Esc is RUN/STOP on the graphics
|
|
106
|
+
# keyboard. The business keyboard's ESC, TAB, REPEAT and keypad are
|
|
107
|
+
# on Esc, Tab, Page Up and the keypad.
|
|
108
|
+
PET_KEYS = {
|
|
109
|
+
40 => :return, 88 => :return, 42 => :delete, 44 => :space, 74 => :clr_home, 78 => :run_stop, 224 => :rvs,
|
|
110
|
+
228 => :rvs
|
|
111
|
+
}.freeze
|
|
112
|
+
|
|
113
|
+
PET_BUSINESS_KEYS = {
|
|
114
|
+
41 => :escape, 43 => :tab, 75 => :repeat, 89 => :kp1, 90 => :kp2, 91 => :kp3, 92 => :kp4, 93 => :kp5,
|
|
115
|
+
94 => :kp6, 95 => :kp7, 96 => :kp8, 97 => :kp9, 98 => :kp0, 99 => :kp_period
|
|
116
|
+
}.freeze
|
|
117
|
+
|
|
118
|
+
# The cursor keys, CRSR ⇔ and CRSR ⇕, each with SHIFT for left or up.
|
|
119
|
+
PET_CURSORS = { RIGHT => :cursor_h, LEFT => :cursor_h, DOWN => :cursor_v, UP => :cursor_v }.freeze
|
|
120
|
+
|
|
68
121
|
ARROWS = { 44 => :fire, 228 => :fire, 79 => :right, 80 => :left, 81 => :down, 82 => :up }.freeze
|
|
69
122
|
WASD = { 225 => :fire, 7 => :right, 4 => :left, 22 => :down, 26 => :up }.freeze
|
|
70
123
|
|
|
@@ -89,6 +142,41 @@ module Badline
|
|
|
89
142
|
# The 264's key for the scancode: one of PLUS4_OTHERS or the C64's.
|
|
90
143
|
def self.plus4_key(scancode) = PLUS4_OTHERS[scancode] || c64_key(scancode)
|
|
91
144
|
|
|
145
|
+
# The PET key for the scancode on the +keyboard+, :graphics or
|
|
146
|
+
# :business, with the host's SHIFT down if +shift+, and how the
|
|
147
|
+
# PET's SHIFT goes with it: :on or :off for a key that types the host
|
|
148
|
+
# key's character or moves the cursor its way only so, and :host to
|
|
149
|
+
# follow the host's. A letter is the PET's letter, and any other key
|
|
150
|
+
# the PET key that types the same character, or :none.
|
|
151
|
+
def self.pet_key(scancode, shift, keyboard)
|
|
152
|
+
return [LETTERS[scancode - 4], :host] if scancode.between?(4, 29)
|
|
153
|
+
|
|
154
|
+
cursor = PET_CURSORS[scancode]
|
|
155
|
+
return [cursor, [LEFT, UP].include?(scancode) ? :on : :off] unless cursor.nil?
|
|
156
|
+
|
|
157
|
+
business = keyboard == :business
|
|
158
|
+
key = business ? PET_BUSINESS_KEYS[scancode] : nil
|
|
159
|
+
key = PET_KEYS[scancode] if key.nil?
|
|
160
|
+
key = :run_stop if key.nil? && scancode == ESCAPE
|
|
161
|
+
return [key, :host] unless key.nil?
|
|
162
|
+
|
|
163
|
+
pet_character(scancode, shift, business)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# The PET key that types the character the scancode types on a US
|
|
167
|
+
# layout, and the SHIFT it takes.
|
|
168
|
+
def self.pet_character(scancode, shift, business)
|
|
169
|
+
characters = US_CHARACTERS[scancode]
|
|
170
|
+
return %i[none host] if characters.nil?
|
|
171
|
+
|
|
172
|
+
character = characters[shift ? 1 : 0]
|
|
173
|
+
unshifted = business ? PET_BUSINESS_CHARACTERS[character] : PET_GRAPHICS_CHARACTERS[character]
|
|
174
|
+
return [unshifted, :off] unless unshifted.nil?
|
|
175
|
+
|
|
176
|
+
shifted = business ? PET_BUSINESS_SHIFTED[character] : nil
|
|
177
|
+
shifted.nil? ? %i[none host] : [shifted, :on]
|
|
178
|
+
end
|
|
179
|
+
|
|
92
180
|
# The character the key types on a US layout: a letter, a capital
|
|
93
181
|
# with `shift`, or a digit, and "" for any other key.
|
|
94
182
|
def self.character(scancode, shift)
|
|
@@ -37,7 +37,12 @@ module Badline
|
|
|
37
37
|
# :program, or :snapshot, which starts in the saves folder.
|
|
38
38
|
def browse(kind)
|
|
39
39
|
@browsing = kind
|
|
40
|
-
extensions =
|
|
40
|
+
extensions = case kind
|
|
41
|
+
when :cartridge then @media.cartridge_extensions
|
|
42
|
+
when :disk then @media.disk_extensions
|
|
43
|
+
when :program then programs
|
|
44
|
+
else EXTENSIONS.fetch(kind)
|
|
45
|
+
end
|
|
41
46
|
@browser.open(kind == :snapshot ? Badline.data_folder("saves") : @browse_from, extensions)
|
|
42
47
|
end
|
|
43
48
|
|
|
@@ -56,15 +61,15 @@ module Badline
|
|
|
56
61
|
def ask_remove = @confirmation.ask(:remove, @media.cartridge_name)
|
|
57
62
|
|
|
58
63
|
# Puts a dropped disk or tape in its device, or asks before a
|
|
59
|
-
# cartridge
|
|
60
|
-
# files are left alone.
|
|
64
|
+
# cartridge, a Plus/4's raw ROM image among them, or a program
|
|
65
|
+
# starts, and says whether it asks. Other files are left alone.
|
|
61
66
|
def drop(path)
|
|
62
67
|
extension = File.extname(path).downcase
|
|
63
|
-
return @media.insert(:disk, path) && false if
|
|
68
|
+
return @media.insert(:disk, path) && false if @media.disk_extensions.include?(extension)
|
|
64
69
|
return @media.insert(:tape, path) && false if EXTENSIONS[:tape].include?(extension)
|
|
65
|
-
return false unless
|
|
70
|
+
return false unless programs.include?(extension)
|
|
66
71
|
|
|
67
|
-
@confirmation.ask(extension
|
|
72
|
+
@confirmation.ask(@media.cartridge_extensions.include?(extension) ? :cartridge : :start, path)
|
|
68
73
|
true
|
|
69
74
|
end
|
|
70
75
|
|
|
@@ -98,6 +103,10 @@ module Badline
|
|
|
98
103
|
|
|
99
104
|
private
|
|
100
105
|
|
|
106
|
+
# What Quick open and a drop start: every medium, and the cartridges
|
|
107
|
+
# the machine takes.
|
|
108
|
+
def programs = (EXTENSIONS[:program] + @media.cartridge_extensions).uniq
|
|
109
|
+
|
|
101
110
|
# Puts the disk or tape picked in, or asks before a cartridge or
|
|
102
111
|
# Quick open power cycles the machine, and leaves the browser, as Esc
|
|
103
112
|
# does.
|