badline 0.15.1 → 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 +39 -0
- data/CLAUDE.md +8 -7
- data/README.md +102 -6
- data/doc/pinned-behaviour.md +67 -0
- data/doc/snapshot-format.md +213 -18
- data/doc/snapshots.md +14 -0
- data/lib/badline/c64/common.rb +1 -1
- 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/vic/register_log.rb +1 -4
- data/lib/badline/core.rb +3 -0
- data/lib/badline/drives/ieee_bus.rb +28 -0
- data/lib/badline/drives/model1551/cable.rb +5 -2
- data/lib/badline/drives/model1551/tia.rb +13 -34
- data/lib/badline/drives/model1551.rb +26 -4
- data/lib/badline/drives.rb +1 -0
- data/lib/badline/frontend/app.rb +1 -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 +2 -1
- data/lib/badline/frontend/menu/menu_media.rb +27 -28
- data/lib/badline/frontend/menu/menu_pages.rb +15 -2
- 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/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 +28 -6
- 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/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 +19 -3
- data/lib/badline/options/table.rb +14 -2
- data/lib/badline/options/validation.rb +68 -5
- data/lib/badline/options.rb +31 -19
- 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/keyed_state.rb +5 -3
- data/lib/badline/plus4/tcbm_drive.rb +14 -0
- data/lib/badline/plus4.rb +1 -1
- data/lib/badline/roms/README +137 -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 +16 -3
- data/lib/badline/snapshot/image.rb +14 -7
- data/lib/badline/snapshot/pet_setup.rb +71 -0
- 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/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 +55 -1
|
@@ -4,8 +4,7 @@ module Badline
|
|
|
4
4
|
module Drives
|
|
5
5
|
class Model1551
|
|
6
6
|
# The drive's 6523 TIA at $4000-$7FFF, its eight registers mirrored
|
|
7
|
-
# (zimmers.net's 1551 memory map, schematic 251860)
|
|
8
|
-
# with what the drive's side wires to it:
|
|
7
|
+
# (zimmers.net's 1551 memory map, schematic 251860):
|
|
9
8
|
#
|
|
10
9
|
# - Port A carries the cable's eight data lines, pulled up.
|
|
11
10
|
# - Port B reads the GCR byte the gate array latched at BYTE READY
|
|
@@ -19,72 +18,52 @@ module Badline
|
|
|
19
18
|
# pulled up, so an input nothing drives reads 1.
|
|
20
19
|
#
|
|
21
20
|
# Any access to the TIA clears BYTE LATCHED.
|
|
22
|
-
class TIA
|
|
21
|
+
class TIA < Chips::TIA
|
|
23
22
|
DAV = 0x80
|
|
24
23
|
SYNC = 0x40
|
|
25
24
|
JUMPER = 0x20
|
|
26
25
|
MODE = 0x10
|
|
27
26
|
|
|
28
|
-
# The chip, whose inputs the drive's side sets before each read.
|
|
29
|
-
attr_reader :chip
|
|
30
|
-
|
|
31
27
|
def initialize(mechanism, device:)
|
|
32
28
|
@mechanism = mechanism
|
|
33
29
|
@jumper = device == 9 ? JUMPER : 0
|
|
34
|
-
@chip = Chips::TIA.new
|
|
35
|
-
inputs = @chip.inputs
|
|
36
|
-
inputs[0] = 0xff
|
|
37
|
-
inputs[1] = 0xff
|
|
38
|
-
inputs[2] = 0xff
|
|
39
30
|
mechanism.tia = self
|
|
40
|
-
|
|
31
|
+
super()
|
|
32
|
+
@inputs[0] = 0xff
|
|
33
|
+
@inputs[1] = 0xff
|
|
34
|
+
@inputs[2] = 0xff
|
|
41
35
|
end
|
|
42
36
|
|
|
43
|
-
def ports = @chip.ports
|
|
44
|
-
|
|
45
|
-
def directions = @chip.directions
|
|
46
|
-
|
|
47
|
-
def inputs = @chip.inputs
|
|
48
|
-
|
|
49
37
|
def reset!
|
|
50
|
-
|
|
38
|
+
super
|
|
51
39
|
mode_written
|
|
52
40
|
end
|
|
53
41
|
|
|
54
42
|
def peek(addr)
|
|
55
43
|
mechanism = @mechanism
|
|
56
44
|
mechanism.release_byte_latch
|
|
57
|
-
chip = @chip
|
|
58
|
-
inputs = chip.inputs
|
|
59
45
|
case addr & 0x07
|
|
60
|
-
when 1 then inputs[1] = mechanism.latched_byte
|
|
61
|
-
when 2 then inputs[2] = (inputs[2] & DAV) | (mechanism.sync? ? 0 : SYNC) | @jumper | 0x1f
|
|
46
|
+
when 1 then @inputs[1] = mechanism.latched_byte
|
|
47
|
+
when 2 then @inputs[2] = (@inputs[2] & DAV) | (mechanism.sync? ? 0 : SYNC) | @jumper | 0x1f
|
|
62
48
|
end
|
|
63
|
-
|
|
49
|
+
super
|
|
64
50
|
end
|
|
65
51
|
|
|
66
52
|
def poke(addr, value)
|
|
67
53
|
@mechanism.release_byte_latch
|
|
68
|
-
|
|
54
|
+
super
|
|
69
55
|
register = addr & 0x07
|
|
70
56
|
mode_written if [2, 5].include?(register)
|
|
71
57
|
end
|
|
72
58
|
|
|
73
59
|
# What port B drives onto the head's write shift register.
|
|
74
|
-
def port_b_output =
|
|
75
|
-
|
|
76
|
-
# The chip's registers, in a keyed snapshot module (BL-D8-TIA).
|
|
77
|
-
def save_fields(out) = @chip.save_fields(out)
|
|
78
|
-
|
|
79
|
-
def load_fields(input)
|
|
80
|
-
@chip.load_fields(input)
|
|
81
|
-
end
|
|
60
|
+
def port_b_output = output(1)
|
|
82
61
|
|
|
83
62
|
private
|
|
84
63
|
|
|
85
64
|
# MODE selects reading while high, as the 1541's CB2 does.
|
|
86
65
|
def mode_written
|
|
87
|
-
@mechanism.cb2_written(
|
|
66
|
+
@mechanism.cb2_written(output(2).anybits?(MODE))
|
|
88
67
|
end
|
|
89
68
|
end
|
|
90
69
|
end
|
|
@@ -26,7 +26,10 @@ module Badline
|
|
|
26
26
|
# The drive comes with its interface for the computer's expansion port,
|
|
27
27
|
# a second 6523 (#host_tia), which the cable joins to the drive's
|
|
28
28
|
# (Cable). The computer puts it at $FEE0 for device 8 and at $FEC0 for
|
|
29
|
-
# device 9
|
|
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).
|
|
30
33
|
#
|
|
31
34
|
# A 555 timer pulls IRQ low for about 7 µs every 8.3 ms, its R1 120K,
|
|
32
35
|
# R2 100R and C1 0.1 µF, which runs the DOS's job loop. Nothing
|
|
@@ -101,10 +104,29 @@ module Badline
|
|
|
101
104
|
@irq_timer = IRQ_PERIOD
|
|
102
105
|
init_idle(debug)
|
|
103
106
|
@pass_counter = PASS_COUNTER
|
|
104
|
-
@
|
|
107
|
+
@dev_high = true
|
|
108
|
+
@on_renumber = nil
|
|
109
|
+
@cable = Cable.new(@host_tia, @tia, self)
|
|
105
110
|
@serial_port = @cable
|
|
106
111
|
end
|
|
107
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
|
+
|
|
108
130
|
# Puts in a Disk from the .d64 or .g64 image at +path+, write-protected
|
|
109
131
|
# with `read_only`.
|
|
110
132
|
def insert_image(path, read_only: false)
|
|
@@ -148,8 +170,8 @@ module Badline
|
|
|
148
170
|
end
|
|
149
171
|
|
|
150
172
|
def idle_state
|
|
151
|
-
|
|
152
|
-
[*@cpu.counting_state, @bus.data, *@mechanism.idle_state, *
|
|
173
|
+
tia = @tia
|
|
174
|
+
[*@cpu.counting_state, @bus.data, *@mechanism.idle_state, *tia.ports, *tia.directions, *tia.inputs,
|
|
153
175
|
@port.ddr, @port.data]
|
|
154
176
|
end
|
|
155
177
|
|
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)
|
|
@@ -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)
|
|
@@ -39,6 +39,7 @@ module Badline
|
|
|
39
39
|
@browsing = kind
|
|
40
40
|
extensions = case kind
|
|
41
41
|
when :cartridge then @media.cartridge_extensions
|
|
42
|
+
when :disk then @media.disk_extensions
|
|
42
43
|
when :program then programs
|
|
43
44
|
else EXTENSIONS.fetch(kind)
|
|
44
45
|
end
|
|
@@ -64,7 +65,7 @@ module Badline
|
|
|
64
65
|
# starts, and says whether it asks. Other files are left alone.
|
|
65
66
|
def drop(path)
|
|
66
67
|
extension = File.extname(path).downcase
|
|
67
|
-
return @media.insert(:disk, path) && false if
|
|
68
|
+
return @media.insert(:disk, path) && false if @media.disk_extensions.include?(extension)
|
|
68
69
|
return @media.insert(:tape, path) && false if EXTENSIONS[:tape].include?(extension)
|
|
69
70
|
return false unless programs.include?(extension)
|
|
70
71
|
|
|
@@ -99,13 +99,22 @@ module Badline
|
|
|
99
99
|
insert(:disk, set[index + step])
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
-
# The extensions of the cartridges the machine takes: .crt,
|
|
103
|
-
#
|
|
102
|
+
# The extensions of the cartridges the machine takes: .crt, on the
|
|
103
|
+
# Plus/4 raw .bin ROM images too, and none on the PET.
|
|
104
104
|
def cartridge_extensions
|
|
105
|
-
extensions = Media::Extensions.of(%i[cartridge])
|
|
105
|
+
extensions = Media::Extensions.of(@computer.family == :pet ? %i[none] : %i[cartridge])
|
|
106
106
|
@computer.family == :plus4 ? extensions + [Media::Plus4Media::ROM_IMAGE] : extensions
|
|
107
107
|
end
|
|
108
108
|
|
|
109
|
+
# The extensions of what goes in device 8: on the PET the 8050's and
|
|
110
|
+
# 8250's disk images besides the others, and no raw GCR, which only
|
|
111
|
+
# a serial true drive reads.
|
|
112
|
+
def disk_extensions
|
|
113
|
+
return Media::Extensions.of(%i[disk ieee_disk archive disk_list]) if @computer.family == :pet
|
|
114
|
+
|
|
115
|
+
Media::Extensions::DEVICE8
|
|
116
|
+
end
|
|
117
|
+
|
|
109
118
|
# Swaps the cartridge for the one at `path`, which power cycles the
|
|
110
119
|
# machine, and says whether it went in.
|
|
111
120
|
def attach_cartridge(path)
|
|
@@ -117,10 +126,10 @@ module Badline
|
|
|
117
126
|
# What a save of the machine is named after: the cartridge, the disk,
|
|
118
127
|
# the tape, or BASIC, without the bracketed parts of a file's name. A
|
|
119
128
|
# VIC-20's are named after its disk, or else the machine, and a
|
|
120
|
-
# Plus/4's after its disk or tape.
|
|
129
|
+
# Plus/4's or a PET's after its disk or tape.
|
|
121
130
|
def game_name
|
|
122
131
|
vic20 = @computer.family == :vic20
|
|
123
|
-
name = vic20 || @computer.family
|
|
132
|
+
name = vic20 || %i[plus4 pet].include?(@computer.family) ? "" : cartridge_name
|
|
124
133
|
disk = disk_path
|
|
125
134
|
name = File.basename(disk, File.extname(disk)) if name.empty? && !disk.empty?
|
|
126
135
|
name = tape_name if name.empty? && !vic20
|
|
@@ -136,14 +145,16 @@ module Badline
|
|
|
136
145
|
end
|
|
137
146
|
|
|
138
147
|
# The name the cartridge in the expansion port gives itself, or an
|
|
139
|
-
# empty one. A VIC-20
|
|
140
|
-
# from: a .crt's own, or else the
|
|
148
|
+
# empty one. A VIC-20 keeps its own (Vic20#cartridge_name). A Plus/4's
|
|
149
|
+
# is the name of the file it came from: a .crt's own, or else the
|
|
150
|
+
# file's. It names each of its
|
|
141
151
|
# cartridges with a ROM in, and a slot filled from elsewhere, such as
|
|
142
152
|
# a snapshot, by the slot.
|
|
143
153
|
def cartridge_name
|
|
144
154
|
case @computer.family
|
|
145
155
|
when :plus4 then @computer.cartridge_slots.map { |slot| slot_name(slot) }.uniq.join(", ")
|
|
146
|
-
when :vic20 then
|
|
156
|
+
when :vic20 then @computer.cartridge_name
|
|
157
|
+
when :pet then ""
|
|
147
158
|
else
|
|
148
159
|
cartridge = @computer.address_bus.cartridge
|
|
149
160
|
cartridge.nil? ? "" : cartridge.name
|
|
@@ -200,28 +211,16 @@ module Badline
|
|
|
200
211
|
|
|
201
212
|
def slot_name(slot) = named? ? @names.fetch(slot, slot.upcase) : slot.upcase
|
|
202
213
|
|
|
203
|
-
# Keeps the name of the cartridge at `path` in
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
#
|
|
214
|
+
# Keeps the name of the cartridge at `path` in a Plus/4 by the slots
|
|
215
|
+
# it fills, by xplus4's names, a raw image's in `slot` if it names
|
|
216
|
+
# one. Other media, and other machines' cartridges, leave the names
|
|
217
|
+
# alone.
|
|
207
218
|
def note_cartridge(computer, path, slot)
|
|
208
|
-
|
|
209
|
-
when :plus4
|
|
210
|
-
return unless Media.kind(computer, path) == :cartridge
|
|
211
|
-
|
|
212
|
-
name = file_name(path)
|
|
213
|
-
Media::Plus4Media.cartridge_chips(path, slot: slot.empty? ? nil : slot).each do |chip|
|
|
214
|
-
@names["c#{chip.bank + 1}#{chip.address < 0xc000 ? 'lo' : 'hi'}"] = name
|
|
215
|
-
end
|
|
216
|
-
when :vic20 then @names[""] = file_name(path) if vic20_cartridge?(path)
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
+
return unless computer.family == :plus4 && Media.kind(computer, path) == :cartridge
|
|
219
220
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
when :program then File.file?(path) && !Media::Vic20Cartridge.chips(path, Media.program_bytes(path)).nil?
|
|
224
|
-
else false
|
|
221
|
+
name = file_name(path)
|
|
222
|
+
Media::Plus4Media.cartridge_chips(path, slot: slot.empty? ? nil : slot).each do |chip|
|
|
223
|
+
@names["c#{chip.bank + 1}#{chip.address < 0xc000 ? 'lo' : 'hi'}"] = name
|
|
225
224
|
end
|
|
226
225
|
end
|
|
227
226
|
|
|
@@ -85,6 +85,9 @@ module Badline
|
|
|
85
85
|
# Whether the machine is a C128, whose F8 switches its screens.
|
|
86
86
|
def c128? = @computer.family == :c128
|
|
87
87
|
|
|
88
|
+
# Whether the machine is a PET, whose ports page shows its keyboard.
|
|
89
|
+
def pet? = @computer.family == :pet
|
|
90
|
+
|
|
88
91
|
private
|
|
89
92
|
|
|
90
93
|
def vic20? = @computer.family == :vic20
|
|
@@ -122,6 +125,8 @@ module Badline
|
|
|
122
125
|
end
|
|
123
126
|
|
|
124
127
|
def draw_expansion
|
|
128
|
+
return draw_pet_memory if pet?
|
|
129
|
+
|
|
125
130
|
info("CARTRIDGE", @media.cartridge_name)
|
|
126
131
|
return info("RAM EXPANSION", RAM_NAMES.fetch(@computer.ram_configuration)) if vic20?
|
|
127
132
|
|
|
@@ -151,6 +156,12 @@ module Badline
|
|
|
151
156
|
toggle(plus4? ? "SID CARD" : "SID", :sid, [%w[6581 8580], %i[sid6581 sid8580], model == :mos8580 ? 1 : 0])
|
|
152
157
|
end
|
|
153
158
|
|
|
159
|
+
# The PET's memory: its RAM, and the 8096's 64K board.
|
|
160
|
+
def draw_pet_memory
|
|
161
|
+
info("RAM", "#{@computer.ram_kb}K")
|
|
162
|
+
info("EXPANSION RAM", @computer.model.expansion ? "64K BOARD" : "")
|
|
163
|
+
end
|
|
164
|
+
|
|
154
165
|
# Saving now and by name, loading a named save, and the quicksaves
|
|
155
166
|
# and autosaves, newest first, which a press loads.
|
|
156
167
|
def draw_snapshots
|
|
@@ -166,10 +177,12 @@ module Badline
|
|
|
166
177
|
end
|
|
167
178
|
end
|
|
168
179
|
|
|
169
|
-
# The C128 names its model and the mode it runs in first,
|
|
170
|
-
#
|
|
180
|
+
# The C128 names its model and the mode it runs in first, the Plus/4
|
|
181
|
+
# and the PET their model and RAM, and the Plus/4 its RAM expansion if
|
|
182
|
+
# it has one.
|
|
171
183
|
def draw_power
|
|
172
184
|
info("MACHINE", "#{@computer.model.name.upcase}, #{@computer.mode.to_s.upcase} MODE") if c128?
|
|
185
|
+
info("MACHINE", @computer.model_label) if pet?
|
|
173
186
|
if plus4?
|
|
174
187
|
info("MACHINE", @computer.model_label)
|
|
175
188
|
expansion = @computer.ram_expansion_label
|
|
@@ -5,18 +5,28 @@ module Badline
|
|
|
5
5
|
# The pause menu's PORTS page, which MenuPages draws: what each C64
|
|
6
6
|
# control port has plugged in, a joystick or a pot device, and whether
|
|
7
7
|
# the keys type or drive the joysticks. The VIC-20 has one port, a
|
|
8
|
-
# joystick's, and the Plus/4 two joysticks and no pot devices.
|
|
8
|
+
# joystick's, and the Plus/4 two joysticks and no pot devices. The PET
|
|
9
|
+
# has no ports, and the page, KEYBOARD there, says where its keys are
|
|
10
|
+
# instead.
|
|
9
11
|
module MenuPorts
|
|
10
12
|
PORT_DEVICES = %i[joystick mouse paddles].freeze
|
|
11
13
|
PORT_NAMES = %w[JOY MOUSE PADDLES].freeze
|
|
12
14
|
PORT_ACTIONS = [%i[port1_joystick port1_mouse port1_paddles], %i[port2_joystick port2_mouse port2_paddles]].freeze
|
|
13
15
|
POTS = [%i[none mouse1 paddles1], %i[none mouse2 paddles2]].freeze
|
|
14
16
|
|
|
17
|
+
# Where the PET's keys without a character of their own are, on both
|
|
18
|
+
# keyboards, and then on the graphics one and on the business one.
|
|
19
|
+
PET_KEYS = ["PAGE DOWN: RUN/STOP", "HOME: CLR/HOME", "BACKSPACE: INST/DEL", "CTRL: RVS",
|
|
20
|
+
"ARROWS: CURSOR", "^: UP ARROW", "_: LEFT ARROW"].freeze
|
|
21
|
+
PET_GRAPHICS_KEYS = ["ESC: RUN/STOP", "SHIFT+LETTER: GRAPHIC", "ALT+SYMBOL: GRAPHIC"].freeze
|
|
22
|
+
PET_BUSINESS_KEYS = ["ESC: ESC", "TAB: TAB", "PAGE UP: REPEAT", "KEYPAD: KEYPAD", "ALT+KEY: SHIFT+KEY"].freeze
|
|
23
|
+
|
|
15
24
|
private
|
|
16
25
|
|
|
17
26
|
def draw_ports
|
|
18
27
|
return draw_vic20_port if @computer.family == :vic20
|
|
19
28
|
return draw_plus4_ports if @computer.family == :plus4
|
|
29
|
+
return draw_pet_keys if @computer.family == :pet
|
|
20
30
|
|
|
21
31
|
toggle("PORT 1", :port1, [PORT_NAMES, PORT_ACTIONS[0], PORT_DEVICES.index(port_device(1))])
|
|
22
32
|
toggle("PORT 2", :port2, [PORT_NAMES, PORT_ACTIONS[1], PORT_DEVICES.index(port_device(2))])
|
|
@@ -49,6 +59,17 @@ module Badline
|
|
|
49
59
|
info("WASD, LEFT SHIFT", "JOYSTICK #{3 - arrows}")
|
|
50
60
|
end
|
|
51
61
|
|
|
62
|
+
# The PET's keyboard, whose keys type the characters on the host's
|
|
63
|
+
# (PETKeyboard), and the keys that stand for the rest of its keys.
|
|
64
|
+
def draw_pet_keys
|
|
65
|
+
business = @computer.model.keyboard == :business
|
|
66
|
+
info("KEYBOARD", business ? "BUSINESS" : "GRAPHICS")
|
|
67
|
+
(PET_KEYS + (business ? PET_BUSINESS_KEYS : PET_GRAPHICS_KEYS)).each do |line|
|
|
68
|
+
@painter.text(@left, @top, line, MenuTheme::TEXT)
|
|
69
|
+
@top += MenuPages::LINE
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
52
73
|
# The toggle between the keyboard named and the joysticks, and
|
|
53
74
|
# whether the keys drive the joysticks.
|
|
54
75
|
def draw_keys(keyboard)
|
|
@@ -238,6 +238,7 @@ module Badline
|
|
|
238
238
|
def draw_sections
|
|
239
239
|
top = @top + 32
|
|
240
240
|
SECTIONS.each_with_index do |name, index|
|
|
241
|
+
name = "KEYBOARD" if PAGES[index] == :ports && @pages.pet?
|
|
241
242
|
@buttons.plain(@left + 6, top, name.ljust(14), PAGES[index], on: index == @section)
|
|
242
243
|
top += Buttons::HEIGHT + 2
|
|
243
244
|
end
|