badline 0.15.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -0
- data/CLAUDE.md +8 -7
- data/README.md +102 -6
- data/doc/pinned-behaviour.md +67 -0
- data/doc/snapshot-format.md +213 -18
- data/doc/snapshots.md +14 -0
- data/lib/badline/c64/common.rb +1 -1
- data/lib/badline/chips/crtc/keyed_state.rb +46 -0
- data/lib/badline/chips/crtc.rb +163 -0
- data/lib/badline/chips/pia/control_lines.rb +138 -0
- data/lib/badline/chips/pia/keyed_parts.rb +56 -0
- data/lib/badline/chips/pia.rb +138 -0
- data/lib/badline/chips/vic/register_log.rb +1 -4
- data/lib/badline/core.rb +3 -0
- data/lib/badline/drives/ieee_bus.rb +28 -0
- data/lib/badline/drives/model1551/cable.rb +5 -2
- data/lib/badline/drives/model1551/tia.rb +13 -34
- data/lib/badline/drives/model1551.rb +26 -4
- data/lib/badline/drives.rb +1 -0
- data/lib/badline/frontend/app.rb +1 -1
- data/lib/badline/frontend/controls.rb +23 -3
- data/lib/badline/frontend/gamepads.rb +10 -3
- data/lib/badline/frontend/keys.rb +88 -0
- data/lib/badline/frontend/media_slots.rb +1 -0
- data/lib/badline/frontend/menu/menu_dialogs.rb +2 -1
- data/lib/badline/frontend/menu/menu_media.rb +27 -28
- data/lib/badline/frontend/menu/menu_pages.rb +15 -2
- data/lib/badline/frontend/menu/menu_ports.rb +22 -1
- data/lib/badline/frontend/menu/pause_menu.rb +1 -0
- data/lib/badline/frontend/pet_keyboard.rb +124 -0
- data/lib/badline/frontend/screens.rb +2 -1
- data/lib/badline/frontend/timeline.rb +16 -9
- data/lib/badline/frontend.rb +1 -0
- data/lib/badline/kernal_trap/dos/writes.rb +3 -1
- data/lib/badline/kernal_trap/dos.rb +6 -1
- data/lib/badline/kernal_trap/frames.rb +117 -0
- data/lib/badline/kernal_trap/ieee.rb +148 -0
- data/lib/badline/kernal_trap/ieee_layout.rb +113 -0
- data/lib/badline/kernal_trap/keyed_state.rb +4 -4
- data/lib/badline/kernal_trap/routine.rb +3 -53
- data/lib/badline/kernal_trap/serial.rb +4 -102
- data/lib/badline/kernal_trap/steps.rb +64 -0
- data/lib/badline/kernal_trap.rb +4 -0
- data/lib/badline/machine/keyboard_buffer.rb +8 -0
- data/lib/badline/machine/settling.rb +1 -1
- data/lib/badline/machine.rb +28 -6
- data/lib/badline/media/extensions.rb +5 -2
- data/lib/badline/media/pet_media.rb +40 -0
- data/lib/badline/media/program.rb +14 -2
- data/lib/badline/media/vic20_media.rb +2 -2
- data/lib/badline/media.rb +36 -16
- data/lib/badline/options/family.rb +17 -6
- data/lib/badline/options/help.rb +19 -3
- data/lib/badline/options/table.rb +14 -2
- data/lib/badline/options/validation.rb +68 -5
- data/lib/badline/options.rb +31 -19
- data/lib/badline/pet/bus/expansion.rb +106 -0
- data/lib/badline/pet/bus.rb +204 -0
- data/lib/badline/pet/canvas.rb +97 -0
- data/lib/badline/pet/cassettes.rb +63 -0
- data/lib/badline/pet/characters.rb +26 -0
- data/lib/badline/pet/cpu.rb +10 -0
- data/lib/badline/pet/crtc_video.rb +142 -0
- data/lib/badline/pet/discrete_video.rb +83 -0
- data/lib/badline/pet/ieee_ports.rb +66 -0
- data/lib/badline/pet/ieee_traps.rb +75 -0
- data/lib/badline/pet/keyboard_ports.rb +74 -0
- data/lib/badline/pet/keyed_modules.rb +85 -0
- data/lib/badline/pet/keyed_parts.rb +123 -0
- data/lib/badline/pet/keyed_state.rb +42 -0
- data/lib/badline/pet/model.rb +113 -0
- data/lib/badline/pet/rom_sets.rb +65 -0
- data/lib/badline/pet/sound.rb +128 -0
- data/lib/badline/pet/via.rb +10 -0
- data/lib/badline/pet/via_ports.rb +19 -0
- data/lib/badline/pet.rb +226 -0
- data/lib/badline/plus4/keyed_state.rb +5 -3
- data/lib/badline/plus4/tcbm_drive.rb +14 -0
- data/lib/badline/plus4.rb +1 -1
- data/lib/badline/roms/README +137 -0
- data/lib/badline/roms/pet/basic1.rom +0 -0
- data/lib/badline/roms/pet/basic2.rom +0 -0
- data/lib/badline/roms/pet/basic4.rom +0 -0
- data/lib/badline/roms/pet/characters1.rom +0 -0
- data/lib/badline/roms/pet/characters2.rom +0 -0
- data/lib/badline/roms/pet/editor1.rom +0 -0
- data/lib/badline/roms/pet/editor2-b.rom +0 -0
- data/lib/badline/roms/pet/editor2-n.rom +0 -0
- data/lib/badline/roms/pet/editor4-40-n-50hz.rom +0 -0
- data/lib/badline/roms/pet/editor4-40-n-60hz.rom +0 -0
- data/lib/badline/roms/pet/editor4-80-b-50hz-0283.rom +0 -0
- data/lib/badline/roms/pet/editor4-80-b-50hz.rom +0 -0
- data/lib/badline/roms/pet/editor4-80-b-60hz.rom +0 -0
- data/lib/badline/roms/pet/editor4-b.rom +0 -0
- data/lib/badline/roms/pet/editor4-n.rom +0 -0
- data/lib/badline/roms/pet/kernal1.rom +0 -0
- data/lib/badline/roms/pet/kernal2.rom +0 -0
- data/lib/badline/roms/pet/kernal4.rom +0 -0
- data/lib/badline/snapshot/container.rb +2 -0
- data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
- data/lib/badline/snapshot/field_doc.rb +16 -3
- data/lib/badline/snapshot/image.rb +14 -7
- data/lib/badline/snapshot/pet_setup.rb +71 -0
- data/lib/badline/snapshot/storage_setup.rb +7 -1
- data/lib/badline/snapshot.rb +9 -5
- data/lib/badline/storage/d80_image.rb +48 -0
- data/lib/badline/storage/d82_image.rb +21 -0
- data/lib/badline/storage/tap.rb +16 -3
- data/lib/badline/storage.rb +2 -0
- data/lib/badline/support/pet_checkpoint.rb +38 -0
- data/lib/badline/timing.rb +5 -4
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic20/attachments.rb +7 -2
- data/lib/badline/vic20/bus/keyed_state.rb +5 -4
- data/lib/badline/vic20/bus.rb +5 -0
- data/lib/badline/vic20.rb +1 -1
- metadata +55 -1
data/lib/badline/options/help.rb
CHANGED
|
@@ -49,7 +49,8 @@ module Badline
|
|
|
49
49
|
# wrapped as the banner is.
|
|
50
50
|
def media_kinds
|
|
51
51
|
text = "Media can be #{listed(Media::Extensions::PROGRAMS)} program, #{listed(%i[disk])} disk image, " \
|
|
52
|
-
"#{listed(%i[
|
|
52
|
+
"#{listed(%i[ieee_disk])} disk image for a PET, #{listed(%i[gcr])} disk image for a true drive, " \
|
|
53
|
+
"#{listed(%i[disk_list])} list of disk images, " \
|
|
53
54
|
"#{listed(%i[archive])} tape archive, #{listed(%i[tape])} tape, #{listed(%i[cartridge])} " \
|
|
54
55
|
"cartridge, #{listed(%i[tune])} tune, #{listed(%w[.vsf])} snapshot, or a directory to mount as device 8."
|
|
55
56
|
wrap(text)
|
|
@@ -84,6 +85,7 @@ module Badline
|
|
|
84
85
|
#{@program} vic20 [options] [media]
|
|
85
86
|
#{@program} c128 [options] [media]
|
|
86
87
|
#{@program} plus4 [options] [media]
|
|
88
|
+
#{@program} pet [options] [media]
|
|
87
89
|
#{@program} --headless [options] tune.sid
|
|
88
90
|
#{@program} [options] tune.sid --audio-out FILE
|
|
89
91
|
#{@program} sid [options] FILE|DIR...
|
|
@@ -93,6 +95,7 @@ module Badline
|
|
|
93
95
|
vic20 Run a PAL VIC-20, with the RAM --ram names
|
|
94
96
|
c128 Run a C128 in C128 mode, the one --model names
|
|
95
97
|
plus4 Run a Plus/4 or a C16, the one --model names
|
|
98
|
+
pet Run a PET or a CBM, the one --model names
|
|
96
99
|
sid Play .sid tunes and directories of them
|
|
97
100
|
|
|
98
101
|
#{media_kinds}
|
|
@@ -116,6 +119,18 @@ module Badline
|
|
|
116
119
|
--drive-type 1541 a 1541 on the serial bus instead. There Escape is
|
|
117
120
|
ESC, Page Down RUN/STOP and F4 HELP.
|
|
118
121
|
|
|
122
|
+
The PET and the CBM take programs, disks, .d80 and .d82 among them,
|
|
123
|
+
and tapes, and a .d80, a .d82 or a PET tape starts one without
|
|
124
|
+
`pet`. Their keys type what the host's keys type, Alt with a key
|
|
125
|
+
presses the PET's SHIFT and the key its unshifted character is on,
|
|
126
|
+
and the pause menu's KEYBOARD page says where the rest are. --basic
|
|
127
|
+
and --editor fit the upgrades Commodore sold, as in --model 2001
|
|
128
|
+
--basic 2. BASIC 1 can't LOAD from a disk drive, though a disk still
|
|
129
|
+
starts its first program, and BASIC 2 can. The 8032 and the 8096
|
|
130
|
+
have two 50 Hz editors: 4-80-b-50hz, the default, and
|
|
131
|
+
4-80-b-50hz-0283, another chip's, with 64-cycle lines and 313 lines
|
|
132
|
+
a frame.
|
|
133
|
+
|
|
119
134
|
--headless plays a .sid tune on the host's audio device without the
|
|
120
135
|
window, and --audio-out renders it to 16-bit PCM instead. The
|
|
121
136
|
container follows the file's extension, .wav or .aiff. Played on a
|
|
@@ -136,8 +151,9 @@ module Badline
|
|
|
136
151
|
--at and --script run events once a number of frames has run, as
|
|
137
152
|
in --at 3500:key=space or --at 250,500:screenshot=shot%05d.bmp.
|
|
138
153
|
key=NAME presses a key (space, return, a, f1, run_stop, restore, and
|
|
139
|
-
on the C128 esc, help, keypad1 and the rest,
|
|
140
|
-
esc, help and f1 to f3) or a
|
|
154
|
+
on the C128 esc, help, keypad1 and the rest, on the Plus/4
|
|
155
|
+
esc, help and f1 to f3, and on the PET rvs, escape and kp1) or a
|
|
156
|
+
joystick's
|
|
141
157
|
direction or fire (joy1-up, joy2-fire) for #{Event::HOLD} frames, and
|
|
142
158
|
pushes the C128's caps_lock or forty_eighty, which lock. type=TEXT
|
|
143
159
|
types it, with \\n for RETURN. insert=FILE swaps in a disk, a list's
|
|
@@ -18,6 +18,10 @@ module Badline
|
|
|
18
18
|
# another name for the C16.
|
|
19
19
|
PLUS4_MODELS = %w[plus4 plus4ntsc c16 c16ntsc c116].freeze
|
|
20
20
|
|
|
21
|
+
# The PETs and CBMs, as PET::Model names them, the 8032 first, the
|
|
22
|
+
# default.
|
|
23
|
+
PET_MODELS = %w[8032 8096 4032 3032 3032b 2001].freeze
|
|
24
|
+
|
|
21
25
|
# The true drives --drive-type names for the Plus/4.
|
|
22
26
|
PLUS4_DRIVE_TYPES = %w[1541 1551].freeze
|
|
23
27
|
|
|
@@ -50,13 +54,21 @@ module Badline
|
|
|
50
54
|
Option.new("--reu SIZE", "Plug in an REU of SIZE K: 128, 256, 512 (a 1750) or up to 16384",
|
|
51
55
|
section: "Window options", needs: :window),
|
|
52
56
|
Option.new("--model NAME", "C64 to run: #{MODELS.join(', ')} (default: #{MODELS.first}); VIC-20: pal; " \
|
|
53
|
-
"C128: #{C128_MODELS.join(', ')}; Plus/4: #{PLUS4_MODELS.join(', ')}"
|
|
57
|
+
"C128: #{C128_MODELS.join(', ')}; Plus/4: #{PLUS4_MODELS.join(', ')}; " \
|
|
58
|
+
"PET: #{PET_MODELS.join(', ')}",
|
|
54
59
|
section: "Window options", needs: :window),
|
|
55
60
|
Option.new("--ntsc", "Run an NTSC C64, as --model ntsc does", section: "Window options", needs: :window),
|
|
56
61
|
Option.new("--c64", "Start the C128 in C64 mode, holding C= at power-on",
|
|
57
62
|
section: "Window options", needs: :window),
|
|
58
63
|
Option.new("--ram SIZE", "VIC-20 RAM expansion: #{RAM_NAMES.join(', ')} (default: unexpanded); " \
|
|
59
|
-
"Plus/4 RAM: 16k, 32k or 64k (default: the model's own)",
|
|
64
|
+
"Plus/4 RAM: 16k, 32k or 64k; PET RAM: 4k, 8k, 16k or 32k (default: the model's own)",
|
|
65
|
+
section: "Window options", needs: :window),
|
|
66
|
+
Option.new("--60hz", "Run a CRTC PET's 60 Hz editor instead of its 50 Hz one",
|
|
67
|
+
section: "Window options", needs: :window),
|
|
68
|
+
Option.new("--basic VERSION", "Fit a PET with BASIC 1, 2 or 4, as its board takes (default: its own)",
|
|
69
|
+
section: "Window options", needs: :window),
|
|
70
|
+
Option.new("--editor NAME", "Fit a PET with an editor ROM its board and BASIC take: " \
|
|
71
|
+
"#{PET::ROMSets::EDITORS.keys.join(', ')}",
|
|
60
72
|
section: "Window options", needs: :window),
|
|
61
73
|
Option.new("--c1lo FILE", "Put a .bin ROM image in the Plus/4's cartridge 1, low slot ($8000)",
|
|
62
74
|
section: "Window options", needs: :window),
|
|
@@ -39,6 +39,12 @@ module Badline
|
|
|
39
39
|
PLUS4_RAM[value]
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
def pet_ram(value)
|
|
43
|
+
raise Error, "invalid argument: --ram #{value}" unless PET_RAM.key?(value)
|
|
44
|
+
|
|
45
|
+
PET_RAM[value]
|
|
46
|
+
end
|
|
47
|
+
|
|
42
48
|
def plus4_ram_expansion(value)
|
|
43
49
|
raise Error, "invalid argument: --ram-expansion #{value}" unless PLUS4_RAM_EXPANSIONS.key?(value)
|
|
44
50
|
|
|
@@ -92,12 +98,10 @@ module Badline
|
|
|
92
98
|
end
|
|
93
99
|
|
|
94
100
|
def validate_family
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
elsif !@ram.nil?
|
|
98
|
-
raise Error, "--ram needs vic20 or plus4"
|
|
99
|
-
end
|
|
101
|
+
ram_option
|
|
102
|
+
validate_vic20 if @family == :vic20
|
|
100
103
|
@family == :plus4 ? validate_plus4 : refuse_plus4_options
|
|
104
|
+
@family == :pet ? validate_pet : refuse_pet_options
|
|
101
105
|
if @family == :c128
|
|
102
106
|
validate_c128
|
|
103
107
|
elsif @c64_mode
|
|
@@ -105,6 +109,19 @@ module Badline
|
|
|
105
109
|
end
|
|
106
110
|
end
|
|
107
111
|
|
|
112
|
+
# --ram names the VIC-20's RAM expansion, or the Plus/4's or the PET's
|
|
113
|
+
# RAM.
|
|
114
|
+
def ram_option
|
|
115
|
+
return if @ram_name.nil?
|
|
116
|
+
|
|
117
|
+
case @family
|
|
118
|
+
when :vic20 then @ram = ram_configuration(@ram_name)
|
|
119
|
+
when :plus4 then @ram_kb = plus4_ram(@ram_name)
|
|
120
|
+
when :pet then @ram_kb = pet_ram(@ram_name)
|
|
121
|
+
else raise Error, "--ram needs vic20, plus4 or pet"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
108
125
|
# The C128 runs in the window, without an REU, and its NTSC models
|
|
109
126
|
# have names of their own.
|
|
110
127
|
def validate_c128
|
|
@@ -153,6 +170,52 @@ module Badline
|
|
|
153
170
|
raise Error, "--sid-card-address needs --sid-card" unless @sid_card_address.nil? || @sid_card
|
|
154
171
|
end
|
|
155
172
|
|
|
173
|
+
# Only the PET has a frame rate to pick and BASIC and editor ROMs to
|
|
174
|
+
# fit.
|
|
175
|
+
def refuse_pet_options
|
|
176
|
+
raise Error, "--60hz needs pet" unless @refresh_hz.nil?
|
|
177
|
+
raise Error, "--basic needs pet" unless @basic.nil?
|
|
178
|
+
raise Error, "--editor needs pet" unless @editor.nil?
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# The PET runs in the window, without the C64's SID and REU or a true
|
|
182
|
+
# drive, at the frame rate its editor sets.
|
|
183
|
+
def validate_pet
|
|
184
|
+
raise Error, "--ntsc needs the C64; a CRTC PET's 60 Hz editor is --60hz" if @models.include?("ntsc")
|
|
185
|
+
raise Error, "--sid needs the C64" unless @sid_model.nil?
|
|
186
|
+
raise Error, "--reu needs the C64" unless @reu.nil?
|
|
187
|
+
raise Error, "the PET has no true drive yet" if @true_drive
|
|
188
|
+
raise Error, "the PET needs the window" if headless?
|
|
189
|
+
|
|
190
|
+
validate_pet_roms if PET_MODELS.include?(model)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# --basic and --editor name ROMs the model's board takes
|
|
194
|
+
# (PET::ROMSets), and an editor named sets the frame rate.
|
|
195
|
+
def validate_pet_roms
|
|
196
|
+
raise Error, "invalid argument: --basic #{@basic}" unless @basic.nil? || PET::ROMSets::BASICS.key?(@basic)
|
|
197
|
+
raise Error, "invalid argument: --editor #{@editor}" unless @editor.nil? || PET::ROMSets::EDITORS.key?(@editor)
|
|
198
|
+
|
|
199
|
+
basic = @basic || PET::ROMSets.stock_basic(model)
|
|
200
|
+
fitting = PET::ROMSets.editors(model, basic)
|
|
201
|
+
raise Error, "the #{model} takes no BASIC #{basic}" if fitting.empty?
|
|
202
|
+
return if @editor.nil?
|
|
203
|
+
raise Error, "the #{model} with BASIC #{basic} takes no editor #{@editor}" unless fitting.include?(@editor)
|
|
204
|
+
|
|
205
|
+
editor_rate(fitting)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# The frame rate of the editor named, on a CRTC board, which has a 50
|
|
209
|
+
# Hz and a 60 Hz one.
|
|
210
|
+
def editor_rate(fitting)
|
|
211
|
+
return if fitting.size == 1
|
|
212
|
+
|
|
213
|
+
editor_hz = @editor == fitting.last ? 60 : 50
|
|
214
|
+
raise Error, "--60hz takes a 60 Hz editor, not #{@editor}" if @refresh_hz == 60 && editor_hz == 50
|
|
215
|
+
|
|
216
|
+
@refresh_hz = editor_hz
|
|
217
|
+
end
|
|
218
|
+
|
|
156
219
|
# `sid` takes directories as well as tunes.
|
|
157
220
|
def validate_tune(path)
|
|
158
221
|
return if !File.exist?(path) || File.extname(path).casecmp?(".sid")
|
data/lib/badline/options.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "badline/pet/rom_sets"
|
|
3
4
|
require "badline/options/option"
|
|
4
5
|
require "badline/options/event"
|
|
5
6
|
require "badline/options/table"
|
|
@@ -12,10 +13,12 @@ module Badline
|
|
|
12
13
|
# Either opens the window for any media, or with --headless or
|
|
13
14
|
# --audio-out plays or renders a .sid tune without one. `sid`, given
|
|
14
15
|
# first, plays .sid tunes and directories of them in the terminal.
|
|
15
|
-
# `c64`, `vic20`, `c128` or `
|
|
16
|
-
# family, and without one the family is the C64,
|
|
17
|
-
# that loads where C128 BASIC starts
|
|
18
|
-
#
|
|
16
|
+
# `c64`, `vic20`, `c128`, `plus4` or `pet`, given first, names the
|
|
17
|
+
# machine family, and without one the family is the C64, the C128 for a
|
|
18
|
+
# program that loads where C128 BASIC starts, or the PET for an 8050's
|
|
19
|
+
# or 8250's disk or a PET tape.
|
|
20
|
+
# The native build adds --no-sound and --version, and badline-ruby adds
|
|
21
|
+
# --disable-jit.
|
|
19
22
|
#
|
|
20
23
|
# TABLE lists the options, and both the parser and the help read it. It
|
|
21
24
|
# parses by hand, inside the subset of Ruby Spinel compiles. Values come
|
|
@@ -51,6 +54,9 @@ module Badline
|
|
|
51
54
|
# The Plus/4's RAM sizes as --ram names them, in K.
|
|
52
55
|
PLUS4_RAM = { "16k" => 16, "32k" => 32, "64k" => 64 }.freeze
|
|
53
56
|
|
|
57
|
+
# The PET's, below $8000.
|
|
58
|
+
PET_RAM = { "4k" => 4, "8k" => 8, "16k" => 16, "32k" => 32 }.freeze
|
|
59
|
+
|
|
54
60
|
# Where --sid-card-address puts the Plus/4's SID card.
|
|
55
61
|
SID_CARD_ADDRESSES = { "fd40" => 0xfd40, "fe80" => 0xfe80 }.freeze
|
|
56
62
|
|
|
@@ -70,16 +76,20 @@ module Badline
|
|
|
70
76
|
attr_reader :program, :media_path, :tune_paths, :subtune, :sid_model, :reu, :frames, :screenshot, :save_snapshot,
|
|
71
77
|
:audio_out, :seconds, :songlengths, :filter_chunk, :timeline
|
|
72
78
|
|
|
73
|
-
# The machine family to build (Badline::Machine), :c64, :vic20, :c128
|
|
74
|
-
# or :
|
|
79
|
+
# The machine family to build (Badline::Machine), :c64, :vic20, :c128,
|
|
80
|
+
# :plus4 or :pet.
|
|
75
81
|
attr_reader :family
|
|
76
82
|
|
|
83
|
+
# The ROMs --basic and --editor fit a PET with, keys of
|
|
84
|
+
# PET::ROMSets::BASICS and PET::ROMSets::EDITORS, or nil for its own.
|
|
85
|
+
attr_reader :basic, :editor
|
|
86
|
+
|
|
77
87
|
# The VIC-20's RAM expansion --ram names, a key of
|
|
78
88
|
# Vic20::Bus::RAM_CONFIGURATIONS, or nil without --ram.
|
|
79
89
|
attr_reader :ram
|
|
80
90
|
|
|
81
|
-
# The Plus/4's RAM in K --ram names, one of PLUS4_RAM's
|
|
82
|
-
# --ram.
|
|
91
|
+
# The Plus/4's or the PET's RAM in K --ram names, one of PLUS4_RAM's or
|
|
92
|
+
# PET_RAM's, or nil without --ram.
|
|
83
93
|
attr_reader :ram_kb
|
|
84
94
|
|
|
85
95
|
# The Plus/4's cartridge ROM images --c1lo, --c1hi, --c2lo and --c2hi
|
|
@@ -121,6 +131,10 @@ module Badline
|
|
|
121
131
|
@reu = nil
|
|
122
132
|
@ram = nil
|
|
123
133
|
@ram_kb = nil
|
|
134
|
+
@ram_name = nil
|
|
135
|
+
@refresh_hz = nil
|
|
136
|
+
@basic = nil
|
|
137
|
+
@editor = nil
|
|
124
138
|
@cartridges = {}
|
|
125
139
|
@autostart = true
|
|
126
140
|
@writable = false
|
|
@@ -229,6 +243,10 @@ module Badline
|
|
|
229
243
|
# power-on.
|
|
230
244
|
def c64_mode? = @c64_mode
|
|
231
245
|
|
|
246
|
+
# The frame rate of a CRTC PET's editor: 60 with --60hz or a 60 Hz
|
|
247
|
+
# --editor, else 50.
|
|
248
|
+
def refresh_hz = @refresh_hz || 50
|
|
249
|
+
|
|
232
250
|
def help? = @help
|
|
233
251
|
|
|
234
252
|
def version? = @version
|
|
@@ -244,7 +262,7 @@ module Badline
|
|
|
244
262
|
# A .sid on its own plays in the SID player, as `sid` would, unless an
|
|
245
263
|
# option of the emulator's window asks for the machine.
|
|
246
264
|
def play_lone_tune
|
|
247
|
-
return if @sid_command || %i[vic20 plus4].include?(@family) || headless? || !@window_only.empty?
|
|
265
|
+
return if @sid_command || %i[vic20 plus4 pet].include?(@family) || headless? || !@window_only.empty?
|
|
248
266
|
return if @media_path.nil?
|
|
249
267
|
|
|
250
268
|
@sid_command = File.extname(@media_path).casecmp?(".sid")
|
|
@@ -303,7 +321,9 @@ module Badline
|
|
|
303
321
|
when "--ram-expansion" then @ram_expansion = plus4_ram_expansion(value)
|
|
304
322
|
when "--model" then @models << value
|
|
305
323
|
when "--reu" then @reu = reu_size(value)
|
|
306
|
-
when "--ram" then
|
|
324
|
+
when "--ram" then @ram_name = value
|
|
325
|
+
when "--basic" then @basic = value
|
|
326
|
+
when "--editor" then @editor = value
|
|
307
327
|
when "--drive-type" then @drive_type = drive_type_for(value)
|
|
308
328
|
when "--c1lo", "--c1hi", "--c2lo", "--c2hi" then @cartridges[CARTRIDGE_SLOTS.fetch(name)] = value
|
|
309
329
|
when "--frames" then @frames = number(flag, value)
|
|
@@ -318,15 +338,6 @@ module Badline
|
|
|
318
338
|
end
|
|
319
339
|
end
|
|
320
340
|
|
|
321
|
-
# --ram names the VIC-20's RAM expansion, or the Plus/4's RAM.
|
|
322
|
-
def ram_option(value)
|
|
323
|
-
if @family == :plus4
|
|
324
|
-
@ram_kb = plus4_ram(value)
|
|
325
|
-
else
|
|
326
|
-
@ram = ram_configuration(value)
|
|
327
|
-
end
|
|
328
|
-
end
|
|
329
|
-
|
|
330
341
|
def switch(name)
|
|
331
342
|
case name
|
|
332
343
|
when "--no-autostart" then @autostart = false
|
|
@@ -339,6 +350,7 @@ module Badline
|
|
|
339
350
|
when "--vice" then @vice = true
|
|
340
351
|
when "--ntsc" then @models << "ntsc"
|
|
341
352
|
when "--c64" then @c64_mode = true
|
|
353
|
+
when "--60hz" then @refresh_hz = 60
|
|
342
354
|
when "--unpaced" then @paced = false
|
|
343
355
|
when "--disable-jit" then @jit = false
|
|
344
356
|
when "--help" then @help = true
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class PET
|
|
5
|
+
class Bus
|
|
6
|
+
# The 8096's 64K board: four 16K banks, which a write-only control
|
|
7
|
+
# register at $FFF0 lays over $8000-$FFFF, two at a time:
|
|
8
|
+
#
|
|
9
|
+
# bit 7 the board on; when clear, a plain 8032 and the rest ignored
|
|
10
|
+
# bit 6 I/O shows through at $E800-$EFFF
|
|
11
|
+
# bit 5 the screen shows through at $8000-$8FFF
|
|
12
|
+
# bit 3 bank 3 at $C000-$FFFF, else bank 1
|
|
13
|
+
# bit 2 bank 2 at $8000-$BFFF, else bank 0
|
|
14
|
+
# bit 1 writes to $C000-$FFFF go nowhere
|
|
15
|
+
# bit 0 writes to $8000-$BFFF go nowhere
|
|
16
|
+
#
|
|
17
|
+
# A write to $FFF0 lands where $FFF0 was before it, too. The RES line
|
|
18
|
+
# clears the register, so the 8096 starts as an 8032.
|
|
19
|
+
class Expansion
|
|
20
|
+
# A 16K bank of the board, seen from +start+.
|
|
21
|
+
class Window
|
|
22
|
+
attr_accessor :read_only
|
|
23
|
+
|
|
24
|
+
def initialize(cells, offset, start)
|
|
25
|
+
@cells = cells
|
|
26
|
+
@offset = offset - start
|
|
27
|
+
@read_only = false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def peek(addr) = @cells[addr + @offset]
|
|
31
|
+
|
|
32
|
+
def poke(addr, value)
|
|
33
|
+
@cells[addr + @offset] = value unless @read_only
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Page $FF's writes, which pass to what the page holds and set the
|
|
38
|
+
# control register at $FFF0.
|
|
39
|
+
class ControlPort
|
|
40
|
+
attr_accessor :under
|
|
41
|
+
|
|
42
|
+
def initialize(expansion, under)
|
|
43
|
+
@expansion = expansion
|
|
44
|
+
@under = under
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def poke(addr, value)
|
|
48
|
+
@under.poke(addr, value)
|
|
49
|
+
@expansion.control = value if addr == 0xfff0
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
attr_reader :cells, :control
|
|
54
|
+
|
|
55
|
+
def initialize(bus)
|
|
56
|
+
@bus = bus
|
|
57
|
+
@cells = Array.new(0x10000, 0)
|
|
58
|
+
@banks = [0x8000, 0xc000, 0x8000, 0xc000].each_with_index.map do |start, bank|
|
|
59
|
+
Window.new(@cells, bank * 0x4000, start)
|
|
60
|
+
end
|
|
61
|
+
@port = ControlPort.new(self, @banks[1])
|
|
62
|
+
@control = 0
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def control=(value)
|
|
66
|
+
@control = value
|
|
67
|
+
@bus.map_pages
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def reset!
|
|
71
|
+
self.control = 0
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def power_on!
|
|
75
|
+
@cells.fill(0)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Lays the banks the register picks over the pages, and the
|
|
79
|
+
# control register over page $FF's writes.
|
|
80
|
+
def map(reads, writes)
|
|
81
|
+
map_banks(reads, writes) if @control.anybits?(0x80)
|
|
82
|
+
@port.under = writes[0xff]
|
|
83
|
+
writes[0xff] = @port
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def map_banks(reads, writes)
|
|
89
|
+
low = @banks[@control.anybits?(0x04) ? 2 : 0]
|
|
90
|
+
high = @banks[@control.anybits?(0x08) ? 3 : 1]
|
|
91
|
+
low.read_only = @control.anybits?(0x01)
|
|
92
|
+
high.read_only = @control.anybits?(0x02)
|
|
93
|
+
first = @control.anybits?(0x20) ? 0x90 : 0x80
|
|
94
|
+
reads.fill(low, first, 0xc0 - first)
|
|
95
|
+
writes.fill(low, first, 0xc0 - first)
|
|
96
|
+
[[0xc0, 0x28], [0xe8, 0x08], [0xf0, 0x10]].each do |page, count|
|
|
97
|
+
next if page == 0xe8 && @control.anybits?(0x40)
|
|
98
|
+
|
|
99
|
+
reads.fill(high, page, count)
|
|
100
|
+
writes.fill(high, page, count)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/pet/bus/expansion"
|
|
4
|
+
|
|
5
|
+
module Badline
|
|
6
|
+
class PET
|
|
7
|
+
# The CPU's bus:
|
|
8
|
+
#
|
|
9
|
+
# $0000-$7FFF - RAM, 4K, 8K, 16K or 32K of it from $0000, then nothing
|
|
10
|
+
# $8000-$8FFF - screen RAM, repeated (Model), then nothing
|
|
11
|
+
# $9000-$AFFF - two empty ROM sockets
|
|
12
|
+
# $B000-$DFFF - BASIC 4, or BASIC 1 or 2 from $C000
|
|
13
|
+
# $E000-$E7FF - the screen editor ROM
|
|
14
|
+
# $E800-$E8FF - I/O (IO)
|
|
15
|
+
# $E900-$EFFF - nothing
|
|
16
|
+
# $F000-$FFFF - the KERNAL ROM
|
|
17
|
+
#
|
|
18
|
+
# The data lines hold the last byte they carried, so a read from an
|
|
19
|
+
# empty spot finds it, mostly the high byte of the address just
|
|
20
|
+
# fetched, as PET/unconnected shows on a real 3016. Writes to ROM and
|
|
21
|
+
# to empty spots go nowhere. An 8096 banks its 64K board over
|
|
22
|
+
# $8000-$FFFF (Expansion).
|
|
23
|
+
class Bus
|
|
24
|
+
include Addressable
|
|
25
|
+
|
|
26
|
+
# The last byte the CPU moved, which an empty spot reads back.
|
|
27
|
+
class OpenBus
|
|
28
|
+
def initialize(bus)
|
|
29
|
+
@bus = bus
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def peek(_addr) = @bus.data
|
|
33
|
+
def poke(_addr, _value); end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Screen RAM, repeated over the pages it answers in.
|
|
37
|
+
class ScreenRAM
|
|
38
|
+
attr_reader :cells
|
|
39
|
+
|
|
40
|
+
def initialize(size)
|
|
41
|
+
@cells = Array.new(size, 0)
|
|
42
|
+
@mask = size - 1
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def peek(addr) = @cells[addr & @mask]
|
|
46
|
+
|
|
47
|
+
def poke(addr, value)
|
|
48
|
+
@cells[addr & @mask] = value
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# $E800-$E8FF. Each chip answers on one address line, with its
|
|
53
|
+
# registers repeated: A4 selects PIA 1, A5 PIA 2, A6 the VIA and A7
|
|
54
|
+
# the CRTC, where there is one. Where more than one chip answers a
|
|
55
|
+
# read, each pulls the lines it drives low, so the CPU reads the AND
|
|
56
|
+
# of them, and a write reaches them all. Where none answers, as in
|
|
57
|
+
# $E800-$E80F, the CPU reads the last byte on the bus.
|
|
58
|
+
class IO
|
|
59
|
+
def initialize(bus, pia1:, pia2:, via:, crtc:, crtc_fitted:)
|
|
60
|
+
@bus = bus
|
|
61
|
+
@pia1 = pia1
|
|
62
|
+
@pia2 = pia2
|
|
63
|
+
@via = via
|
|
64
|
+
@crtc = crtc
|
|
65
|
+
@lines = crtc_fitted ? 0xf0 : 0x70
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def peek(addr)
|
|
69
|
+
return @bus.data if addr.nobits?(@lines)
|
|
70
|
+
|
|
71
|
+
value = 0xff
|
|
72
|
+
value &= @pia1.peek(addr) if addr.anybits?(0x10)
|
|
73
|
+
value &= @pia2.peek(addr) if addr.anybits?(0x20)
|
|
74
|
+
value &= @via.peek(addr) if addr.anybits?(0x40)
|
|
75
|
+
value &= @crtc.peek(addr) if addr.anybits?(@lines & 0x80)
|
|
76
|
+
value
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def poke(addr, value)
|
|
80
|
+
@pia1.poke(addr, value) if addr.anybits?(0x10)
|
|
81
|
+
@pia2.poke(addr, value) if addr.anybits?(0x20)
|
|
82
|
+
@via.poke(addr, value) if addr.anybits?(0x40)
|
|
83
|
+
@crtc.poke(addr, value) if addr.anybits?(@lines & 0x80)
|
|
84
|
+
@bus.io_written
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# VICE's debug cartridge register at $8BFF, which a write calls the
|
|
89
|
+
# handler with, the exit code the VICE testprogs report. It answers
|
|
90
|
+
# on every model, ahead of the 2001's screen RAM there and the
|
|
91
|
+
# 8096's banks, and every other write to its page passes to what
|
|
92
|
+
# the page holds.
|
|
93
|
+
class DebugRegister
|
|
94
|
+
ADDRESS = 0x8bff
|
|
95
|
+
|
|
96
|
+
attr_accessor :under
|
|
97
|
+
|
|
98
|
+
def initialize(&handler)
|
|
99
|
+
@handler = handler
|
|
100
|
+
@under = nil
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def poke(addr, value)
|
|
104
|
+
return @handler.call(value) if addr == ADDRESS
|
|
105
|
+
|
|
106
|
+
@under.poke(addr, value)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
attr_reader :ram, :screen, :expansion
|
|
111
|
+
|
|
112
|
+
# The last byte the CPU read or wrote.
|
|
113
|
+
attr_reader :data
|
|
114
|
+
|
|
115
|
+
# +model+ is a Model::Profile, +ram_kb+ the RAM below $8000 and
|
|
116
|
+
# +editor+ the editor ROM's file. The CRTC answers only on a board
|
|
117
|
+
# the model fits one on.
|
|
118
|
+
def initialize(model:, ram_kb:, editor:, pia1:, pia2:, via:, crtc:)
|
|
119
|
+
addressable_at(0, length: 2**16)
|
|
120
|
+
@ram = Memory.new([], length: ram_kb * 1024, start: 0)
|
|
121
|
+
@screen = ScreenRAM.new(model.screen_bytes)
|
|
122
|
+
@screen_pages = model.screen_pages
|
|
123
|
+
basic = ROM.read(model.basic)
|
|
124
|
+
@basic_rom = ROM.new(basic, length: basic.length, start: 0xe000 - basic.length)
|
|
125
|
+
@editor_rom = ROM.load(editor, 0xe000)
|
|
126
|
+
@kernal_rom = ROM.load(model.kernal, 0xf000)
|
|
127
|
+
@io = IO.new(self, pia1:, pia2:, via:, crtc:, crtc_fitted: model.crtc?)
|
|
128
|
+
@open_bus = OpenBus.new(self)
|
|
129
|
+
@data = 0
|
|
130
|
+
@read_pages = Array.new(256, @open_bus)
|
|
131
|
+
@write_pages = Array.new(256, @open_bus)
|
|
132
|
+
@expansion = model.expansion ? Expansion.new(self) : nil
|
|
133
|
+
@debug_register = nil
|
|
134
|
+
@io_write_handler = nil
|
|
135
|
+
map_pages
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Calls the block after each write to the I/O chips, which may have
|
|
139
|
+
# moved the lines their ports drive.
|
|
140
|
+
def on_io_write(&handler)
|
|
141
|
+
@io_write_handler = handler
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def io_written
|
|
145
|
+
@io_write_handler&.call
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# The RES line reaches the 8096's control register.
|
|
149
|
+
def reset!
|
|
150
|
+
@expansion&.reset!
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def power_on!
|
|
154
|
+
@ram.clear!
|
|
155
|
+
@screen.cells.fill(0)
|
|
156
|
+
@expansion&.power_on!
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def peek(addr)
|
|
160
|
+
@data = @read_pages[addr >> 8].peek(addr)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def poke(addr, value)
|
|
164
|
+
@data = value
|
|
165
|
+
@write_pages[addr >> 8].poke(addr, value)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Whether the CPU reads the KERNAL ROM at $F000-$FFFF, which an 8096's
|
|
169
|
+
# board covers while its control register turns it on.
|
|
170
|
+
def kernal_mapped? = @expansion.nil? || @expansion.control.nobits?(0x80)
|
|
171
|
+
|
|
172
|
+
# Calls the block with each byte written to $8BFF (DebugRegister).
|
|
173
|
+
def install_debug_register(&)
|
|
174
|
+
@debug_register = DebugRegister.new(&)
|
|
175
|
+
map_pages
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Lays the pages out again, for the 8096's control register.
|
|
179
|
+
def map_pages
|
|
180
|
+
map(@open_bus, 0x00, 0x100)
|
|
181
|
+
map(@ram, 0x00, @ram.length >> 8)
|
|
182
|
+
map(@screen, 0x80, @screen_pages)
|
|
183
|
+
@read_pages.fill(@basic_rom, @basic_rom.start >> 8, @basic_rom.length >> 8)
|
|
184
|
+
@read_pages.fill(@editor_rom, 0xe0, 0x08)
|
|
185
|
+
map(@io, 0xe8, 0x01)
|
|
186
|
+
@read_pages.fill(@kernal_rom, 0xf0, 0x10)
|
|
187
|
+
@expansion&.map(@read_pages, @write_pages)
|
|
188
|
+
map_debug_register if @debug_register
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
private
|
|
192
|
+
|
|
193
|
+
def map_debug_register
|
|
194
|
+
@debug_register.under = @write_pages[0x8b]
|
|
195
|
+
@write_pages[0x8b] = @debug_register
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def map(chip, first, count)
|
|
199
|
+
@read_pages.fill(chip, first, count)
|
|
200
|
+
@write_pages.fill(chip, first, count)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|