badline 0.13.0 → 0.14.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 +49 -1
- data/CLAUDE.md +27 -7
- data/CONTRIBUTING.md +4 -3
- data/README.md +8 -5
- data/doc/media.md +2 -1
- data/doc/pinned-behaviour.md +29 -0
- data/doc/snapshot-format.md +1047 -0
- data/doc/snapshots.md +72 -39
- data/lib/badline/audio/bare_player.rb +50 -17
- data/lib/badline/audio/machine_player.rb +15 -9
- data/lib/badline/audio/renderer.rb +32 -21
- data/lib/badline/c128/banks.rb +6 -3
- data/lib/badline/c128/bus/keyed_state.rb +45 -0
- data/lib/badline/c128/bus.rb +17 -3
- data/lib/badline/c128/drives.rb +6 -25
- data/lib/badline/c128/keyed_modules.rb +117 -0
- data/lib/badline/c128/keyed_state.rb +91 -0
- data/lib/badline/c128/keys.rb +9 -0
- data/lib/badline/c128/mmu.rb +9 -8
- data/lib/badline/c128/mmu_pages.rb +20 -0
- data/lib/badline/c128/modes.rb +19 -9
- data/lib/badline/c128/z80_turns.rb +1 -1
- data/lib/badline/c128.rb +8 -8
- data/lib/badline/c64/attachments.rb +0 -44
- data/lib/badline/c64/banked_ram/plus256k.rb +18 -10
- data/lib/badline/c64/banked_ram/plus60k.rb +8 -6
- data/lib/badline/c64/banked_ram/unexpanded.rb +2 -2
- data/lib/badline/c64/bus/debug_register.rb +32 -0
- data/lib/badline/c64/bus/keyed_state.rb +41 -0
- data/lib/badline/c64/bus.rb +2 -1
- data/lib/badline/c64/cartridge/action_replay.rb +0 -13
- data/lib/badline/c64/cartridge/easy_flash.rb +0 -15
- data/lib/badline/c64/cartridge/epyx_fastload.rb +0 -8
- data/lib/badline/c64/cartridge/final_cartridge3.rb +0 -12
- data/lib/badline/c64/cartridge/flash.rb +3 -3
- data/lib/badline/c64/cartridge/g_mod2.rb +0 -12
- data/lib/badline/c64/cartridge/geo_ram.rb +0 -16
- data/lib/badline/c64/cartridge/kcs_power.rb +0 -8
- data/lib/badline/c64/cartridge/keyed_state.rb +393 -0
- data/lib/badline/c64/cartridge/pagefox.rb +0 -10
- data/lib/badline/c64/cartridge/retro_replay.rb +0 -8
- data/lib/badline/c64/cartridge/rgcd.rb +0 -10
- data/lib/badline/c64/cartridge/super_games.rb +0 -10
- data/lib/badline/c64/cartridge/zaxxon.rb +0 -16
- data/lib/badline/c64/cartridge.rb +1 -27
- data/lib/badline/c64/kernal_traps.rb +28 -25
- data/lib/badline/c64/keyed_modules.rb +111 -0
- data/lib/badline/c64/keyed_state.rb +68 -0
- data/lib/badline/c64/reu/dma.rb +0 -4
- data/lib/badline/c64/reu/keyed_state.rb +140 -0
- data/lib/badline/c64/reu/ram.rb +0 -15
- data/lib/badline/c64/reu.rb +1 -3
- data/lib/badline/c64/true_drives.rb +56 -5
- data/lib/badline/c64.rb +7 -3
- data/lib/badline/chips/cia/interrupt_register.rb +0 -17
- data/lib/badline/chips/cia/keyed_parts.rb +213 -0
- data/lib/badline/chips/cia/serial.rb +0 -4
- data/lib/badline/chips/cia/time_of_day.rb +0 -22
- data/lib/badline/chips/cia/timer.rb +0 -20
- data/lib/badline/chips/cia.rb +2 -2
- data/lib/badline/chips/cpu/core.rb +18 -2
- data/lib/badline/chips/cpu/keyed_state.rb +177 -0
- data/lib/badline/chips/cpu/microcode.rb +16 -0
- data/lib/badline/chips/cpu/port.rb +20 -3
- data/lib/badline/chips/cpu.rb +5 -0
- data/lib/badline/chips/sid/decimator.rb +10 -8
- data/lib/badline/chips/sid/envelope.rb +1 -6
- data/lib/badline/chips/sid/filter.rb +0 -14
- data/lib/badline/chips/sid/keyed_parts.rb +286 -0
- data/lib/badline/chips/sid/voice.rb +0 -10
- data/lib/badline/chips/sid/waveform.rb +0 -2
- data/lib/badline/chips/sid.rb +1 -2
- data/lib/badline/chips/ted/beam.rb +119 -0
- data/lib/badline/chips/ted/timers.rb +66 -0
- data/lib/badline/chips/ted.rb +143 -0
- data/lib/badline/chips/tia.rb +49 -0
- data/lib/badline/chips/vdc/keyed_parts.rb +105 -0
- data/lib/badline/chips/vdc/painter.rb +0 -12
- data/lib/badline/chips/vdc/raster.rb +0 -16
- data/lib/badline/chips/vdc/window.rb +0 -9
- data/lib/badline/chips/vdc.rb +1 -2
- data/lib/badline/chips/via/control_lines.rb +0 -15
- data/lib/badline/chips/via/interrupt_register.rb +0 -9
- data/lib/badline/chips/via/keyed_parts.rb +163 -0
- data/lib/badline/chips/via/shift_register.rb +0 -14
- data/lib/badline/chips/via/timer1.rb +0 -13
- data/lib/badline/chips/via/timer2.rb +0 -18
- data/lib/badline/chips/via.rb +1 -2
- data/lib/badline/chips/vic/border_mask.rb +0 -10
- data/lib/badline/chips/vic/collisions.rb +0 -11
- data/lib/badline/chips/vic/color_patches.rb +0 -11
- data/lib/badline/chips/vic/display_state.rb +0 -19
- data/lib/badline/chips/vic/graphics_shifter.rb +0 -4
- data/lib/badline/chips/vic/keyed_parts.rb +228 -0
- data/lib/badline/chips/vic/keyed_sprites.rb +225 -0
- data/lib/badline/chips/vic/keyed_state.rb +102 -0
- data/lib/badline/chips/vic/register_log.rb +0 -18
- data/lib/badline/chips/vic/registers.rb +0 -9
- data/lib/badline/chips/vic/sequencer.rb +0 -2
- data/lib/badline/chips/vic/sprite/internal_bus.rb +0 -11
- data/lib/badline/chips/vic/sprite.rb +0 -2
- data/lib/badline/chips/vic/sprites.rb +0 -3
- data/lib/badline/chips/vic.rb +6 -2
- data/lib/badline/chips/vic_i/keyed_parts.rb +142 -0
- data/lib/badline/chips/vic_i/painter.rb +0 -4
- data/lib/badline/chips/vic_i/sound.rb +38 -46
- data/lib/badline/chips/vic_i.rb +2 -2
- data/lib/badline/chips/z80/core.rb +2 -2
- data/lib/badline/chips/z80/keyed_state.rb +73 -0
- data/lib/badline/chips.rb +10 -0
- data/lib/badline/core.rb +4 -21
- data/lib/badline/drives/datasette/keyed_state.rb +38 -0
- data/lib/badline/drives/datasette.rb +40 -7
- data/lib/badline/drives/engine/core.rb +3 -0
- data/lib/badline/drives/engine/serial_lines.rb +4 -7
- data/lib/badline/drives/model1541/bus.rb +0 -13
- data/lib/badline/drives/model1541/disk.rb +9 -8
- data/lib/badline/drives/model1541/keyed_state.rb +251 -0
- data/lib/badline/drives/model1541/mechanism.rb +0 -2
- data/lib/badline/drives/model1541.rb +1 -2
- data/lib/badline/drives/model1571/bus.rb +0 -13
- data/lib/badline/drives/model1571/keyed_state.rb +108 -0
- data/lib/badline/drives/model1571/wd1770.rb +0 -14
- data/lib/badline/drives/model1571.rb +1 -2
- data/lib/badline/drives/model1581/bus.rb +0 -13
- data/lib/badline/drives/model1581/disk.rb +0 -4
- data/lib/badline/drives/model1581/keyed_state.rb +280 -0
- data/lib/badline/drives/model1581/mechanism.rb +0 -21
- data/lib/badline/drives/model1581/slot.rb +0 -18
- data/lib/badline/drives/model1581/wd1772.rb +0 -2
- data/lib/badline/drives/model1581.rb +2 -2
- data/lib/badline/drives.rb +8 -0
- data/lib/badline/frontend/app.rb +3 -3
- data/lib/badline/frontend/boot.rb +1 -1
- data/lib/badline/frontend/controls.rb +19 -3
- data/lib/badline/frontend/drive_led.rb +44 -21
- data/lib/badline/frontend/menu/menu_dialogs.rb +2 -2
- data/lib/badline/frontend/snapshots.rb +12 -1
- data/lib/badline/frontend/timeline.rb +28 -10
- data/lib/badline/input/matrix_typist.rb +98 -0
- data/lib/badline/input.rb +1 -0
- data/lib/badline/kernal_trap/chrout.rb +45 -0
- data/lib/badline/kernal_trap/dos/channel.rb +0 -27
- data/lib/badline/kernal_trap/dos/channels.rb +0 -25
- data/lib/badline/kernal_trap/dos/memory.rb +0 -9
- data/lib/badline/kernal_trap/dos/status.rb +0 -8
- data/lib/badline/kernal_trap/dos/write_file.rb +0 -18
- data/lib/badline/kernal_trap/dos.rb +0 -19
- data/lib/badline/kernal_trap/file_routine.rb +4 -4
- data/lib/badline/kernal_trap/keyed_state.rb +150 -0
- data/lib/badline/kernal_trap/layout.rb +75 -11
- data/lib/badline/kernal_trap/load.rb +14 -16
- data/lib/badline/kernal_trap/routine.rb +12 -0
- data/lib/badline/kernal_trap/save.rb +8 -18
- data/lib/badline/kernal_trap/serial.rb +1 -17
- data/lib/badline/kernal_trap.rb +2 -0
- data/lib/badline/machine/keyboard_buffer.rb +49 -0
- data/lib/badline/machine/settling.rb +29 -0
- data/lib/badline/machine.rb +5 -2
- data/lib/badline/media/boot_disk.rb +14 -2
- data/lib/badline/media/extensions.rb +10 -0
- data/lib/badline/memory.rb +3 -6
- data/lib/badline/options/family.rb +5 -2
- data/lib/badline/options/help.rb +42 -16
- data/lib/badline/options/table.rb +2 -0
- data/lib/badline/options/validation.rb +2 -0
- data/lib/badline/options.rb +6 -1
- data/lib/badline/plus4/acia.rb +45 -0
- data/lib/badline/plus4/bus.rb +312 -0
- data/lib/badline/plus4/cpu.rb +10 -0
- data/lib/badline/plus4/cpu_port.rb +93 -0
- data/lib/badline/plus4/keyboard_port.rb +51 -0
- data/lib/badline/plus4/model.rb +38 -0
- data/lib/badline/plus4/user_port.rb +31 -0
- data/lib/badline/plus4.rb +164 -0
- data/lib/badline/roms/README +38 -0
- data/lib/badline/roms/plus4/basic.rom +0 -0
- data/lib/badline/roms/plus4/function-high.rom +0 -0
- data/lib/badline/roms/plus4/function-low.rom +0 -0
- data/lib/badline/roms/plus4/kernal-ntsc.rom +0 -0
- data/lib/badline/roms/plus4/kernal-pal.rom +0 -0
- data/lib/badline/snapshot/c128_setup.rb +9 -20
- data/lib/badline/snapshot/c64_setup.rb +28 -24
- data/lib/badline/snapshot/container.rb +4 -0
- data/lib/badline/snapshot/field_doc/markdown.rb +112 -0
- data/lib/badline/snapshot/field_doc.rb +109 -0
- data/lib/badline/snapshot/image.rb +134 -28
- data/lib/badline/snapshot/keyed/cursor.rb +40 -0
- data/lib/badline/snapshot/keyed/modules.rb +66 -0
- data/lib/badline/snapshot/keyed/reader.rb +188 -0
- data/lib/badline/snapshot/keyed/recorder.rb +88 -0
- data/lib/badline/snapshot/keyed/report.rb +26 -0
- data/lib/badline/snapshot/keyed/writer.rb +55 -0
- data/lib/badline/snapshot/keyed.rb +64 -0
- data/lib/badline/snapshot/machine_state.rb +17 -64
- data/lib/badline/snapshot/storage_setup.rb +29 -34
- data/lib/badline/snapshot/vic20_setup.rb +6 -15
- data/lib/badline/snapshot/vice/reu1764.rb +2 -2
- data/lib/badline/snapshot/vice.rb +7 -31
- data/lib/badline/snapshot.rb +43 -20
- data/lib/badline/storage/crt_file.rb +3 -1
- data/lib/badline/storage/g64_image.rb +8 -3
- data/lib/badline/storage/tap.rb +43 -15
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic20/attachments.rb +3 -0
- data/lib/badline/vic20/bus/keyed_state.rb +52 -0
- data/lib/badline/vic20/bus.rb +1 -2
- data/lib/badline/vic20/keyed_modules.rb +73 -0
- data/lib/badline/vic20/keyed_state.rb +45 -0
- data/lib/badline/vic20.rb +5 -5
- data/lib/badline.rb +1 -0
- metadata +61 -42
- data/lib/badline/c128/bus/saved_state.rb +0 -53
- data/lib/badline/c128/saved_state.rb +0 -117
- data/lib/badline/c64/bus/saved_state.rb +0 -68
- data/lib/badline/c64/cartridge/flash/saved_state.rb +0 -40
- data/lib/badline/c64/cartridge/retro_replay/saved_state.rb +0 -47
- data/lib/badline/c64/cartridge/saved_state.rb +0 -63
- data/lib/badline/c64/reu/dma/saved_state.rb +0 -52
- data/lib/badline/c64/reu/saved_state.rb +0 -55
- data/lib/badline/c64/saved_state.rb +0 -112
- data/lib/badline/chips/cia/saved_state.rb +0 -49
- data/lib/badline/chips/cia/serial/saved_state.rb +0 -45
- data/lib/badline/chips/cpu/saved_state.rb +0 -117
- data/lib/badline/chips/sid/envelope/saved_state.rb +0 -70
- data/lib/badline/chips/sid/filter/saved_state.rb +0 -42
- data/lib/badline/chips/sid/saved_state.rb +0 -52
- data/lib/badline/chips/sid/waveform/saved_state.rb +0 -68
- data/lib/badline/chips/vdc/saved_state.rb +0 -36
- data/lib/badline/chips/via/saved_state.rb +0 -44
- data/lib/badline/chips/vic/graphics_shifter/saved_state.rb +0 -40
- data/lib/badline/chips/vic/saved_state.rb +0 -82
- data/lib/badline/chips/vic/sequencer/saved_state.rb +0 -91
- data/lib/badline/chips/vic/sprite/saved_state.rb +0 -66
- data/lib/badline/chips/vic/sprites/saved_state.rb +0 -59
- data/lib/badline/chips/vic_i/painter/saved_state.rb +0 -40
- data/lib/badline/chips/vic_i/saved_state.rb +0 -55
- data/lib/badline/chips/z80/saved_state.rb +0 -71
- data/lib/badline/chrout_trap.rb +0 -43
- data/lib/badline/debug_register.rb +0 -28
- data/lib/badline/drives/datasette/saved_state.rb +0 -37
- data/lib/badline/drives/model1541/disk/saved_state.rb +0 -88
- data/lib/badline/drives/model1541/mechanism/saved_state.rb +0 -72
- data/lib/badline/drives/model1541/saved_state.rb +0 -44
- data/lib/badline/drives/model1571/saved_state.rb +0 -47
- data/lib/badline/drives/model1581/disk/saved_state.rb +0 -89
- data/lib/badline/drives/model1581/saved_state.rb +0 -42
- data/lib/badline/drives/model1581/wd1772/saved_state.rb +0 -68
- data/lib/badline/keyboard_buffer.rb +0 -33
- data/lib/badline/snapshot/setup.rb +0 -31
- data/lib/badline/snapshot/state.rb +0 -179
- data/lib/badline/vic20/bus/saved_state.rb +0 -35
- data/lib/badline/vic20/saved_state.rb +0 -110
|
@@ -21,10 +21,13 @@ module Badline
|
|
|
21
21
|
@failed = false
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
# The C128's two locking keys, outside its matrix.
|
|
25
|
+
LOCKS = %w[caps_lock forty_eighty].freeze
|
|
26
|
+
|
|
27
|
+
# What's wrong with the events for the family's machine, the first
|
|
28
|
+
# key or menu page that names nothing, or an empty string.
|
|
29
|
+
def self.error(events, family)
|
|
30
|
+
event = events.find { |candidate| candidate.action == "key" && !key?(candidate.argument, family) }
|
|
28
31
|
return "no such key: #{event.argument}" unless event.nil?
|
|
29
32
|
|
|
30
33
|
event = events.find { |candidate| candidate.action == "menu" && !page?(candidate.argument) }
|
|
@@ -35,11 +38,16 @@ module Badline
|
|
|
35
38
|
# sound. An empty one is its first.
|
|
36
39
|
def self.page?(name) = name.empty? || PauseMenu::PAGES.any? { |page| page.to_s == name }
|
|
37
40
|
|
|
38
|
-
def self.key?(name
|
|
41
|
+
def self.key?(name, family)
|
|
42
|
+
name == "restore" || (family == :c128 && LOCKS.include?(name)) ||
|
|
43
|
+
!keyboard_key(name, family).nil? || !joystick_direction(name).nil?
|
|
44
|
+
end
|
|
39
45
|
|
|
40
|
-
# The
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
# The family's Keyboard key for a name such as space, return or a,
|
|
47
|
+
# or on the C128 esc or keypad1.
|
|
48
|
+
def self.keyboard_key(name, family)
|
|
49
|
+
matrix = family == :c128 ? C128::KEYBOARD_MATRIX : Input::Keyboard::C64_MATRIX
|
|
50
|
+
key = matrix.flatten.find { |candidate| candidate.to_s == name }
|
|
43
51
|
key.nil? ? Input::Keyboard::COMBINATIONS.keys.find { |candidate| candidate.to_s == name } : key
|
|
44
52
|
end
|
|
45
53
|
|
|
@@ -130,6 +138,8 @@ module Badline
|
|
|
130
138
|
|
|
131
139
|
def press(computer, event)
|
|
132
140
|
name = event.argument
|
|
141
|
+
return lock(computer, name) if LOCKS.include?(name)
|
|
142
|
+
|
|
133
143
|
if name == "restore"
|
|
134
144
|
computer.press_restore
|
|
135
145
|
return hold(event)
|
|
@@ -137,13 +147,21 @@ module Badline
|
|
|
137
147
|
|
|
138
148
|
direction = Timeline.joystick_direction(name)
|
|
139
149
|
if direction.nil?
|
|
140
|
-
computer.keyboard.press(Timeline.keyboard_key(name))
|
|
150
|
+
computer.keyboard.press(Timeline.keyboard_key(name, computer.family))
|
|
141
151
|
else
|
|
142
152
|
joystick(computer, name).press(direction)
|
|
143
153
|
end
|
|
144
154
|
hold(event)
|
|
145
155
|
end
|
|
146
156
|
|
|
157
|
+
# CAPS LOCK and 40/80 DISPLAY lock: a push puts one down if it was up,
|
|
158
|
+
# and up if it was down.
|
|
159
|
+
def lock(computer, name)
|
|
160
|
+
return unless computer.family == :c128
|
|
161
|
+
|
|
162
|
+
name == "caps_lock" ? computer.push_caps_lock : computer.push_display_key
|
|
163
|
+
end
|
|
164
|
+
|
|
147
165
|
# The VIC-20 and the C128 have no cartridge with a button yet.
|
|
148
166
|
def freeze(computer, event)
|
|
149
167
|
return unless computer.family == :c64
|
|
@@ -164,7 +182,7 @@ module Badline
|
|
|
164
182
|
|
|
165
183
|
direction = Timeline.joystick_direction(name)
|
|
166
184
|
if direction.nil?
|
|
167
|
-
computer.keyboard.release(Timeline.keyboard_key(name))
|
|
185
|
+
computer.keyboard.release(Timeline.keyboard_key(name, computer.family))
|
|
168
186
|
else
|
|
169
187
|
joystick(computer, name).release(direction)
|
|
170
188
|
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Input
|
|
5
|
+
# Types PETSCII codes on a Keyboard's matrix, one key at a time, for
|
|
6
|
+
# software that scans the keys itself instead of reading a KERNAL's
|
|
7
|
+
# keyboard buffer: each code's keys go down for HOLD cycles, then up for
|
|
8
|
+
# GAP cycles. Codes it has no key for are skipped.
|
|
9
|
+
class MatrixTypist
|
|
10
|
+
HOLD = 100_000
|
|
11
|
+
GAP = 40_000
|
|
12
|
+
|
|
13
|
+
# The keys typed without SHIFT, by PETSCII code.
|
|
14
|
+
PLAIN = {
|
|
15
|
+
0x0d => :return, 0x20 => :space, 0x2a => :*, 0x2b => :+, 0x2c => :",", 0x2d => :-, 0x2e => :".",
|
|
16
|
+
0x2f => :/, 0x3a => :":", 0x3b => :";", 0x3d => :"=", 0x40 => :"@"
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
# The keys typed with SHIFT, by PETSCII code.
|
|
20
|
+
SHIFTED = {
|
|
21
|
+
0x21 => :"1", 0x22 => :"2", 0x23 => :"3", 0x24 => :"4", 0x25 => :"5", 0x26 => :"6", 0x27 => :"7",
|
|
22
|
+
0x28 => :"8", 0x29 => :"9", 0x3c => :",", 0x3e => :".", 0x3f => :/, 0x5b => :":", 0x5d => :";"
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
DIGITS = %i[0 1 2 3 4 5 6 7 8 9].freeze
|
|
26
|
+
LETTERS = %i[a b c d e f g h i j k l m n o p q r s t u v w x y z].freeze
|
|
27
|
+
|
|
28
|
+
# The keys that type the PETSCII code, SHIFT first, or none.
|
|
29
|
+
def self.keys(code)
|
|
30
|
+
return [DIGITS[code - 0x30]] if code.between?(0x30, 0x39)
|
|
31
|
+
return [LETTERS[code - 0x41]] if code.between?(0x41, 0x5a)
|
|
32
|
+
return [:lshift, LETTERS[code - 0xc1]] if code.between?(0xc1, 0xda)
|
|
33
|
+
return [PLAIN[code]] if PLAIN.key?(code)
|
|
34
|
+
return [:lshift, SHIFTED[code]] if SHIFTED.key?(code)
|
|
35
|
+
|
|
36
|
+
[]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def initialize(keyboard)
|
|
40
|
+
@keyboard = keyboard
|
|
41
|
+
@held = []
|
|
42
|
+
@code = 0
|
|
43
|
+
@wait = 0
|
|
44
|
+
@typing = false
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Whether it is part way through codes: from the first key down until
|
|
48
|
+
# the last one is up and its gap has passed.
|
|
49
|
+
def typing? = @typing
|
|
50
|
+
|
|
51
|
+
# A cycle of typing +codes+, taking each code off its front as its keys
|
|
52
|
+
# go down.
|
|
53
|
+
def cycle!(codes)
|
|
54
|
+
@wait -= 1 if @wait.positive?
|
|
55
|
+
return unless @wait.zero?
|
|
56
|
+
|
|
57
|
+
if @held.empty?
|
|
58
|
+
press(codes)
|
|
59
|
+
else
|
|
60
|
+
@held.each { |key| @keyboard.release(key) }
|
|
61
|
+
@held = []
|
|
62
|
+
@wait = GAP
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Where it is in its typing, in a keyed snapshot module under `prefix`:
|
|
67
|
+
# the code whose keys are down, if any, the cycles left before they
|
|
68
|
+
# go up or the next code's go down, and whether it is part way
|
|
69
|
+
# through. The keys go down again on load.
|
|
70
|
+
def save_fields(out, prefix)
|
|
71
|
+
out.optional_int("#{prefix}code", @held.empty? ? nil : @code)
|
|
72
|
+
out.int("#{prefix}wait", @wait).bool("#{prefix}typing", @typing)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def load_fields(input, prefix)
|
|
76
|
+
@held.each { |key| @keyboard.release(key) }
|
|
77
|
+
code = input.optional_int("#{prefix}code")
|
|
78
|
+
@code = code || 0
|
|
79
|
+
@held = code.nil? ? [] : MatrixTypist.keys(code)
|
|
80
|
+
@held.each { |key| @keyboard.press(key) }
|
|
81
|
+
@wait = input.int("#{prefix}wait", @wait)
|
|
82
|
+
@typing = input.bool("#{prefix}typing", @typing)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def press(codes)
|
|
88
|
+
@typing = !codes.empty?
|
|
89
|
+
return unless @typing
|
|
90
|
+
|
|
91
|
+
@code = codes.shift
|
|
92
|
+
@held = MatrixTypist.keys(@code)
|
|
93
|
+
@held.each { |key| @keyboard.press(key) }
|
|
94
|
+
@wait = @held.empty? ? 1 : HOLD
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
data/lib/badline/input.rb
CHANGED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module KernalTrap
|
|
5
|
+
# Observe-only PC trap on the KERNAL CHROUT routine ($FFD2). Records each
|
|
6
|
+
# character written while the layout finds the KERNAL mapped in, then lets execution fall
|
|
7
|
+
# through to the ROM, so the screen output is unaffected. Used for headless
|
|
8
|
+
# capture of program output.
|
|
9
|
+
class Chrout
|
|
10
|
+
ADDRESS = 0xffd2
|
|
11
|
+
|
|
12
|
+
attr_reader :output
|
|
13
|
+
|
|
14
|
+
# The KERNAL layout whose mapping the trap checks.
|
|
15
|
+
attr_writer :layout
|
|
16
|
+
|
|
17
|
+
def initialize(cpu:, bus:, layout:)
|
|
18
|
+
@cpu = cpu
|
|
19
|
+
@bus = bus
|
|
20
|
+
@layout = layout
|
|
21
|
+
@output = +""
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def call
|
|
25
|
+
@output << ascii(@cpu.a) if @layout.kernal_mapped?(@bus)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def inspect
|
|
29
|
+
"#<#{self.class.name} output=#{@output.inspect}>"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def ascii(byte)
|
|
35
|
+
case byte
|
|
36
|
+
when 0x0d then "\n"
|
|
37
|
+
when 0x41..0x5a then (byte + 0x20).chr
|
|
38
|
+
when 0xc1..0xda then (byte - 0x80).chr
|
|
39
|
+
when 0x20..0x7e then byte.chr
|
|
40
|
+
else ""
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -81,33 +81,6 @@ module Badline
|
|
|
81
81
|
data ? new(data) : new([], error: [ILLEGAL_TRACK_OR_SECTOR, track, sector])
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
-
# Reads what save_state wrote after the flag that marks a file open
|
|
85
|
-
# for writing (Channels.channel_from_state). A buffer channel
|
|
86
|
-
# takes its block in `ram`, the drive's RAM, again.
|
|
87
|
-
def self.from_state(input, ram)
|
|
88
|
-
new.tap { |channel| channel.load_state(input, ram) }
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# A buffer channel's bytes are the drive's RAM, which the drive
|
|
92
|
-
# writes itself.
|
|
93
|
-
def save_state(out)
|
|
94
|
-
out.boolean(false).boolean(@writable).int(@base).int(@length).int(@pointer)
|
|
95
|
-
out.optional_int(@lead).optional_int(@buffer).boolean(!@error.nil?)
|
|
96
|
-
out.ints(@error) if @error
|
|
97
|
-
out.blob(@bytes) unless @writable
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def load_state(input, ram)
|
|
101
|
-
@writable = input.boolean?
|
|
102
|
-
@base = input.int
|
|
103
|
-
@length = input.int
|
|
104
|
-
@pointer = input.int
|
|
105
|
-
@lead = input.optional_int
|
|
106
|
-
@buffer = input.optional_int
|
|
107
|
-
@error = input.boolean? ? input.ints : nil
|
|
108
|
-
@bytes = @writable ? ram : input.blob
|
|
109
|
-
end
|
|
110
|
-
|
|
111
84
|
def replace(bytes, length = bytes.length)
|
|
112
85
|
@bytes = bytes
|
|
113
86
|
rewind(length)
|
|
@@ -22,31 +22,6 @@ module Badline
|
|
|
22
22
|
|
|
23
23
|
def clear = @channels.clear
|
|
24
24
|
|
|
25
|
-
# Each open channel with its secondary address, in the order they
|
|
26
|
-
# opened.
|
|
27
|
-
def save_state(out)
|
|
28
|
-
out.int(@channels.length)
|
|
29
|
-
@channels.each do |secondary, channel|
|
|
30
|
-
out.int(secondary)
|
|
31
|
-
channel.save_state(out)
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Buffer channels take their block in the drive's RAM again.
|
|
36
|
-
def load_state(input, memory)
|
|
37
|
-
@channels.clear
|
|
38
|
-
input.int.times do
|
|
39
|
-
secondary = input.int
|
|
40
|
-
@channels[secondary] = Channels.channel_from_state(input, memory.ram)
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# A channel save_state wrote: a file open for writing, or a Channel
|
|
45
|
-
# whose buffer, if it has one, is in `ram`.
|
|
46
|
-
def self.channel_from_state(input, ram)
|
|
47
|
-
input.boolean? ? WriteFile.from_state(input) : Channel.from_state(input, ram)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
25
|
# Opens a buffer channel on the buffer a "#n" name asks for, or on
|
|
51
26
|
# the highest free one. Returns nil when that buffer isn't free,
|
|
52
27
|
# which leaves a channel asking again for its own buffer open.
|
|
@@ -62,15 +62,6 @@ module Badline
|
|
|
62
62
|
load_buffer(Channels::BAM_BUFFER, block) if block
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
def save_state(out)
|
|
66
|
-
out.blob(@ram).boolean(@bam)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def load_state(input)
|
|
70
|
-
input.blob_into(@ram)
|
|
71
|
-
@bam = input.boolean?
|
|
72
|
-
end
|
|
73
|
-
|
|
74
65
|
# Whether the BAM has been read since the last reset.
|
|
75
66
|
def bam? = @bam
|
|
76
67
|
|
|
@@ -14,14 +14,6 @@ module Badline
|
|
|
14
14
|
TYPES = %i[del seq prg usr rel].freeze
|
|
15
15
|
MODES = %i[write replace append].freeze
|
|
16
16
|
|
|
17
|
-
# Reads what save_state wrote after the flag that marks a file
|
|
18
|
-
# open for writing.
|
|
19
|
-
def self.from_state(input)
|
|
20
|
-
name = input.string
|
|
21
|
-
type = TYPES.fetch(input.int).to_sym
|
|
22
|
-
new(name, type, mode: MODES.fetch(input.int).to_sym).tap { |file| file.load_state(input) }
|
|
23
|
-
end
|
|
24
|
-
|
|
25
17
|
def initialize(name, type, mode: :write)
|
|
26
18
|
@name = name
|
|
27
19
|
@type = type
|
|
@@ -29,16 +21,6 @@ module Badline
|
|
|
29
21
|
@bytes = []
|
|
30
22
|
end
|
|
31
23
|
|
|
32
|
-
def save_state(out)
|
|
33
|
-
out.boolean(true).string(@name).int(TYPES.index(@type)).int(MODES.index(@mode))
|
|
34
|
-
out.blob(@bytes).optional_int(@buffer)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def load_state(input)
|
|
38
|
-
@bytes = input.blob
|
|
39
|
-
@buffer = input.optional_int
|
|
40
|
-
end
|
|
41
|
-
|
|
42
24
|
def write(bytes) = @bytes.concat(bytes)
|
|
43
25
|
|
|
44
26
|
def writable? = true
|
|
@@ -147,25 +147,6 @@ module Badline
|
|
|
147
147
|
# vector, as U: does.
|
|
148
148
|
def reset! = reset(cold: true)
|
|
149
149
|
|
|
150
|
-
# The mounted storage's setup (Snapshot::StorageSetup), then the drive's
|
|
151
|
-
# RAM, its status and its open channels.
|
|
152
|
-
def save_state(out)
|
|
153
|
-
Snapshot::StorageSetup.write(@storage, out)
|
|
154
|
-
out.marker("TRAP DRIVE")
|
|
155
|
-
@memory.save_state(out)
|
|
156
|
-
@status.save_state(out)
|
|
157
|
-
@channels.save_state(out)
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
# Takes the state after the storage's setup, into a drive mounted on
|
|
161
|
-
# the reopened storage.
|
|
162
|
-
def load_state(input)
|
|
163
|
-
input.marker("TRAP DRIVE")
|
|
164
|
-
@memory.load_state(input)
|
|
165
|
-
@status.load_state(input)
|
|
166
|
-
@channels.load_state(input, @memory)
|
|
167
|
-
end
|
|
168
|
-
|
|
169
150
|
# Returns the next byte and whether it is the channel's last, or nil
|
|
170
151
|
# when there is nothing left to send.
|
|
171
152
|
def read(secondary)
|
|
@@ -6,17 +6,17 @@ module Badline
|
|
|
6
6
|
private
|
|
7
7
|
|
|
8
8
|
def active?
|
|
9
|
-
kernal_mapped? && @bus.peek(
|
|
9
|
+
kernal_mapped? && @bus.peek(zero_page.device) == DEVICE
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def filename
|
|
13
13
|
Storage.strip_drive_prefix(full_filename)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
#
|
|
16
|
+
# The file name FNADR points to, FNLEN bytes long.
|
|
17
17
|
def full_filename
|
|
18
|
-
pointer =
|
|
19
|
-
bytes = Array.new(@bus.peek(
|
|
18
|
+
pointer = peek_word(zero_page.filename_address)
|
|
19
|
+
bytes = Array.new(@bus.peek(zero_page.filename_length)) do |i|
|
|
20
20
|
@layout.filename_byte(@bus, (pointer + i) & 0xffff)
|
|
21
21
|
end
|
|
22
22
|
Storage.ascii(bytes)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module KernalTrap
|
|
5
|
+
class DOS
|
|
6
|
+
class Channel
|
|
7
|
+
# A channel's fields under `prefix`: whether it is a buffer in the
|
|
8
|
+
# drive's RAM, where it starts and ends, the pointer, the byte it
|
|
9
|
+
# leads with and its buffer's number, the error it reports once its
|
|
10
|
+
# bytes run out, and a file's bytes.
|
|
11
|
+
def save_fields(out, prefix)
|
|
12
|
+
out.text("#{prefix}kind", "channel").bool("#{prefix}writable", @writable).int("#{prefix}base", @base)
|
|
13
|
+
out.int("#{prefix}length", @length).int("#{prefix}pointer", @pointer).optional_int("#{prefix}lead", @lead)
|
|
14
|
+
out.optional_int("#{prefix}buffer", @buffer)
|
|
15
|
+
error = @error
|
|
16
|
+
out.ints("#{prefix}error", error) if error
|
|
17
|
+
out.bytes("#{prefix}bytes", @bytes) unless @writable
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# A buffer channel's bytes are the drive's `ram`.
|
|
21
|
+
def load_fields(input, prefix, ram)
|
|
22
|
+
@writable = input.bool("#{prefix}writable", false)
|
|
23
|
+
@base = input.int("#{prefix}base", 0)
|
|
24
|
+
@length = input.int("#{prefix}length", 0)
|
|
25
|
+
@pointer = input.int("#{prefix}pointer", 0)
|
|
26
|
+
@lead = input.optional_int("#{prefix}lead")
|
|
27
|
+
@buffer = input.optional_int("#{prefix}buffer")
|
|
28
|
+
error = input.ints("#{prefix}error", [])
|
|
29
|
+
@error = error.empty? ? nil : error.dup
|
|
30
|
+
@bytes = @writable ? ram : input.bytes("#{prefix}bytes", "".b).bytes
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class WriteFile
|
|
36
|
+
# A file open for writing, under `prefix`: its name, type and mode,
|
|
37
|
+
# the bytes written so far and the byte held back.
|
|
38
|
+
def save_fields(out, prefix)
|
|
39
|
+
out.text("#{prefix}kind", "write_file").bytes("#{prefix}name", @name.b).text("#{prefix}type", @type.to_s)
|
|
40
|
+
out.text("#{prefix}mode", @mode.to_s).bytes("#{prefix}bytes", @bytes).optional_int("#{prefix}held", @buffer)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.from_fields(input, prefix)
|
|
44
|
+
type = input.choice("#{prefix}type", TYPES.map(&:to_s), "prg")
|
|
45
|
+
mode = input.choice("#{prefix}mode", MODES.map(&:to_s), "write")
|
|
46
|
+
new(input.bytes("#{prefix}name", "".b), TYPES.find { |each| each.to_s == type } || :prg,
|
|
47
|
+
mode: MODES.find { |each| each.to_s == mode } || :write).tap do |file|
|
|
48
|
+
file.load_fields(input, prefix)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def load_fields(input, prefix)
|
|
53
|
+
@bytes = input.bytes("#{prefix}bytes", "".b).bytes
|
|
54
|
+
@buffer = input.optional_int("#{prefix}held")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class Channels
|
|
59
|
+
# The channels open, by secondary address, each under channel<SA>.
|
|
60
|
+
def save_fields(out)
|
|
61
|
+
out.ints("channels", @channels.keys)
|
|
62
|
+
@channels.each { |secondary, channel| channel.save_fields(out, "channel#{secondary}.") }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def load_fields(input, ram)
|
|
66
|
+
@channels.clear
|
|
67
|
+
input.ints("channels", []).each do |secondary|
|
|
68
|
+
@channels[secondary] = Channels.channel_from_fields(input, "channel#{secondary}.", ram)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def self.channel_from_fields(input, prefix, ram)
|
|
73
|
+
return WriteFile.from_fields(input, prefix) if input.text("#{prefix}kind", "channel") == "write_file"
|
|
74
|
+
|
|
75
|
+
Channel.new.load_fields(input, prefix, ram)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
class Status
|
|
80
|
+
# The error channel, under status.
|
|
81
|
+
def save_fields(out) = @channel.save_fields(out, "status.")
|
|
82
|
+
|
|
83
|
+
def load_fields(input)
|
|
84
|
+
input.choice("status.kind", ["channel"], "channel")
|
|
85
|
+
@channel = Channel.new.load_fields(input, "status.", [])
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
class Memory
|
|
90
|
+
# The drive's RAM, and whether the BAM has been read.
|
|
91
|
+
def save_fields(out)
|
|
92
|
+
out.bytes("ram", @ram).bool("bam", @bam)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def load_fields(input)
|
|
96
|
+
input.bytes_into("ram", @ram)
|
|
97
|
+
@bam = input.bool("bam", @bam)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# The storage device 8 serves.
|
|
102
|
+
attr_reader :storage
|
|
103
|
+
|
|
104
|
+
# The DOS's fields in a keyed snapshot module (BL-TRAPDOS), after the
|
|
105
|
+
# storage's under storage.*: its RAM, the error channel and the
|
|
106
|
+
# channels open.
|
|
107
|
+
def save_fields(out)
|
|
108
|
+
@memory.save_fields(out)
|
|
109
|
+
@status.save_fields(out)
|
|
110
|
+
@channels.save_fields(out)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def load_fields(input)
|
|
114
|
+
@memory.load_fields(input)
|
|
115
|
+
@status.load_fields(input)
|
|
116
|
+
@channels.load_fields(input, @memory.ram)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
class Serial
|
|
121
|
+
# The serial trap's fields under `prefix`: whether device 8 listens
|
|
122
|
+
# or talks, on which channel, the frame it is in and the bytes it has
|
|
123
|
+
# gathered.
|
|
124
|
+
def save_fields(out, prefix)
|
|
125
|
+
out.bool("#{prefix}listening", @listening).bool("#{prefix}talking", @talking)
|
|
126
|
+
out.optional_int("#{prefix}listen_channel", @listen_channel)
|
|
127
|
+
out.optional_int("#{prefix}talk_channel", @talk_channel).int("#{prefix}frame", @frame)
|
|
128
|
+
out.bytes("#{prefix}buffer", @buffer)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def load_fields(input, prefix)
|
|
132
|
+
@listening = input.bool("#{prefix}listening", @listening)
|
|
133
|
+
@talking = input.bool("#{prefix}talking", @talking)
|
|
134
|
+
@listen_channel = input.optional_int("#{prefix}listen_channel")
|
|
135
|
+
@talk_channel = input.optional_int("#{prefix}talk_channel")
|
|
136
|
+
@frame = input.int("#{prefix}frame", @frame)
|
|
137
|
+
@buffer = input.bytes("#{prefix}buffer", "".b).bytes
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
class Save
|
|
142
|
+
# Whether a SAVE is under way, under `prefix`.
|
|
143
|
+
def save_fields(out, prefix) = out.bool("#{prefix}saving", @saving)
|
|
144
|
+
|
|
145
|
+
def load_fields(input, prefix)
|
|
146
|
+
@saving = input.bool("#{prefix}saving", @saving)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|