badline 0.15.1 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +55 -0
- data/CLAUDE.md +8 -7
- data/README.md +128 -9
- data/doc/media.md +9 -0
- data/doc/pinned-behaviour.md +67 -0
- data/doc/snapshot-format.md +215 -18
- data/doc/snapshots.md +55 -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 +74 -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/model2031/ieee_port.rb +133 -0
- data/lib/badline/drives/model2031/keyed_state.rb +66 -0
- data/lib/badline/drives/model2031/parts.rb +25 -0
- data/lib/badline/drives/model2031.rb +92 -0
- 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 +24 -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 +151 -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 +29 -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/true_drive.rb +19 -6
- data/lib/badline/media/vic20_media.rb +2 -2
- data/lib/badline/media.rb +41 -17
- data/lib/badline/options/family.rb +17 -6
- data/lib/badline/options/help.rb +23 -3
- data/lib/badline/options/table.rb +22 -4
- data/lib/badline/options/validation.rb +84 -5
- data/lib/badline/options.rb +47 -16
- data/lib/badline/pet/bus/expansion.rb +200 -0
- data/lib/badline/pet/bus.rb +222 -0
- data/lib/badline/pet/canvas.rb +97 -0
- data/lib/badline/pet/cassettes.rb +63 -0
- data/lib/badline/pet/characters.rb +32 -0
- data/lib/badline/pet/cpu.rb +10 -0
- data/lib/badline/pet/crtc_video.rb +155 -0
- data/lib/badline/pet/discrete_video.rb +83 -0
- data/lib/badline/pet/ieee_drive.rb +76 -0
- data/lib/badline/pet/ieee_ports.rb +77 -0
- data/lib/badline/pet/ieee_traps.rb +80 -0
- data/lib/badline/pet/keyboard_ports.rb +74 -0
- data/lib/badline/pet/keyed_modules.rb +88 -0
- data/lib/badline/pet/keyed_parts.rb +127 -0
- data/lib/badline/pet/keyed_state.rb +42 -0
- data/lib/badline/pet/model.rb +163 -0
- data/lib/badline/pet/rom_sets.rb +89 -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 +244 -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 +169 -0
- data/lib/badline/roms/dos2031.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/characters-din.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-8296.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 +6 -1
- data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
- data/lib/badline/snapshot/field_doc.rb +17 -3
- data/lib/badline/snapshot/image.rb +28 -10
- data/lib/badline/snapshot/pet_setup.rb +88 -0
- data/lib/badline/snapshot/storage_setup.rb +7 -1
- data/lib/badline/snapshot/vice/pet.rb +117 -0
- data/lib/badline/snapshot/vice/pet_cpu.rb +67 -0
- data/lib/badline/snapshot/vice/pet_crtc.rb +246 -0
- data/lib/badline/snapshot/vice/pet_mem.rb +93 -0
- data/lib/badline/snapshot/vice/pet_pias.rb +56 -0
- data/lib/badline/snapshot/vice/pet_restore.rb +150 -0
- data/lib/badline/snapshot/vice/pet_via.rb +164 -0
- data/lib/badline/snapshot/vice.rb +1 -0
- 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 +70 -1
|
@@ -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,21 @@ 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, the 8096's 64K board, and the 8296's
|
|
160
|
+
# 128K with the jumpers that put RAM under its ROMs.
|
|
161
|
+
def draw_pet_memory
|
|
162
|
+
model = @computer.model
|
|
163
|
+
return draw_8296_memory if model.cbm8296?
|
|
164
|
+
|
|
165
|
+
info("RAM", "#{@computer.ram_kb}K")
|
|
166
|
+
info("EXPANSION RAM", model.expansion ? "64K BOARD" : "")
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def draw_8296_memory
|
|
170
|
+
info("RAM", "128K, 64K OF IT BANKED")
|
|
171
|
+
info("JUMPERS", @computer.jumpers.join(", ").upcase)
|
|
172
|
+
end
|
|
173
|
+
|
|
154
174
|
# Saving now and by name, loading a named save, and the quicksaves
|
|
155
175
|
# and autosaves, newest first, which a press loads.
|
|
156
176
|
def draw_snapshots
|
|
@@ -166,10 +186,12 @@ module Badline
|
|
|
166
186
|
end
|
|
167
187
|
end
|
|
168
188
|
|
|
169
|
-
# The C128 names its model and the mode it runs in first,
|
|
170
|
-
#
|
|
189
|
+
# The C128 names its model and the mode it runs in first, the Plus/4
|
|
190
|
+
# and the PET their model and RAM, and the Plus/4 its RAM expansion if
|
|
191
|
+
# it has one.
|
|
171
192
|
def draw_power
|
|
172
193
|
info("MACHINE", "#{@computer.model.name.upcase}, #{@computer.mode.to_s.upcase} MODE") if c128?
|
|
194
|
+
info("MACHINE", @computer.model_label) if pet?
|
|
173
195
|
if plus4?
|
|
174
196
|
info("MACHINE", @computer.model_label)
|
|
175
197
|
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
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Frontend
|
|
5
|
+
# The host's keys on a PET's key matrix, by the character each types
|
|
6
|
+
# (Keys.pet_key), so the host's " types the PET's " on either keyboard.
|
|
7
|
+
#
|
|
8
|
+
# The PET's SHIFT follows the host's, except while a key down needs it
|
|
9
|
+
# one way to type its character: then it is held that way until the
|
|
10
|
+
# key goes up, the last such key deciding.
|
|
11
|
+
#
|
|
12
|
+
# With either Alt down, a key that types a character presses the PET
|
|
13
|
+
# key its unshifted character is on with the PET's SHIFT, so the
|
|
14
|
+
# graphics on the graphics keyboard's symbol keys can be typed.
|
|
15
|
+
class PETKeyboard
|
|
16
|
+
LSHIFT = 225
|
|
17
|
+
RSHIFT = 229
|
|
18
|
+
LALT = 226
|
|
19
|
+
RALT = 230
|
|
20
|
+
|
|
21
|
+
# +layout+ is the PET's keyboard, :graphics or :business.
|
|
22
|
+
def initialize(keyboard, layout)
|
|
23
|
+
@keyboard = keyboard
|
|
24
|
+
@layout = layout
|
|
25
|
+
@held = {}
|
|
26
|
+
@left_shift = false
|
|
27
|
+
@right_shift = false
|
|
28
|
+
@left_alt = false
|
|
29
|
+
@right_alt = false
|
|
30
|
+
@forced = -1
|
|
31
|
+
@forced_shift = false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def key(scancode, down)
|
|
35
|
+
if [LSHIFT, RSHIFT].include?(scancode)
|
|
36
|
+
shift_key(scancode, down)
|
|
37
|
+
elsif [LALT, RALT].include?(scancode)
|
|
38
|
+
alt_key(scancode, down)
|
|
39
|
+
elsif down
|
|
40
|
+
key_down(scancode)
|
|
41
|
+
else
|
|
42
|
+
key_up(scancode)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Lets go of every key, SHIFT among them.
|
|
47
|
+
def release_all
|
|
48
|
+
@held.each_value { |key| @keyboard.release(key) }
|
|
49
|
+
@held = {}
|
|
50
|
+
@left_shift = false
|
|
51
|
+
@right_shift = false
|
|
52
|
+
@left_alt = false
|
|
53
|
+
@right_alt = false
|
|
54
|
+
@forced = -1
|
|
55
|
+
sync_shift
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def key_down(scancode)
|
|
61
|
+
key_up(scancode) if @held.key?(scancode)
|
|
62
|
+
key, shift = alt? ? shifted_key(scancode) : Keys.pet_key(scancode, shifted?, @layout)
|
|
63
|
+
return if key == :none
|
|
64
|
+
|
|
65
|
+
unless shift == :host
|
|
66
|
+
@forced = scancode
|
|
67
|
+
@forced_shift = shift == :on
|
|
68
|
+
end
|
|
69
|
+
sync_shift
|
|
70
|
+
@held[scancode] = key
|
|
71
|
+
@keyboard.press(key)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def key_up(scancode)
|
|
75
|
+
key = @held.delete(scancode)
|
|
76
|
+
return if key.nil?
|
|
77
|
+
|
|
78
|
+
@keyboard.release(key)
|
|
79
|
+
return unless @forced == scancode
|
|
80
|
+
|
|
81
|
+
@forced = -1
|
|
82
|
+
sync_shift
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def shift_key(scancode, down)
|
|
86
|
+
if scancode == LSHIFT
|
|
87
|
+
@left_shift = down
|
|
88
|
+
else
|
|
89
|
+
@right_shift = down
|
|
90
|
+
end
|
|
91
|
+
sync_shift
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def alt_key(scancode, down)
|
|
95
|
+
if scancode == LALT
|
|
96
|
+
@left_alt = down
|
|
97
|
+
else
|
|
98
|
+
@right_alt = down
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# The PET key for the scancode with Alt down: for a letter or a key
|
|
103
|
+
# that types a character, the key its unshifted character is on, with
|
|
104
|
+
# SHIFT, and for any other key the one it presses without Alt.
|
|
105
|
+
def shifted_key(scancode)
|
|
106
|
+
key, shift = Keys.pet_key(scancode, false, @layout)
|
|
107
|
+
return [key, shift] if key == :none
|
|
108
|
+
return [key, shift] unless scancode.between?(4, 29) || Keys::US_CHARACTERS.key?(scancode)
|
|
109
|
+
|
|
110
|
+
[key, :on]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def shifted? = @left_shift || @right_shift
|
|
114
|
+
|
|
115
|
+
def alt? = @left_alt || @right_alt
|
|
116
|
+
|
|
117
|
+
def sync_shift
|
|
118
|
+
shift = @forced == -1 ? shifted? : @forced_shift
|
|
119
|
+
@keyboard.release(:lshift)
|
|
120
|
+
@keyboard.press(:lshift) if shift
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -71,7 +71,8 @@ module Badline
|
|
|
71
71
|
else
|
|
72
72
|
timing = @computer.timing
|
|
73
73
|
@screen = Screen.new(@computer.video, timing.crop,
|
|
74
|
-
pixel_width: timing.pixel_width,
|
|
74
|
+
pixel_width: timing.pixel_width, dots: timing.dots,
|
|
75
|
+
frame_lines: timing.lines_per_frame)
|
|
75
76
|
end
|
|
76
77
|
end
|
|
77
78
|
|
|
@@ -39,19 +39,22 @@ module Badline
|
|
|
39
39
|
def self.page?(name) = name.empty? || PauseMenu::PAGES.any? { |page| page.to_s == name }
|
|
40
40
|
|
|
41
41
|
# Whether the family's machine has a key of the name. The Plus/4 has
|
|
42
|
-
# no RESTORE.
|
|
42
|
+
# no RESTORE, and the PET neither that nor joysticks.
|
|
43
43
|
def self.key?(name, family)
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
pet = family == :pet
|
|
45
|
+
(name == "restore" && !pet && family != :plus4) || (family == :c128 && LOCKS.include?(name)) ||
|
|
46
|
+
!keyboard_key(name, family).nil? || (!pet && !joystick_direction(name).nil?)
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
# The family's Keyboard key for a name such as space, return or a,
|
|
49
|
-
# on the C128 esc or keypad1,
|
|
50
|
-
#
|
|
51
|
-
# C128's.
|
|
50
|
+
# on the C128 esc or keypad1, on the Plus/4 help or crsr_up, or on
|
|
51
|
+
# the PET rvs or kp1, of either of its keyboards. The shifted cursor
|
|
52
|
+
# keys, such as cursor_up, are the C64's and the C128's.
|
|
52
53
|
def self.keyboard_key(name, family)
|
|
54
|
+
return nil if name == "none"
|
|
55
|
+
|
|
53
56
|
key = keyboard_matrix(family).flatten.find { |candidate| candidate.to_s == name }
|
|
54
|
-
return key unless key.nil? && family
|
|
57
|
+
return key unless key.nil? && !%i[plus4 pet].include?(family)
|
|
55
58
|
|
|
56
59
|
Input::Keyboard::COMBINATIONS.keys.find { |candidate| candidate.to_s == name }
|
|
57
60
|
end
|
|
@@ -60,6 +63,7 @@ module Badline
|
|
|
60
63
|
case family
|
|
61
64
|
when :c128 then C128::KEYBOARD_MATRIX
|
|
62
65
|
when :plus4 then Plus4::KeyboardPort::MATRIX
|
|
66
|
+
when :pet then PET::KeyboardPorts::GRAPHICS_MATRIX + PET::KeyboardPorts::BUSINESS_MATRIX
|
|
63
67
|
else Input::Keyboard::C64_MATRIX
|
|
64
68
|
end
|
|
65
69
|
end
|
|
@@ -223,8 +227,11 @@ module Badline
|
|
|
223
227
|
puts "Ejected the #{what}"
|
|
224
228
|
end
|
|
225
229
|
|
|
226
|
-
# Whether there's one in to eject. A VIC-20 has no cartridge to take
|
|
227
|
-
|
|
230
|
+
# Whether there's one in to eject. A VIC-20 has no cartridge to take
|
|
231
|
+
# out yet, and a PET no expansion port.
|
|
232
|
+
def ejectable?(computer, what)
|
|
233
|
+
(what != "cartridge" || !%i[vic20 pet].include?(computer.family)) && inserted?(computer, what)
|
|
234
|
+
end
|
|
228
235
|
|
|
229
236
|
def inserted?(computer, what)
|
|
230
237
|
return !computer.datasette.tape.nil? if what == "tape"
|
data/lib/badline/frontend.rb
CHANGED
|
@@ -15,6 +15,7 @@ require "badline/frontend/audio_device"
|
|
|
15
15
|
require "badline/frontend/sound"
|
|
16
16
|
require "badline/frontend/pacer"
|
|
17
17
|
require "badline/frontend/frame_report"
|
|
18
|
+
require "badline/frontend/pet_keyboard"
|
|
18
19
|
require "badline/frontend/controls"
|
|
19
20
|
require "badline/frontend/pad_port"
|
|
20
21
|
require "badline/frontend/gamepads"
|
|
@@ -19,7 +19,7 @@ module Badline
|
|
|
19
19
|
@channels.close(secondary)
|
|
20
20
|
return report(FILE_EXISTS)
|
|
21
21
|
end
|
|
22
|
-
return refuse_write(secondary) unless writable_disk?
|
|
22
|
+
return refuse_write(secondary) unless writable_disk? || channel_save?(secondary)
|
|
23
23
|
|
|
24
24
|
open_writer(secondary, WriteFile.new(file, type || :seq, mode: replace ? :replace : :write))
|
|
25
25
|
end
|
|
@@ -41,6 +41,8 @@ module Badline
|
|
|
41
41
|
|
|
42
42
|
def writable_disk? = @storage.respond_to?(:writable?) && @storage.writable?
|
|
43
43
|
|
|
44
|
+
def channel_save?(secondary) = @channel_saves && secondary == 1 && saves?
|
|
45
|
+
|
|
44
46
|
# Runs a write to the disk and reports how it went. Returns whether
|
|
45
47
|
# the disk took it.
|
|
46
48
|
def writing
|
|
@@ -68,7 +68,12 @@ module Badline
|
|
|
68
68
|
|
|
69
69
|
# A drive powers on reporting its DOS version, as a reset does. It
|
|
70
70
|
# can take the RAM of a drive it replaces, and initializes its disk.
|
|
71
|
-
|
|
71
|
+
# With +channel_saves+, SAVE's channel, secondary address 1, takes a
|
|
72
|
+
# program for storage that saves only whole files, such as a host
|
|
73
|
+
# directory, and saves it when the channel closes: a PET's SAVE sends
|
|
74
|
+
# it through the bus with no trap of its own.
|
|
75
|
+
def initialize(storage, memory = Memory.new, channel_saves: false)
|
|
76
|
+
@channel_saves = channel_saves
|
|
72
77
|
@channels = Channels.new
|
|
73
78
|
@status = Status.new
|
|
74
79
|
@memory = memory
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module KernalTrap
|
|
5
|
+
# The frames a drive takes and sends through the traps on a KERNAL's
|
|
6
|
+
# eight bus primitives, shared by the serial bus's (Serial) and the
|
|
7
|
+
# IEEE-488 bus's (IEEE): the traps going in and out, the bytes a
|
|
8
|
+
# listener gathers into a frame for the DOS, the bytes a talker sends,
|
|
9
|
+
# and the status they leave. The class that includes it, with Steps,
|
|
10
|
+
# answers each primitive and keeps @dos, @listening, @talking,
|
|
11
|
+
# @listen_channel, @talk_channel, @frame and @buffer.
|
|
12
|
+
module Frames
|
|
13
|
+
# Frame type in the high nibble of the secondary address
|
|
14
|
+
OPEN = 0xf0
|
|
15
|
+
CLOSE = 0xe0
|
|
16
|
+
|
|
17
|
+
# ST bits
|
|
18
|
+
DEVICE_NOT_PRESENT = 0x80
|
|
19
|
+
EOI = 0x40
|
|
20
|
+
READ_TIMEOUT = 0x02
|
|
21
|
+
|
|
22
|
+
NO_DATA = [0x0d, EOI | READ_TIMEOUT].freeze
|
|
23
|
+
|
|
24
|
+
def install
|
|
25
|
+
routines.each do |address, routine|
|
|
26
|
+
@cpu.install_trap(address) { call_routine(routine) if kernal_mapped? }
|
|
27
|
+
end
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Takes the traps out again, so the KERNAL's own routines run.
|
|
32
|
+
def uninstall
|
|
33
|
+
routines.each_key { |address| @cpu.remove_trap(address) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def routines
|
|
39
|
+
{
|
|
40
|
+
@layout.talk => :talk,
|
|
41
|
+
@layout.listen => :listen,
|
|
42
|
+
@layout.second => :second,
|
|
43
|
+
@layout.tksa => :tksa,
|
|
44
|
+
@layout.ciout => :ciout,
|
|
45
|
+
@layout.untalk => :untalk,
|
|
46
|
+
@layout.unlisten => :unlisten,
|
|
47
|
+
@layout.acptr => :acptr
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def call_routine(routine)
|
|
52
|
+
case routine
|
|
53
|
+
when :talk then talk
|
|
54
|
+
when :listen then listen
|
|
55
|
+
when :second then second
|
|
56
|
+
when :tksa then tksa
|
|
57
|
+
when :ciout then ciout
|
|
58
|
+
when :untalk then untalk
|
|
59
|
+
when :unlisten then unlisten
|
|
60
|
+
when :acptr then acptr
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# CIOUT holds each byte back until the next one, or the UNLISTEN,
|
|
65
|
+
# comes, so the first byte of a frame sends nothing.
|
|
66
|
+
def ciout
|
|
67
|
+
return unless @listening
|
|
68
|
+
|
|
69
|
+
time_serial_byte(Routine::ISOUR_TIMEOUT) if !@buffer.empty? && accepted?
|
|
70
|
+
@buffer << @cpu.a
|
|
71
|
+
update_status(DEVICE_NOT_PRESENT) unless accepted?
|
|
72
|
+
@cpu.status.carry = false
|
|
73
|
+
return_to_caller
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# The drive acknowledges every byte of an open or close frame, but
|
|
77
|
+
# takes data only on a channel it has open. Otherwise nothing holds
|
|
78
|
+
# the data line when a byte starts, so the KERNAL finds no device.
|
|
79
|
+
def accepted?
|
|
80
|
+
[OPEN, CLOSE].include?(@frame) || @dos.listening?(@listen_channel)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# UNLSN and UNTLK end by releasing the bus, and leave the port's last
|
|
84
|
+
# read in the accumulator.
|
|
85
|
+
def release_bus
|
|
86
|
+
@cpu.a = @layout.release_serial_lines(@bus)
|
|
87
|
+
@cpu.status.negative = @cpu.a.anybits?(0x80)
|
|
88
|
+
@cpu.status.zero = @cpu.a.zero?
|
|
89
|
+
return_to_caller
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def deliver_frame
|
|
93
|
+
return unless @listen_channel
|
|
94
|
+
|
|
95
|
+
case @frame
|
|
96
|
+
when OPEN then @dos.open(@listen_channel, @buffer.pack("C*"))
|
|
97
|
+
when CLOSE then @dos.close(@listen_channel)
|
|
98
|
+
else @dos.write(@listen_channel, @buffer)
|
|
99
|
+
end
|
|
100
|
+
@buffer = []
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def received
|
|
104
|
+
byte, eoi = @dos.read(@talk_channel)
|
|
105
|
+
return NO_DATA unless byte
|
|
106
|
+
|
|
107
|
+
[byte, eoi ? EOI : 0]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def update_status(bits)
|
|
111
|
+
return if bits.zero?
|
|
112
|
+
|
|
113
|
+
@bus.poke(zero_page.status, @bus.peek(zero_page.status) | bits)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|