badline 0.16.0 → 0.17.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 +76 -0
- data/CLAUDE.md +1 -1
- data/README.md +130 -17
- data/doc/media.md +14 -2
- data/doc/pinned-behaviour.md +61 -0
- data/doc/snapshot-format.md +379 -70
- data/doc/snapshots.md +185 -10
- data/lib/badline/cbm2/acia.rb +10 -0
- data/lib/badline/cbm2/bus.rb +263 -0
- data/lib/badline/cbm2/cpu.rb +58 -0
- data/lib/badline/cbm2/crtc_video.rb +133 -0
- data/lib/badline/cbm2/ieee_drive.rb +18 -0
- data/lib/badline/cbm2/ieee_layout.rb +69 -0
- data/lib/badline/cbm2/ieee_ports.rb +104 -0
- data/lib/badline/cbm2/ieee_traps.rb +81 -0
- data/lib/badline/cbm2/keyboard_ports.rb +60 -0
- data/lib/badline/cbm2/keyed_modules.rb +67 -0
- data/lib/badline/cbm2/keyed_state.rb +91 -0
- data/lib/badline/cbm2/model.rb +44 -0
- data/lib/badline/cbm2/p500/control_ports.rb +80 -0
- data/lib/badline/cbm2/p500/ieee_layout.rb +41 -0
- data/lib/badline/cbm2/p500/keyed_state.rb +85 -0
- data/lib/badline/cbm2/p500/model.rb +39 -0
- data/lib/badline/cbm2/p500/pages.rb +57 -0
- data/lib/badline/cbm2/p500.rb +262 -0
- data/lib/badline/cbm2/sid_page.rb +19 -0
- data/lib/badline/cbm2.rb +267 -0
- data/lib/badline/{plus4 → chips}/acia/frames.rb +1 -1
- data/lib/badline/chips/acia/keyed_state.rb +47 -0
- data/lib/badline/chips/acia.rb +307 -0
- data/lib/badline/chips/crtc.rb +3 -2
- data/lib/badline/chips/riot/fast_forward.rb +72 -0
- data/lib/badline/chips/riot/keyed_parts.rb +69 -0
- data/lib/badline/chips/riot/timer.rb +80 -0
- data/lib/badline/chips/riot.rb +162 -0
- data/lib/badline/chips/rriot/keyed_state.rb +30 -0
- data/lib/badline/chips/rriot.rb +132 -0
- data/lib/badline/chips/tpi/keyed_state.rb +38 -0
- data/lib/badline/chips/tpi.rb +233 -0
- data/lib/badline/chips/vic/bank.rb +60 -5
- data/lib/badline/chips/vic/sprite.rb +4 -3
- data/lib/badline/drives/datasette/keyed_state.rb +9 -1
- data/lib/badline/drives/datasette.rb +27 -3
- data/lib/badline/drives/ieee_bus.rb +50 -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/model8050/disk.rb +163 -0
- data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
- data/lib/badline/drives/model8050/idle.rb +177 -0
- data/lib/badline/drives/model8050/ieee_port.rb +147 -0
- data/lib/badline/drives/model8050/ip_bus.rb +93 -0
- data/lib/badline/drives/model8050/keyed_state.rb +160 -0
- data/lib/badline/drives/model8050/mechanism.rb +230 -0
- data/lib/badline/drives/model8050/parts.rb +37 -0
- data/lib/badline/drives/model8050.rb +243 -0
- data/lib/badline/frontend/app.rb +10 -3
- data/lib/badline/frontend/boot.rb +1 -1
- data/lib/badline/frontend/controls.rb +51 -15
- data/lib/badline/frontend/gamepads.rb +3 -3
- data/lib/badline/frontend/keys.rb +61 -2
- data/lib/badline/frontend/media_slots.rb +1 -1
- data/lib/badline/frontend/menu/menu_media.rb +14 -11
- data/lib/badline/frontend/menu/menu_pages.rb +29 -6
- data/lib/badline/frontend/menu/menu_ports.rb +40 -4
- data/lib/badline/frontend/menu/pause_menu.rb +1 -1
- data/lib/badline/frontend/pet_keyboard.rb +23 -7
- data/lib/badline/frontend/timeline.rb +19 -10
- data/lib/badline/kernal_trap/dos/disk_commands.rb +80 -0
- data/lib/badline/kernal_trap/dos/status.rb +3 -0
- data/lib/badline/kernal_trap/dos.rb +18 -5
- data/lib/badline/kernal_trap/ieee.rb +7 -2
- data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
- data/lib/badline/machine/ieee_drives.rb +194 -0
- data/lib/badline/machine/settling.rb +4 -0
- data/lib/badline/machine.rb +26 -8
- data/lib/badline/media/cbm2_media.rb +29 -0
- data/lib/badline/media/pet_media.rb +5 -5
- data/lib/badline/media/program.rb +6 -3
- data/lib/badline/media/true_drive.rb +50 -8
- data/lib/badline/media.rb +21 -8
- data/lib/badline/options/family.rb +15 -3
- data/lib/badline/options/help.rb +24 -6
- data/lib/badline/options/table.rb +25 -5
- data/lib/badline/options/validation.rb +47 -7
- data/lib/badline/options.rb +48 -15
- data/lib/badline/pet/bus/expansion.rb +99 -5
- data/lib/badline/pet/bus.rb +30 -12
- data/lib/badline/pet/cassettes.rb +22 -1
- data/lib/badline/pet/characters.rb +8 -2
- data/lib/badline/pet/crtc_video.rb +20 -7
- data/lib/badline/pet/ieee_drive.rb +17 -0
- data/lib/badline/pet/ieee_ports.rb +19 -1
- data/lib/badline/pet/ieee_traps.rb +10 -5
- data/lib/badline/pet/keyed_modules.rb +4 -1
- data/lib/badline/pet/keyed_parts.rb +10 -6
- data/lib/badline/pet/model.rb +56 -6
- data/lib/badline/pet/rom_sets.rb +30 -6
- data/lib/badline/pet.rb +45 -16
- data/lib/badline/plus4/acia.rb +4 -298
- data/lib/badline/plus4/keyed_parts.rb +0 -44
- data/lib/badline/plus4.rb +1 -0
- data/lib/badline/roms/8050/dos-2.7.rom +0 -0
- data/lib/badline/roms/8050/fdc-901869-01.rom +0 -0
- data/lib/badline/roms/8050/fdc-901885-04.rom +0 -0
- data/lib/badline/roms/README +144 -1
- data/lib/badline/roms/cbm2/basic128-02b.rom +0 -0
- data/lib/badline/roms/cbm2/basic128.rom +0 -0
- data/lib/badline/roms/cbm2/basic256.rom +0 -0
- data/lib/badline/roms/cbm2/characters-600.rom +0 -0
- data/lib/badline/roms/cbm2/characters-700.rom +0 -0
- data/lib/badline/roms/cbm2/kernal-03b.rom +0 -0
- data/lib/badline/roms/cbm2/kernal-04a.rom +0 -0
- data/lib/badline/roms/cbm2/p500-basic.rom +0 -0
- data/lib/badline/roms/cbm2/p500-kernal.rom +0 -0
- data/lib/badline/roms/dos2031.rom +0 -0
- data/lib/badline/roms/pet/characters-din.rom +0 -0
- data/lib/badline/roms/pet/editor4-80-b-50hz-8296.rom +0 -0
- data/lib/badline/snapshot/cbm2_setup.rb +53 -0
- data/lib/badline/snapshot/container.rb +9 -1
- data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
- data/lib/badline/snapshot/field_doc.rb +39 -3
- data/lib/badline/snapshot/image.rb +79 -15
- data/lib/badline/snapshot/pet_setup.rb +30 -13
- data/lib/badline/snapshot/vice/cbm2.rb +93 -0
- data/lib/badline/snapshot/vice/cbm2_acia.rb +75 -0
- data/lib/badline/snapshot/vice/cbm2_crtc.rb +127 -0
- data/lib/badline/snapshot/vice/cbm2_mains.rb +49 -0
- data/lib/badline/snapshot/vice/cbm2_mem.rb +75 -0
- data/lib/badline/snapshot/vice/cbm2_restore.rb +204 -0
- data/lib/badline/snapshot/vice/cbm2_tpi.rb +58 -0
- data/lib/badline/snapshot/vice/cias.rb +13 -9
- data/lib/badline/snapshot/vice/p500.rb +96 -0
- data/lib/badline/snapshot/vice/p500_restore.rb +130 -0
- data/lib/badline/snapshot/vice/p500_vicii.rb +166 -0
- data/lib/badline/snapshot/vice/pet.rb +121 -0
- data/lib/badline/snapshot/vice/pet_cpu.rb +70 -0
- data/lib/badline/snapshot/vice/pet_crtc.rb +241 -0
- data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
- data/lib/badline/snapshot/vice/pet_drive8050.rb +216 -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 +214 -0
- data/lib/badline/snapshot/vice/pet_via.rb +165 -0
- data/lib/badline/snapshot/vice/sid_registers.rb +2 -1
- data/lib/badline/snapshot/vice/vicii_import.rb +10 -6
- data/lib/badline/snapshot/vice.rb +4 -0
- data/lib/badline/snapshot.rb +11 -3
- data/lib/badline/storage/crt_file.rb +7 -1
- data/lib/badline/storage/d64_image.rb +7 -0
- data/lib/badline/storage/d80_image.rb +17 -1
- data/lib/badline/storage/d81_image.rb +14 -0
- data/lib/badline/storage/disk_image/bam.rb +18 -3
- data/lib/badline/storage/disk_image/formatting.rb +69 -0
- data/lib/badline/storage/disk_image/writing.rb +35 -0
- data/lib/badline/storage/disk_image.rb +2 -0
- data/lib/badline/storage.rb +1 -0
- data/lib/badline/support/cbm2_checkpoint.rb +48 -0
- data/lib/badline/support/p500_checkpoint.rb +35 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline.rb +1 -0
- metadata +87 -2
|
@@ -6,8 +6,9 @@ module Badline
|
|
|
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
8
|
# joystick's, and the Plus/4 two joysticks and no pot devices. The PET
|
|
9
|
-
#
|
|
10
|
-
# instead.
|
|
9
|
+
# and the CBM-II's B series have no ports, and the page, KEYBOARD
|
|
10
|
+
# there, says where their keys are instead. The P500 has two joysticks,
|
|
11
|
+
# and the page says where its keys are while they type.
|
|
11
12
|
module MenuPorts
|
|
12
13
|
PORT_DEVICES = %i[joystick mouse paddles].freeze
|
|
13
14
|
PORT_NAMES = %w[JOY MOUSE PADDLES].freeze
|
|
@@ -21,15 +22,21 @@ module Badline
|
|
|
21
22
|
PET_GRAPHICS_KEYS = ["ESC: RUN/STOP", "SHIFT+LETTER: GRAPHIC", "ALT+SYMBOL: GRAPHIC"].freeze
|
|
22
23
|
PET_BUSINESS_KEYS = ["ESC: ESC", "TAB: TAB", "PAGE UP: REPEAT", "KEYPAD: KEYPAD", "ALT+KEY: SHIFT+KEY"].freeze
|
|
23
24
|
|
|
25
|
+
# Where the CBM-II's keys without a character of their own are.
|
|
26
|
+
CBM2_KEYS = ["ESC: ESC", "F12: RUN/STOP", "HOME: CLR/HOME", "BACKSPACE: INST/DEL", "F1-F8: F1-F8",
|
|
27
|
+
"ALT+F9, ALT+F10: F9, F10", "KEYPAD: KEYPAD", "ALT+0: KEYPAD 00", "ALT+C: C= ALT+E: CE",
|
|
28
|
+
"ALT+G: NORM/GRAPH", "ALT+R: OFF/RVS ALT+P: PI", "_: LEFT ARROW"].freeze
|
|
29
|
+
|
|
24
30
|
private
|
|
25
31
|
|
|
26
32
|
def draw_ports
|
|
27
33
|
return draw_vic20_port if @computer.family == :vic20
|
|
28
34
|
return draw_plus4_ports if @computer.family == :plus4
|
|
29
35
|
return draw_pet_keys if @computer.family == :pet
|
|
36
|
+
return draw_p500_ports if Controls.p500?(@computer)
|
|
37
|
+
return draw_cbm2_keys if @computer.family == :cbm2
|
|
30
38
|
|
|
31
|
-
|
|
32
|
-
toggle("PORT 2", :port2, [PORT_NAMES, PORT_ACTIONS[1], PORT_DEVICES.index(port_device(2))])
|
|
39
|
+
draw_pot_ports
|
|
33
40
|
return unless draw_keys(@computer.family == :c128 ? "C128" : "C64")
|
|
34
41
|
|
|
35
42
|
row("SWAP JOYSTICKS", :swap)
|
|
@@ -70,6 +77,35 @@ module Badline
|
|
|
70
77
|
end
|
|
71
78
|
end
|
|
72
79
|
|
|
80
|
+
# What each control port has plugged in, a joystick, a mouse or
|
|
81
|
+
# paddles.
|
|
82
|
+
def draw_pot_ports
|
|
83
|
+
toggle("PORT 1", :port1, [PORT_NAMES, PORT_ACTIONS[0], PORT_DEVICES.index(port_device(1))])
|
|
84
|
+
toggle("PORT 2", :port2, [PORT_NAMES, PORT_ACTIONS[1], PORT_DEVICES.index(port_device(2))])
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The P500's two ports, its joysticks one for each set of keys, and
|
|
88
|
+
# while the keys type, where its keys are.
|
|
89
|
+
def draw_p500_ports
|
|
90
|
+
draw_pot_ports
|
|
91
|
+
return draw_cbm2_keys unless draw_keys("P500")
|
|
92
|
+
|
|
93
|
+
row("SWAP JOYSTICKS", :swap)
|
|
94
|
+
skip
|
|
95
|
+
arrows = @controls.arrows_port
|
|
96
|
+
info("ARROWS, SPACE", "JOYSTICK #{arrows}")
|
|
97
|
+
info("WASD, LEFT SHIFT", "JOYSTICK #{3 - arrows}")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# The CBM-II's keyboard, which types as the PET's does, and the keys
|
|
101
|
+
# that stand for the rest of its keys.
|
|
102
|
+
def draw_cbm2_keys
|
|
103
|
+
CBM2_KEYS.each do |line|
|
|
104
|
+
@painter.text(@left, @top, line, MenuTheme::TEXT)
|
|
105
|
+
@top += MenuPages::LINE
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
73
109
|
# The toggle between the keyboard named and the joysticks, and
|
|
74
110
|
# whether the keys drive the joysticks.
|
|
75
111
|
def draw_keys(keyboard)
|
|
@@ -238,7 +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
|
+
name = "KEYBOARD" if PAGES[index] == :ports && (@pages.pet? || @pages.b_series?)
|
|
242
242
|
@buttons.plain(@left + 6, top, name.ljust(14), PAGES[index], on: index == @section)
|
|
243
243
|
top += Buttons::HEIGHT + 2
|
|
244
244
|
end
|
|
@@ -12,16 +12,22 @@ module Badline
|
|
|
12
12
|
# With either Alt down, a key that types a character presses the PET
|
|
13
13
|
# key its unshifted character is on with the PET's SHIFT, so the
|
|
14
14
|
# graphics on the graphics keyboard's symbol keys can be typed.
|
|
15
|
+
#
|
|
16
|
+
# The CBM-II's keyboard goes the same way (Keys.cbm2_key), and there
|
|
17
|
+
# Alt with a key presses one of the keys the host has none for
|
|
18
|
+
# (Keys.cbm2_alt_key).
|
|
15
19
|
class PETKeyboard
|
|
16
20
|
LSHIFT = 225
|
|
17
21
|
RSHIFT = 229
|
|
18
22
|
LALT = 226
|
|
19
23
|
RALT = 230
|
|
20
24
|
|
|
21
|
-
# +layout+ is the PET's keyboard, :graphics or :business
|
|
25
|
+
# +layout+ is the PET's keyboard, :graphics or :business, or :cbm2
|
|
26
|
+
# for the CBM-II's.
|
|
22
27
|
def initialize(keyboard, layout)
|
|
23
28
|
@keyboard = keyboard
|
|
24
29
|
@layout = layout
|
|
30
|
+
@shift = layout == :cbm2 ? :shift : :lshift
|
|
25
31
|
@held = {}
|
|
26
32
|
@left_shift = false
|
|
27
33
|
@right_shift = false
|
|
@@ -43,6 +49,11 @@ module Badline
|
|
|
43
49
|
end
|
|
44
50
|
end
|
|
45
51
|
|
|
52
|
+
# Whether a key the scancode pressed is still down.
|
|
53
|
+
def holds?(scancode) = @held.key?(scancode)
|
|
54
|
+
|
|
55
|
+
def alt? = @left_alt || @right_alt
|
|
56
|
+
|
|
46
57
|
# Lets go of every key, SHIFT among them.
|
|
47
58
|
def release_all
|
|
48
59
|
@held.each_value { |key| @keyboard.release(key) }
|
|
@@ -59,7 +70,7 @@ module Badline
|
|
|
59
70
|
|
|
60
71
|
def key_down(scancode)
|
|
61
72
|
key_up(scancode) if @held.key?(scancode)
|
|
62
|
-
key, shift = alt? ? shifted_key(scancode) :
|
|
73
|
+
key, shift = alt? ? shifted_key(scancode) : layout_key(scancode, shifted?)
|
|
63
74
|
return if key == :none
|
|
64
75
|
|
|
65
76
|
unless shift == :host
|
|
@@ -99,10 +110,17 @@ module Badline
|
|
|
99
110
|
end
|
|
100
111
|
end
|
|
101
112
|
|
|
113
|
+
def layout_key(scancode, shift)
|
|
114
|
+
@layout == :cbm2 ? Keys.cbm2_key(scancode, shift) : Keys.pet_key(scancode, shift, @layout)
|
|
115
|
+
end
|
|
116
|
+
|
|
102
117
|
# The PET key for the scancode with Alt down: for a letter or a key
|
|
103
118
|
# 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.
|
|
119
|
+
# SHIFT, and for any other key the one it presses without Alt. The
|
|
120
|
+
# CBM-II's is the one Keys.cbm2_alt_key names.
|
|
105
121
|
def shifted_key(scancode)
|
|
122
|
+
return Keys.cbm2_alt_key(scancode) if @layout == :cbm2
|
|
123
|
+
|
|
106
124
|
key, shift = Keys.pet_key(scancode, false, @layout)
|
|
107
125
|
return [key, shift] if key == :none
|
|
108
126
|
return [key, shift] unless scancode.between?(4, 29) || Keys::US_CHARACTERS.key?(scancode)
|
|
@@ -112,12 +130,10 @@ module Badline
|
|
|
112
130
|
|
|
113
131
|
def shifted? = @left_shift || @right_shift
|
|
114
132
|
|
|
115
|
-
def alt? = @left_alt || @right_alt
|
|
116
|
-
|
|
117
133
|
def sync_shift
|
|
118
134
|
shift = @forced == -1 ? shifted? : @forced_shift
|
|
119
|
-
@keyboard.release(
|
|
120
|
-
@keyboard.press(
|
|
135
|
+
@keyboard.release(@shift)
|
|
136
|
+
@keyboard.press(@shift) if shift
|
|
121
137
|
end
|
|
122
138
|
end
|
|
123
139
|
end
|
|
@@ -25,7 +25,8 @@ module Badline
|
|
|
25
25
|
LOCKS = %w[caps_lock forty_eighty].freeze
|
|
26
26
|
|
|
27
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.
|
|
28
|
+
# key or menu page that names nothing, or an empty string. The
|
|
29
|
+
# CBM-II's P500 is family :p500 here, for its joysticks.
|
|
29
30
|
def self.error(events, family)
|
|
30
31
|
event = events.find { |candidate| candidate.action == "key" && !key?(candidate.argument, family) }
|
|
31
32
|
return "no such key: #{event.argument}" unless event.nil?
|
|
@@ -38,23 +39,26 @@ module Badline
|
|
|
38
39
|
# sound. An empty one is its first.
|
|
39
40
|
def self.page?(name) = name.empty? || PauseMenu::PAGES.any? { |page| page.to_s == name }
|
|
40
41
|
|
|
41
|
-
# Whether the family's machine has a key of the name
|
|
42
|
-
# no RESTORE,
|
|
42
|
+
# Whether the family's machine has a key of the name, the CBM-II's
|
|
43
|
+
# P500's as family :p500. The Plus/4 has no RESTORE, the P500 none
|
|
44
|
+
# either, and the PET and the CBM-II's B series neither that nor
|
|
45
|
+
# joysticks.
|
|
43
46
|
def self.key?(name, family)
|
|
44
|
-
pet = family
|
|
45
|
-
(name == "restore" && !pet && family
|
|
47
|
+
pet = %i[pet cbm2].include?(family)
|
|
48
|
+
(name == "restore" && !pet && !%i[plus4 p500].include?(family)) || (family == :c128 && LOCKS.include?(name)) ||
|
|
46
49
|
!keyboard_key(name, family).nil? || (!pet && !joystick_direction(name).nil?)
|
|
47
50
|
end
|
|
48
51
|
|
|
49
52
|
# The family's Keyboard key for a name such as space, return or a,
|
|
50
53
|
# 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
|
|
52
|
-
#
|
|
54
|
+
# the PET rvs or kp1, of either of its keyboards, or on the CBM-II
|
|
55
|
+
# escape, norm_graph or kp00. The shifted cursor keys, such as
|
|
56
|
+
# cursor_up, are the C64's and the C128's.
|
|
53
57
|
def self.keyboard_key(name, family)
|
|
54
58
|
return nil if name == "none"
|
|
55
59
|
|
|
56
60
|
key = keyboard_matrix(family).flatten.find { |candidate| candidate.to_s == name }
|
|
57
|
-
return key unless key.nil? && !%i[plus4 pet].include?(family)
|
|
61
|
+
return key unless key.nil? && !%i[plus4 pet cbm2 p500].include?(family)
|
|
58
62
|
|
|
59
63
|
Input::Keyboard::COMBINATIONS.keys.find { |candidate| candidate.to_s == name }
|
|
60
64
|
end
|
|
@@ -64,6 +68,7 @@ module Badline
|
|
|
64
68
|
when :c128 then C128::KEYBOARD_MATRIX
|
|
65
69
|
when :plus4 then Plus4::KeyboardPort::MATRIX
|
|
66
70
|
when :pet then PET::KeyboardPorts::GRAPHICS_MATRIX + PET::KeyboardPorts::BUSINESS_MATRIX
|
|
71
|
+
when :cbm2, :p500 then CBM2::KeyboardPorts::MATRIX
|
|
67
72
|
else Input::Keyboard::C64_MATRIX
|
|
68
73
|
end
|
|
69
74
|
end
|
|
@@ -228,9 +233,13 @@ module Badline
|
|
|
228
233
|
end
|
|
229
234
|
|
|
230
235
|
# Whether there's one in to eject. A VIC-20 has no cartridge to take
|
|
231
|
-
# out yet,
|
|
236
|
+
# out yet, a PET no expansion port, and a CBM-II no datasette, and
|
|
237
|
+
# its cartridge stays in.
|
|
232
238
|
def ejectable?(computer, what)
|
|
233
|
-
|
|
239
|
+
family = computer.family
|
|
240
|
+
return false if family == :cbm2 && what != "disk"
|
|
241
|
+
|
|
242
|
+
(what != "cartridge" || !%i[vic20 pet].include?(family)) && inserted?(computer, what)
|
|
234
243
|
end
|
|
235
244
|
|
|
236
245
|
def inserted?(computer, what)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module KernalTrap
|
|
5
|
+
class DOS
|
|
6
|
+
# N, R and C: the commands that format a disk, rename a file and copy
|
|
7
|
+
# files. Each takes its names after a colon. A name can't hold a
|
|
8
|
+
# wildcard, and R and C take their new name before an "=". The DOS
|
|
9
|
+
# here has one drive, so a file on drive 1 is never found, and C
|
|
10
|
+
# without a colon, which copies a whole disk between the drives of a
|
|
11
|
+
# dual drive, and D, which duplicates one, are invalid commands, as on
|
|
12
|
+
# a single drive.
|
|
13
|
+
module DiskCommands
|
|
14
|
+
WILDCARD = /[*?]/
|
|
15
|
+
OTHER_DRIVE = /\A[1-9]\d*:/
|
|
16
|
+
DRIVE_ZERO = /\A0+:/
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
# N with an ID after a comma formats the disk, and without one only
|
|
21
|
+
# clears it. The ID is the two bytes after the comma, which takes the
|
|
22
|
+
# carriage return PRINT# ends the command with when the ID is one
|
|
23
|
+
# character. A comma with nothing after it gives no ID.
|
|
24
|
+
def new_disk(text)
|
|
25
|
+
colon = text.index(":")
|
|
26
|
+
return report(NO_FILE_GIVEN) unless colon
|
|
27
|
+
|
|
28
|
+
command = text.chomp("\r")
|
|
29
|
+
name, id = command[(colon + 1)..].split(",", 2)
|
|
30
|
+
return report(SYNTAX_ERROR) if name.to_s.match?(/[*?=]/)
|
|
31
|
+
return report(WRITE_PROTECT_ON) unless @storage.respond_to?(:format)
|
|
32
|
+
|
|
33
|
+
id = id.to_s.empty? ? nil : text.bytes[(command.index(",") + 1), 2]
|
|
34
|
+
writing { @storage.format(name.to_s, id && (id + [Storage::DiskImage::NAME_PADDING]).first(2)) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# R takes the new name, then the file's.
|
|
38
|
+
def rename_file(text)
|
|
39
|
+
new_name, old_names = file_names(text, NO_FILE_GIVEN)
|
|
40
|
+
return unless new_name
|
|
41
|
+
return report(SYNTAX_ERROR) unless old_names.one?
|
|
42
|
+
return report(WRITE_PROTECT_ON) unless @storage.respond_to?(:rename)
|
|
43
|
+
return report(FILE_NOT_FOUND) if old_names.first.match?(OTHER_DRIVE)
|
|
44
|
+
|
|
45
|
+
writing { @storage.rename(Storage.strip_drive_prefix(old_names.first), new_name) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# C takes the new name, then the files it joins, separated by
|
|
49
|
+
# commas. A first file named with drive 0 that is the new file
|
|
50
|
+
# itself has the others added to its end.
|
|
51
|
+
def copy_file(text)
|
|
52
|
+
new_name, old_names = file_names(text, INVALID_COMMAND)
|
|
53
|
+
return unless new_name
|
|
54
|
+
return report(WRITE_PROTECT_ON) unless @storage.respond_to?(:copy)
|
|
55
|
+
return report(FILE_NOT_FOUND) if old_names.any? { |name| name.match?(OTHER_DRIVE) }
|
|
56
|
+
|
|
57
|
+
names = old_names.map { |name| Storage.strip_drive_prefix(name) }
|
|
58
|
+
in_place = old_names.first.match?(DRIVE_ZERO) && names.first == new_name
|
|
59
|
+
writing { in_place ? @storage.concat(new_name, names.drop(1)) : @storage.copy(new_name, names) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The new name and the old ones of "new=old,...", or nil once it
|
|
63
|
+
# has reported what's wrong.
|
|
64
|
+
def file_names(text, missing_colon)
|
|
65
|
+
colon = text.index(":")
|
|
66
|
+
return report(missing_colon) unless colon
|
|
67
|
+
|
|
68
|
+
new_name, rest = text[(colon + 1)..].split("=", 2)
|
|
69
|
+
new_name = new_name.to_s
|
|
70
|
+
return report(SYNTAX_ERROR) if rest.nil? || new_name.include?(",")
|
|
71
|
+
|
|
72
|
+
old_names = rest.empty? ? [""] : rest.split(",", -1)
|
|
73
|
+
return report(SYNTAX_ERROR) if [new_name, *old_names].any? { |name| name.match?(WILDCARD) }
|
|
74
|
+
|
|
75
|
+
[new_name, old_names]
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -15,8 +15,11 @@ module Badline
|
|
|
15
15
|
28 => "WRITE ERROR",
|
|
16
16
|
29 => "DISK ID MISMATCH",
|
|
17
17
|
30 => "SYNTAX ERROR",
|
|
18
|
+
31 => "SYNTAX ERROR",
|
|
19
|
+
34 => "SYNTAX ERROR",
|
|
18
20
|
62 => "FILE NOT FOUND",
|
|
19
21
|
63 => "FILE EXISTS",
|
|
22
|
+
64 => "FILE TYPE MISMATCH",
|
|
20
23
|
65 => "NO BLOCK",
|
|
21
24
|
66 => "ILLEGAL TRACK OR SECTOR",
|
|
22
25
|
70 => "NO CHANNEL",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "badline/kernal_trap/dos/block_commands"
|
|
4
4
|
require "badline/kernal_trap/dos/channel"
|
|
5
5
|
require "badline/kernal_trap/dos/channels"
|
|
6
|
+
require "badline/kernal_trap/dos/disk_commands"
|
|
6
7
|
require "badline/kernal_trap/dos/memory"
|
|
7
8
|
require "badline/kernal_trap/dos/parameters"
|
|
8
9
|
require "badline/kernal_trap/dos/status"
|
|
@@ -15,10 +16,12 @@ module Badline
|
|
|
15
16
|
# command channel and its status message. `U1` reads a raw block into a
|
|
16
17
|
# buffer channel, which is how block-access loaders bypass the KERNAL's
|
|
17
18
|
# LOAD. A disk image whose host file takes writes takes files, blocks
|
|
18
|
-
# and BAM changes too
|
|
19
|
-
#
|
|
19
|
+
# and BAM changes too, and N, R and C format it, rename and copy files.
|
|
20
|
+
# Other disks are write-protected, though storage that can write files
|
|
21
|
+
# still takes a SAVE handed over whole.
|
|
20
22
|
class DOS
|
|
21
23
|
include BlockCommands
|
|
24
|
+
include DiskCommands
|
|
22
25
|
include Writes
|
|
23
26
|
|
|
24
27
|
COMMAND_CHANNEL = 15
|
|
@@ -28,6 +31,8 @@ module Badline
|
|
|
28
31
|
FILES_SCRATCHED = 1
|
|
29
32
|
WRITE_PROTECT_ON = 26
|
|
30
33
|
SYNTAX_ERROR = 30
|
|
34
|
+
INVALID_COMMAND = 31
|
|
35
|
+
NO_FILE_GIVEN = 34
|
|
31
36
|
FILE_NOT_FOUND = 62
|
|
32
37
|
FILE_EXISTS = 63
|
|
33
38
|
NO_BLOCK = 65
|
|
@@ -60,11 +65,15 @@ module Badline
|
|
|
60
65
|
/\AV/i => :initialized,
|
|
61
66
|
/\AB-A\s*:?\s*(.*)/i => :block_allocate,
|
|
62
67
|
/\AB-F\s*:?\s*(.*)/i => :block_free,
|
|
63
|
-
/\AS[^:]*:(.*)/im => :scratch
|
|
68
|
+
/\AS[^:]*:(.*)/im => :scratch,
|
|
69
|
+
/\AN(.*)/im => :new_disk,
|
|
70
|
+
/\AR(.*)/im => :rename_file,
|
|
71
|
+
/\AC(.*)/im => :copy_file,
|
|
72
|
+
/\AD/i => :invalid
|
|
64
73
|
}.freeze
|
|
65
74
|
|
|
66
75
|
# Commands that take their argument as text rather than numbers.
|
|
67
|
-
TEXT_COMMANDS = %i[scratch].freeze
|
|
76
|
+
TEXT_COMMANDS = %i[scratch new_disk rename_file copy_file].freeze
|
|
68
77
|
|
|
69
78
|
# A drive powers on reporting its DOS version, as a reset does. It
|
|
70
79
|
# can take the RAM of a drive it replaces, and initializes its disk.
|
|
@@ -197,7 +206,7 @@ module Badline
|
|
|
197
206
|
return report(SYNTAX_ERROR) unless action
|
|
198
207
|
|
|
199
208
|
action = USER_TABLE[(command.getbyte(1) - 1) & 0x0f] if action == :user
|
|
200
|
-
argument = pattern.match(command)[1]
|
|
209
|
+
argument = pattern.match(action == :new_disk ? text.lstrip : command)[1]
|
|
201
210
|
run_command(action, TEXT_COMMANDS.include?(action) ? argument : Parameters.parse(argument))
|
|
202
211
|
end
|
|
203
212
|
|
|
@@ -218,6 +227,10 @@ module Badline
|
|
|
218
227
|
when :block_allocate then block_allocate(arguments)
|
|
219
228
|
when :block_free then block_free(arguments)
|
|
220
229
|
when :scratch then scratch(arguments)
|
|
230
|
+
when :new_disk then new_disk(arguments)
|
|
231
|
+
when :rename_file then rename_file(arguments)
|
|
232
|
+
when :copy_file then copy_file(arguments)
|
|
233
|
+
when :invalid then report(INVALID_COMMAND)
|
|
221
234
|
when :cold_reset then reset(cold: true)
|
|
222
235
|
when :warm_reset then reset(cold: false)
|
|
223
236
|
when :memory_write then memory_write(arguments)
|
|
@@ -23,6 +23,9 @@ module Badline
|
|
|
23
23
|
UNTALK_COMMAND = 0x5f
|
|
24
24
|
UNLISTEN_COMMAND = 0x3f
|
|
25
25
|
|
|
26
|
+
# The device the traps serve, or nil to serve none.
|
|
27
|
+
attr_accessor :device
|
|
28
|
+
|
|
26
29
|
def initialize(cpu:, bus:, layout:, dos:)
|
|
27
30
|
@cpu = cpu
|
|
28
31
|
@bus = bus
|
|
@@ -51,7 +54,9 @@ module Badline
|
|
|
51
54
|
address_device if @listening
|
|
52
55
|
end
|
|
53
56
|
|
|
54
|
-
|
|
57
|
+
# The device TALK or LISTEN addresses: the one in A or in FA, as the
|
|
58
|
+
# layout says.
|
|
59
|
+
def addressed? = (@layout.device_in_a ? @cpu.a : @bus.peek(zero_page.device)) == @device
|
|
55
60
|
|
|
56
61
|
# TALK and LISTEN end with the command sent under ATN, which stays
|
|
57
62
|
# low, and A holding $FF.
|
|
@@ -113,7 +118,7 @@ module Badline
|
|
|
113
118
|
def acptr
|
|
114
119
|
return unless @talking
|
|
115
120
|
|
|
116
|
-
@layout.
|
|
121
|
+
@layout.byte_received(@bus)
|
|
117
122
|
byte, status = received
|
|
118
123
|
time_serial_byte(Routine::ACPTR_TIMEOUT)
|
|
119
124
|
@cpu.a = byte
|
|
@@ -2,70 +2,122 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
module KernalTrap
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
# The +keep+ of an IEEELineStep that only reads its register.
|
|
6
|
+
IEEE_LINE_READ = -1
|
|
7
|
+
|
|
8
|
+
# One step a KERNAL takes on the IEEE-488 bus's lines: a write of +set+
|
|
9
|
+
# to the register at +address+, or with a +keep+ mask the register read
|
|
10
|
+
# first, its +keep+ bits kept and +set+ ORed in, or with keep
|
|
11
|
+
# IEEE_LINE_READ only a read. Returns the value written, or read.
|
|
12
|
+
IEEELineStep = Data.define(:address, :keep, :set) do
|
|
13
|
+
def act_on(bus)
|
|
14
|
+
return bus.peek(address) if keep == IEEE_LINE_READ
|
|
15
|
+
|
|
16
|
+
value = keep.zero? ? set : (bus.peek(address) & keep) | set
|
|
17
|
+
bus.poke(address, value)
|
|
18
|
+
value
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# The steps a KERNAL takes on the bus's lines at each moment the traps
|
|
23
|
+
# stand in for, each run in order (#run_steps), the last one's value
|
|
24
|
+
# returned: +timer+ starts the timer that times out each byte's
|
|
25
|
+
# handshake, +attention+ readies the bus and pulls ATN low for TALK
|
|
26
|
+
# and LISTEN, +byte_sent+ ends a byte sent, +release_attention+ ends
|
|
27
|
+
# SECOND, TKSA, UNTALK and UNLISTEN by letting ATN go, +listen_to_talker+
|
|
28
|
+
# ends TKSA with the computer listening, +byte_received+ ends ACPTR,
|
|
29
|
+
# and +release+ ends UNTALK and UNLISTEN.
|
|
30
|
+
IEEELines = Data.define(:timer, :attention, :byte_sent, :release_attention, :listen_to_talker, :byte_received,
|
|
31
|
+
:release) do
|
|
32
|
+
def run_steps(steps, bus)
|
|
33
|
+
value = 0
|
|
34
|
+
steps.each { |step| value = step.act_on(bus) }
|
|
35
|
+
value
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Where a KERNAL keeps the eight IEEE-488 bus primitives, which are
|
|
40
|
+
# internal routines, not jump table entries, the ZeroPage of its file
|
|
41
|
+
# variables, and the steps it takes on the bus's lines (IEEELines).
|
|
42
|
+
# Each ROM set has its own.
|
|
16
43
|
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
44
|
+
# On a PET, TALK and LISTEN take no device number in A: they send $40
|
|
45
|
+
# or $20 ORed with FA. UNTALK and UNLISTEN send $5F and $3F through the
|
|
46
|
+
# same code, and SECOND, TKSA, UNTALK and UNLISTEN end by letting go of
|
|
47
|
+
# ATN themselves. LOAD and SAVE run in the ROM, through the primitives.
|
|
48
|
+
# +overwrites_device+ says UNTALK and UNLISTEN store their command in
|
|
49
|
+
# FA before they send it. +device_in_a+ says TALK and LISTEN take the
|
|
50
|
+
# device in A, as the CBM-II's do, where a PET's ignore A and read FA.
|
|
23
51
|
IEEELayout = Data.define(:talk, :listen, :second, :tksa, :ciout, :untalk, :unlisten, :acptr, :overwrites_device,
|
|
24
|
-
:zero_page) do
|
|
25
|
-
# The KERNAL is mapped unless
|
|
52
|
+
:device_in_a, :zero_page, :lines) do
|
|
53
|
+
# The KERNAL is mapped unless something lies over it.
|
|
26
54
|
def kernal_mapped?(bus) = bus.kernal_mapped?
|
|
27
55
|
|
|
28
|
-
# Starts
|
|
56
|
+
# Starts the timer that times out the handshake of each byte.
|
|
29
57
|
def time_serial_byte(bus, _timer_high)
|
|
30
|
-
|
|
58
|
+
lines.run_steps(lines.timer, bus)
|
|
31
59
|
end
|
|
32
60
|
|
|
33
|
-
# TALK and LISTEN:
|
|
61
|
+
# TALK and LISTEN: the handshake lines let go, then ATN pulled low.
|
|
34
62
|
def attention(bus)
|
|
35
|
-
|
|
36
|
-
bus.poke(0xe821, 0x3c)
|
|
37
|
-
bus.poke(0xe840, bus.peek(0xe840) & 0xfb)
|
|
63
|
+
lines.run_steps(lines.attention, bus)
|
|
38
64
|
end
|
|
39
65
|
|
|
40
66
|
# The end of a byte sent: DAV and the data lines let go.
|
|
41
67
|
def byte_sent(bus)
|
|
42
|
-
|
|
43
|
-
bus.poke(0xe822, 0xff)
|
|
68
|
+
lines.run_steps(lines.byte_sent, bus)
|
|
44
69
|
end
|
|
45
70
|
|
|
46
71
|
# The end of SECOND, TKSA, UNTALK and UNLISTEN: ATN let go. Returns
|
|
47
72
|
# the value written, which the ROM leaves in A.
|
|
48
73
|
def release_attention(bus)
|
|
49
|
-
|
|
50
|
-
bus.poke(0xe840, value)
|
|
51
|
-
value
|
|
74
|
+
lines.run_steps(lines.release_attention, bus)
|
|
52
75
|
end
|
|
53
76
|
|
|
54
|
-
# TKSA
|
|
77
|
+
# TKSA ends listening: NRFD and NDAC pulled low.
|
|
55
78
|
def listen_to_talker(bus)
|
|
56
|
-
|
|
57
|
-
|
|
79
|
+
lines.run_steps(lines.listen_to_talker, bus)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# ACPTR ends listening as well.
|
|
83
|
+
def byte_received(bus)
|
|
84
|
+
lines.run_steps(lines.byte_received, bus)
|
|
58
85
|
end
|
|
59
86
|
|
|
60
87
|
# The end of UNTALK and UNLISTEN, which address the bus as TALK and
|
|
61
|
-
# LISTEN do, send their command and let ATN go.
|
|
88
|
+
# LISTEN do, send their command and let ATN go. Returns the value
|
|
89
|
+
# the ROM leaves in A.
|
|
62
90
|
def release_serial_lines(bus)
|
|
63
|
-
|
|
64
|
-
byte_sent(bus)
|
|
65
|
-
release_attention(bus)
|
|
91
|
+
lines.run_steps(lines.release, bus)
|
|
66
92
|
end
|
|
67
93
|
end
|
|
68
94
|
|
|
95
|
+
# A PET drives the bus's control lines from three chips: ATN from VIA
|
|
96
|
+
# PB2 and NRFD from PB1 at $E840, NDAC from PIA 2's CA2 and DAV from its
|
|
97
|
+
# CB2, through the control registers at $E821 and $E823, which the ROM
|
|
98
|
+
# writes $34 to pull a line low and $3C to let it go, and the data lines
|
|
99
|
+
# from PIA 2's port B at $E822. Each handshake times out on VIA timer 1,
|
|
100
|
+
# which the ROM starts with a write of $FF to $E845. The steps leave the
|
|
101
|
+
# lines as each primitive leaves them.
|
|
102
|
+
PET_ATTENTION = [
|
|
103
|
+
IEEELineStep.new(address: 0xe840, keep: 0xff, set: 0x02),
|
|
104
|
+
IEEELineStep.new(address: 0xe821, keep: 0, set: 0x3c),
|
|
105
|
+
IEEELineStep.new(address: 0xe840, keep: 0xfb, set: 0)
|
|
106
|
+
].freeze
|
|
107
|
+
PET_BYTE_SENT = [
|
|
108
|
+
IEEELineStep.new(address: 0xe823, keep: 0, set: 0x3c),
|
|
109
|
+
IEEELineStep.new(address: 0xe822, keep: 0, set: 0xff)
|
|
110
|
+
].freeze
|
|
111
|
+
PET_RELEASE_ATTENTION = [IEEELineStep.new(address: 0xe840, keep: 0xff, set: 0x04)].freeze
|
|
112
|
+
PET_LISTEN = [IEEELineStep.new(address: 0xe840, keep: 0xfd, set: 0),
|
|
113
|
+
IEEELineStep.new(address: 0xe821, keep: 0, set: 0x34)].freeze
|
|
114
|
+
PET_LINES = IEEELines.new(
|
|
115
|
+
timer: [IEEELineStep.new(address: 0xe845, keep: 0, set: 0xff)].freeze,
|
|
116
|
+
attention: PET_ATTENTION, byte_sent: PET_BYTE_SENT, release_attention: PET_RELEASE_ATTENTION,
|
|
117
|
+
listen_to_talker: PET_LISTEN, byte_received: PET_LISTEN,
|
|
118
|
+
release: (PET_ATTENTION + PET_BYTE_SENT + PET_RELEASE_ATTENTION).freeze
|
|
119
|
+
)
|
|
120
|
+
|
|
69
121
|
# The file variables of the BASIC 2 and BASIC 4 KERNALs, which keep
|
|
70
122
|
# them at the same addresses. SAVE copies STAL at $FB to SAL at $C7 and
|
|
71
123
|
# walks SAL up to EAL at $C9. LOAD keeps no MEMUSS: it stores each byte
|
|
@@ -90,24 +142,24 @@ module Badline
|
|
|
90
142
|
# LISTEN without a secondary address.
|
|
91
143
|
PET_BASIC1_LAYOUT = IEEELayout.new(
|
|
92
144
|
talk: 0xf0b6, listen: 0xf0ba, second: 0xf12c, tksa: 0xf15b,
|
|
93
|
-
ciout: 0xf167, untalk: 0xf17a, unlisten: 0xf17e, acptr: 0xf187, overwrites_device: true,
|
|
94
|
-
zero_page: PET_BASIC1_ZERO_PAGE
|
|
145
|
+
ciout: 0xf167, untalk: 0xf17a, unlisten: 0xf17e, acptr: 0xf187, overwrites_device: true, device_in_a: false,
|
|
146
|
+
zero_page: PET_BASIC1_ZERO_PAGE, lines: PET_LINES
|
|
95
147
|
)
|
|
96
148
|
|
|
97
149
|
# BASIC 2, 901465-03. Its LOAD sends the secondary address after TALK
|
|
98
150
|
# with SECOND, not TKSA.
|
|
99
151
|
PET_BASIC2_LAYOUT = IEEELayout.new(
|
|
100
152
|
talk: 0xf0b6, listen: 0xf0ba, second: 0xf128, tksa: 0xf164,
|
|
101
|
-
ciout: 0xf16f, untalk: 0xf17f, unlisten: 0xf183, acptr: 0xf18c, overwrites_device: false,
|
|
102
|
-
zero_page: PET_ZERO_PAGE
|
|
153
|
+
ciout: 0xf16f, untalk: 0xf17f, unlisten: 0xf183, acptr: 0xf18c, overwrites_device: false, device_in_a: false,
|
|
154
|
+
zero_page: PET_ZERO_PAGE, lines: PET_LINES
|
|
103
155
|
)
|
|
104
156
|
|
|
105
157
|
# BASIC 4, 901465-22. LISTEN at $F0D5 is the operand of the BIT
|
|
106
158
|
# instruction that skips it on the way from TALK.
|
|
107
159
|
PET_BASIC4_LAYOUT = IEEELayout.new(
|
|
108
160
|
talk: 0xf0d2, listen: 0xf0d5, second: 0xf143, tksa: 0xf193,
|
|
109
|
-
ciout: 0xf19e, untalk: 0xf1ae, unlisten: 0xf1b9, acptr: 0xf1c0, overwrites_device: false,
|
|
110
|
-
zero_page: PET_ZERO_PAGE
|
|
161
|
+
ciout: 0xf19e, untalk: 0xf1ae, unlisten: 0xf1b9, acptr: 0xf1c0, overwrites_device: false, device_in_a: false,
|
|
162
|
+
zero_page: PET_ZERO_PAGE, lines: PET_LINES
|
|
111
163
|
)
|
|
112
164
|
end
|
|
113
165
|
end
|