badline 0.14.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +86 -0
- data/CLAUDE.md +22 -5
- data/README.md +101 -11
- data/doc/media.md +19 -0
- data/doc/pinned-behaviour.md +145 -0
- data/doc/snapshot-format.md +244 -46
- data/doc/snapshots.md +18 -1
- data/exe/badline-ruby +1 -1
- data/lib/badline/audio/bare_player.rb +4 -8
- data/lib/badline/audio/machine_player.rb +4 -26
- data/lib/badline/c128/banks.rb +1 -3
- data/lib/badline/c128/bus.rb +19 -1
- data/lib/badline/c128/mmu_pages.rb +7 -0
- data/lib/badline/c64/bus/extra_sids.rb +80 -0
- data/lib/badline/c64/bus.rb +1 -0
- data/lib/badline/c64/cartridge/action_replay.rb +6 -2
- data/lib/badline/c64/cartridge/comal80.rb +1 -0
- data/lib/badline/c64/cartridge/dinamic.rb +3 -1
- data/lib/badline/c64/cartridge/final_cartridge3.rb +1 -0
- data/lib/badline/c64/cartridge/fun_play.rb +2 -1
- data/lib/badline/c64/cartridge/game_system.rb +2 -1
- data/lib/badline/c64/cartridge/keyed_state.rb +155 -34
- data/lib/badline/c64/cartridge/magic_desk.rb +2 -1
- data/lib/badline/c64/cartridge/ocean.rb +1 -0
- data/lib/badline/c64/cartridge/pagefox.rb +1 -0
- data/lib/badline/c64/cartridge/rgcd.rb +2 -1
- data/lib/badline/c64/cartridge/super_games.rb +3 -2
- data/lib/badline/c64/cartridge/zaxxon.rb +1 -0
- data/lib/badline/c64/cartridge.rb +3 -3
- data/lib/badline/c64/keyed_modules.rb +6 -2
- data/lib/badline/c64/true_drives.rb +13 -1
- data/lib/badline/c64.rb +6 -0
- data/lib/badline/chips/ted/beam.rb +13 -14
- data/lib/badline/chips/ted/fetches.rb +199 -0
- data/lib/badline/chips/ted/keyed_state.rb +156 -0
- data/lib/badline/chips/ted/modes.rb +134 -0
- data/lib/badline/chips/ted/palette.rb +43 -0
- data/lib/badline/chips/ted/picture.rb +182 -0
- data/lib/badline/chips/ted/sound/output.rb +86 -0
- data/lib/badline/chips/ted/sound.rb +284 -0
- data/lib/badline/chips/ted.rb +53 -6
- data/lib/badline/chips/tia.rb +28 -0
- data/lib/badline/chips/vic/register_log.rb +4 -1
- data/lib/badline/drives/datasette/keyed_state.rb +14 -4
- data/lib/badline/drives/datasette.rb +22 -5
- data/lib/badline/drives/engine/core.rb +6 -14
- data/lib/badline/drives/engine/idle.rb +42 -4
- data/lib/badline/drives/engine/idle_cpu.rb +18 -0
- data/lib/badline/drives/engine/serial_connection.rb +25 -0
- data/lib/badline/drives/engine/sleep.rb +1 -0
- data/lib/badline/drives/engine.rb +1 -0
- data/lib/badline/drives/model1541/byte_ready.rb +19 -3
- data/lib/badline/drives/model1541/mechanism.rb +10 -1
- data/lib/badline/drives/model1541.rb +1 -0
- data/lib/badline/drives/model1551/bus.rb +138 -0
- data/lib/badline/drives/model1551/cable.rb +104 -0
- data/lib/badline/drives/model1551/cpu.rb +14 -0
- data/lib/badline/drives/model1551/keyed_state.rb +65 -0
- data/lib/badline/drives/model1551/port.rb +74 -0
- data/lib/badline/drives/model1551/tia.rb +92 -0
- data/lib/badline/drives/model1551.rb +170 -0
- data/lib/badline/drives/model1571.rb +1 -0
- data/lib/badline/drives/model1581.rb +1 -0
- data/lib/badline/frontend/boot.rb +23 -2
- data/lib/badline/frontend/controls.rb +7 -3
- data/lib/badline/frontend/keys.rb +14 -2
- data/lib/badline/frontend/media_slots.rb +11 -1
- data/lib/badline/frontend/menu/menu_dialogs.rb +14 -5
- data/lib/badline/frontend/menu/menu_media.rb +102 -14
- data/lib/badline/frontend/menu/menu_pages.rb +16 -11
- data/lib/badline/frontend/menu/menu_ports.rb +13 -1
- data/lib/badline/frontend/player/player_controller.rb +11 -4
- data/lib/badline/frontend/screen.rb +33 -12
- data/lib/badline/frontend/screens.rb +2 -1
- data/lib/badline/frontend/sound.rb +4 -4
- data/lib/badline/frontend/timeline.rb +19 -6
- data/lib/badline/machine/settling.rb +1 -1
- data/lib/badline/machine.rb +30 -6
- data/lib/badline/media/plus4_media.rb +68 -0
- data/lib/badline/media/program.rb +20 -6
- data/lib/badline/media/true_drive.rb +7 -5
- data/lib/badline/media.rb +33 -12
- data/lib/badline/options/family.rb +1 -0
- data/lib/badline/options/help.rb +32 -6
- data/lib/badline/options/option.rb +2 -1
- data/lib/badline/options/table.rb +29 -4
- data/lib/badline/options/validation.rb +66 -4
- data/lib/badline/options.rb +71 -11
- data/lib/badline/plus4/acia/frames.rb +55 -0
- data/lib/badline/plus4/acia.rb +276 -16
- data/lib/badline/plus4/bus.rb +245 -46
- data/lib/badline/plus4/cassette_sense.rb +21 -0
- data/lib/badline/plus4/keyboard_port.rb +35 -10
- data/lib/badline/plus4/keyed_modules.rb +116 -0
- data/lib/badline/plus4/keyed_parts.rb +172 -0
- data/lib/badline/plus4/keyed_state.rb +44 -0
- data/lib/badline/plus4/ram_expansion.rb +145 -0
- data/lib/badline/plus4/sid_card.rb +95 -0
- data/lib/badline/plus4/sound_mix.rb +51 -0
- data/lib/badline/plus4/tcbm_drive.rb +120 -0
- data/lib/badline/plus4/user_port.rb +11 -2
- data/lib/badline/plus4.rb +184 -26
- data/lib/badline/roms/README +9 -0
- data/lib/badline/roms/dos1551.rom +0 -0
- data/lib/badline/snapshot/container.rb +2 -0
- data/lib/badline/snapshot/field_doc/markdown.rb +7 -6
- data/lib/badline/snapshot/field_doc.rb +48 -7
- data/lib/badline/snapshot/image.rb +12 -3
- data/lib/badline/snapshot/plus4_setup.rb +81 -0
- data/lib/badline/snapshot.rb +9 -4
- data/lib/badline/storage/tap.rb +21 -12
- data/lib/badline/support/checkpoint.rb +3 -2
- data/lib/badline/support/plus4_checkpoint.rb +42 -0
- data/lib/badline/version.rb +1 -1
- metadata +29 -1
|
@@ -2,26 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
module Media
|
|
5
|
-
# A program loading into a C64 or a
|
|
6
|
-
# (Media.attach). The VIC-20 has its own (Vic20Basic).
|
|
5
|
+
# A program loading into a C64, a C128 or a 264 series machine once it
|
|
6
|
+
# has booted (Media.attach). The VIC-20 has its own (Vic20Basic).
|
|
7
7
|
module Program
|
|
8
|
+
# Where BASIC 3.5 starts on the 264 series.
|
|
9
|
+
PLUS4_BASIC_START = 0x1001
|
|
10
|
+
|
|
8
11
|
class << self
|
|
9
12
|
# Loads the program where it says, and RUNs one that loads at the
|
|
10
13
|
# start of BASIC.
|
|
11
14
|
def load(computer, data, autostart:)
|
|
12
15
|
load_addr = computer.load_prg(data)
|
|
16
|
+
start, text_end, load_end = basic(computer)
|
|
13
17
|
# Run only makes sense for programs at BASIC start, or a byte ahead
|
|
14
|
-
# of it, where BASIC keeps the zero before its first line.
|
|
15
|
-
# starts at $1C01 and keeps the end of its text at $1210.
|
|
16
|
-
start, text_end = computer.family == :c128 && computer.mode == :c128 ? [0x1c01, 0x1210] : [BASIC_START, 0x2d]
|
|
18
|
+
# of it, where BASIC keeps the zero before its first line.
|
|
17
19
|
return unless autostart && (load_addr == start || load_addr == start - 1)
|
|
18
20
|
|
|
19
21
|
# The end of the program, where BASIC's variables start, and where
|
|
20
22
|
# the KERNAL's LOAD leaves its end address.
|
|
21
23
|
end_addr = load_addr + data.length - 2
|
|
22
|
-
[text_end,
|
|
24
|
+
[text_end, load_end].each { |pointer| computer.ram.write(pointer, [end_addr & 0xff, end_addr >> 8]) }
|
|
23
25
|
computer.type_text("run\r")
|
|
24
26
|
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# Where BASIC starts, the pointer to the end of its text and the
|
|
31
|
+
# KERNAL's end address (EAL). BASIC 7.0 starts at $1C01 and keeps
|
|
32
|
+
# the end of its text at $1210, and the 264 KERNAL keeps EAL at $9D.
|
|
33
|
+
def basic(computer)
|
|
34
|
+
return [PLUS4_BASIC_START, 0x2d, KernalTrap::PLUS4_ZERO_PAGE.end_address] if computer.family == :plus4
|
|
35
|
+
return [0x1c01, 0x1210, 0xae] if computer.family == :c128 && computer.mode == :c128
|
|
36
|
+
|
|
37
|
+
[BASIC_START, 0x2d, 0xae]
|
|
38
|
+
end
|
|
25
39
|
end
|
|
26
40
|
end
|
|
27
41
|
end
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
module Badline
|
|
4
4
|
module Media
|
|
5
5
|
# Device 8 as a true drive instead of the KERNAL traps: a 1541, or on
|
|
6
|
-
# the C128 its 1571, or
|
|
7
|
-
#
|
|
8
|
-
#
|
|
6
|
+
# the C128 its 1571, or on the Plus/4 a 1551 if it asks for one, or a
|
|
7
|
+
# 1581. Once a true drive is plugged in as device 8, disk media go into
|
|
8
|
+
# it rather than into the traps, so a LOAD runs the DOS and every byte
|
|
9
|
+
# crosses the serial bus or TCBM. A .g64 holds the
|
|
9
10
|
# disk's raw GCR, which only a true drive reads, so it plugs one in
|
|
10
11
|
# when there's none yet. The 1541 reads only .d64 and .g64 images, the
|
|
11
12
|
# 1571 .d71 and .g71 as well, and the 1581 only .d81, so a .d81 swaps
|
|
@@ -52,9 +53,10 @@ module Badline
|
|
|
52
53
|
raise Error, "#{path} is not a #{types.join(', ')} or .d81 image, which the #{name} and the 1581 read"
|
|
53
54
|
end
|
|
54
55
|
|
|
55
|
-
|
|
56
|
+
drive ||= computer.plug_true_drive
|
|
57
|
+
drive.insert_image(path, read_only:)
|
|
56
58
|
computer.unmount
|
|
57
|
-
"Inserted #{path} in the #{
|
|
59
|
+
"Inserted #{path} in the #{drive.model_name} as device 8"
|
|
58
60
|
end
|
|
59
61
|
|
|
60
62
|
private
|
data/lib/badline/media.rb
CHANGED
|
@@ -10,6 +10,7 @@ require "badline/media/disk_list"
|
|
|
10
10
|
require "badline/media/vic20_basic"
|
|
11
11
|
require "badline/media/vic20_cartridge"
|
|
12
12
|
require "badline/media/vic20_media"
|
|
13
|
+
require "badline/media/plus4_media"
|
|
13
14
|
require "badline/media/tune"
|
|
14
15
|
require "badline/media/program"
|
|
15
16
|
|
|
@@ -44,13 +45,14 @@ module Badline
|
|
|
44
45
|
# An .m3u or .vfl list of disks attaches the first disk it lists
|
|
45
46
|
# (DiskList). Every machine takes each kind of medium the same way,
|
|
46
47
|
# except that the VIC-20 (Vic20Media) has cartridges, programs and a
|
|
47
|
-
# disk autostart of its own, and takes no .sid tunes
|
|
48
|
-
#
|
|
48
|
+
# disk autostart of its own, and takes no .sid tunes, and the 264
|
|
49
|
+
# series loads programs at the start of its BASIC (Program), takes
|
|
50
|
+
# .crt cartridges and raw .bin ROM images in its two cartridges'
|
|
51
|
+
# slots (Plus4Media), and no .sid tunes either. The
|
|
52
|
+
# C128 in C64 mode takes a C64's.
|
|
49
53
|
def attach(computer, path, autostart: true, subtune: nil, **)
|
|
50
54
|
path = DiskList.disk(path)
|
|
51
|
-
|
|
52
|
-
raise ArgumentError, "#{path} doesn't go in a VIC-20"
|
|
53
|
-
end
|
|
55
|
+
refuse_unsupported(computer, path)
|
|
54
56
|
|
|
55
57
|
BootDisk.attach(computer, path, **) || attach_kind(computer, path, autostart:, subtune:, **)
|
|
56
58
|
end
|
|
@@ -89,10 +91,12 @@ module Badline
|
|
|
89
91
|
# What the medium at `path` is to `computer`, which attach goes by:
|
|
90
92
|
# :true_drive for a disk that goes in a true drive (TrueDrive),
|
|
91
93
|
# :directory, :storage for a disk image or a .t64 that the traps
|
|
92
|
-
# mount,
|
|
94
|
+
# mount, :cartridge for a raw ROM image on the 264 series
|
|
95
|
+
# (Plus4Media), and otherwise its kind by its extension (Extensions).
|
|
93
96
|
def kind(computer, path)
|
|
94
97
|
return :true_drive if TrueDrive.takes?(computer, path)
|
|
95
98
|
return :directory if File.directory?(path)
|
|
99
|
+
return :cartridge if computer.family == :plus4 && Plus4Media.rom_image?(path)
|
|
96
100
|
|
|
97
101
|
kind = Extensions.kind(path)
|
|
98
102
|
%i[disk archive].include?(kind) ? :storage : kind
|
|
@@ -116,6 +120,16 @@ module Badline
|
|
|
116
120
|
|
|
117
121
|
private
|
|
118
122
|
|
|
123
|
+
# The VIC-20 and the 264 series have no SID to play a .sid tune on.
|
|
124
|
+
def refuse_unsupported(computer, path)
|
|
125
|
+
return unless Vic20Media::UNSUPPORTED.include?(File.extname(path).downcase)
|
|
126
|
+
|
|
127
|
+
case computer.family
|
|
128
|
+
when :vic20 then raise ArgumentError, "#{path} doesn't go in a VIC-20"
|
|
129
|
+
when :plus4 then raise ArgumentError, "#{path} doesn't go in a Plus/4"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
119
133
|
def attach_kind(computer, path, autostart:, subtune:, **options)
|
|
120
134
|
disk = options.fetch(:disk, {})
|
|
121
135
|
case kind(computer, path)
|
|
@@ -151,25 +165,32 @@ module Badline
|
|
|
151
165
|
computer.family == :vic20 ? Vic20Media.disk_autostart(yield) : AUTOSTART
|
|
152
166
|
end
|
|
153
167
|
|
|
154
|
-
# A VIC-20 cartridge is its ROM chips.
|
|
168
|
+
# A VIC-20 or 264 series cartridge is its ROM chips. On the 264, the
|
|
169
|
+
# options' `slot` names the slot a raw ROM image goes in
|
|
170
|
+
# (Plus4Media.cartridge_chips).
|
|
155
171
|
def attach_cartridge(computer, path, options)
|
|
156
|
-
|
|
157
|
-
|
|
172
|
+
case computer.family
|
|
173
|
+
when :vic20 then computer.attach_cartridge(Storage::CRTFile.new(path, machine: :vic20).chips)
|
|
174
|
+
when :plus4 then computer.attach_cartridge(Plus4Media.cartridge_chips(path, slot: options.fetch(:slot, nil)))
|
|
158
175
|
else
|
|
159
176
|
computer.attach_cartridge(C64::Cartridge.from_file(path, **options))
|
|
160
177
|
end
|
|
161
178
|
"Attached cartridge #{path}"
|
|
162
179
|
end
|
|
163
180
|
|
|
164
|
-
# A VIC-20 says when the tape was made for another machine
|
|
165
|
-
# (Vic20Media.tape_message).
|
|
181
|
+
# A VIC-20 or a 264 says when the tape was made for another machine
|
|
182
|
+
# (Vic20Media.tape_message, Plus4Media.tape_message).
|
|
166
183
|
def attach_tape(computer, path, autostart:)
|
|
167
184
|
tape = Storage::TAP.new(path)
|
|
168
185
|
computer.datasette.insert(tape)
|
|
169
186
|
computer.datasette.play!
|
|
170
187
|
computer.type_text(TAPE_AUTOSTART) if autostart
|
|
171
188
|
message = "Inserted #{path} in the datasette"
|
|
172
|
-
computer.family
|
|
189
|
+
case computer.family
|
|
190
|
+
when :vic20 then Vic20Media.tape_message(tape, message)
|
|
191
|
+
when :plus4 then Plus4Media.tape_message(tape, message)
|
|
192
|
+
else message
|
|
193
|
+
end
|
|
173
194
|
end
|
|
174
195
|
|
|
175
196
|
# A VIC-20 has programs of its own (Vic20Media.attach_program).
|
data/lib/badline/options/help.rb
CHANGED
|
@@ -16,6 +16,16 @@ module Badline
|
|
|
16
16
|
filter cycle by cycle, which is exact but takes about twice as long.
|
|
17
17
|
TEXT
|
|
18
18
|
|
|
19
|
+
# The article for +name+: "an" where it starts with a vowel sound. A
|
|
20
|
+
# name of letters with a vowel among them is read as a word, and any
|
|
21
|
+
# other letter by letter, so .sid and .tap take "a", and .m3u and .lnx
|
|
22
|
+
# "an".
|
|
23
|
+
def self.article(name)
|
|
24
|
+
word = name.delete_prefix(".").downcase
|
|
25
|
+
vowels = word.match?(/\A[a-z]*[aeiou][a-z]*\z/) ? "aeiou" : "aefhilmnorsx8"
|
|
26
|
+
vowels.include?(word[0]) ? "an" : "a"
|
|
27
|
+
end
|
|
28
|
+
|
|
19
29
|
# What --help prints: the usage, then the options this build takes,
|
|
20
30
|
# section by section, laid out as OptionParser did.
|
|
21
31
|
def help
|
|
@@ -38,14 +48,19 @@ module Badline
|
|
|
38
48
|
# The media the window opens, by their extensions (Media::Extensions),
|
|
39
49
|
# wrapped as the banner is.
|
|
40
50
|
def media_kinds
|
|
41
|
-
text = "Media can be
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"cartridge,
|
|
51
|
+
text = "Media can be #{listed(Media::Extensions::PROGRAMS)} program, #{listed(%i[disk])} disk image, " \
|
|
52
|
+
"#{listed(%i[gcr])} disk image for a true drive, #{listed(%i[disk_list])} list of disk images, " \
|
|
53
|
+
"#{listed(%i[archive])} tape archive, #{listed(%i[tape])} tape, #{listed(%i[cartridge])} " \
|
|
54
|
+
"cartridge, #{listed(%i[tune])} tune, #{listed(%w[.vsf])} snapshot, or a directory to mount as device 8."
|
|
45
55
|
wrap(text)
|
|
46
56
|
end
|
|
47
57
|
|
|
48
|
-
|
|
58
|
+
# The extensions of the kinds, or the extensions themselves, after the
|
|
59
|
+
# article the first takes.
|
|
60
|
+
def listed(kinds)
|
|
61
|
+
extensions = kinds.first.is_a?(Symbol) ? Media::Extensions.of(kinds) : kinds
|
|
62
|
+
"#{Options.article(extensions.first)} #{extensions.join('/')}"
|
|
63
|
+
end
|
|
49
64
|
|
|
50
65
|
# The words of `text` in lines of at most 70 characters.
|
|
51
66
|
def wrap(text)
|
|
@@ -68,6 +83,7 @@ module Badline
|
|
|
68
83
|
Usage: #{@program} [c64] [options] [media]
|
|
69
84
|
#{@program} vic20 [options] [media]
|
|
70
85
|
#{@program} c128 [options] [media]
|
|
86
|
+
#{@program} plus4 [options] [media]
|
|
71
87
|
#{@program} --headless [options] tune.sid
|
|
72
88
|
#{@program} [options] tune.sid --audio-out FILE
|
|
73
89
|
#{@program} sid [options] FILE|DIR...
|
|
@@ -76,6 +92,7 @@ module Badline
|
|
|
76
92
|
c64 Run a C64, the one --model names (the default)
|
|
77
93
|
vic20 Run a PAL VIC-20, with the RAM --ram names
|
|
78
94
|
c128 Run a C128 in C128 mode, the one --model names
|
|
95
|
+
plus4 Run a Plus/4 or a C16, the one --model names
|
|
79
96
|
sid Play .sid tunes and directories of them
|
|
80
97
|
|
|
81
98
|
#{media_kinds}
|
|
@@ -91,6 +108,14 @@ module Badline
|
|
|
91
108
|
tune opens in the SID player, as `sid` plays it, unless an option
|
|
92
109
|
of the emulator's window asks for the machine.
|
|
93
110
|
|
|
111
|
+
The Plus/4 and the C16 take programs, disks, tapes, and cartridges as
|
|
112
|
+
.crt files or raw .bin ROM images. A .bin goes in cartridge 1, its
|
|
113
|
+
low slot for up to 16K and both slots for 32K, and --c1lo, --c1hi,
|
|
114
|
+
--c2lo and --c2hi put one in the slot they name. Their --true-drive
|
|
115
|
+
makes device 8 an emulated 1551, on the parallel TCBM cable, and
|
|
116
|
+
--drive-type 1541 a 1541 on the serial bus instead. There Escape is
|
|
117
|
+
ESC, Page Down RUN/STOP and F4 HELP.
|
|
118
|
+
|
|
94
119
|
--headless plays a .sid tune on the host's audio device without the
|
|
95
120
|
window, and --audio-out renders it to 16-bit PCM instead. The
|
|
96
121
|
container follows the file's extension, .wav or .aiff. Played on a
|
|
@@ -111,7 +136,8 @@ module Badline
|
|
|
111
136
|
--at and --script run events once a number of frames has run, as
|
|
112
137
|
in --at 3500:key=space or --at 250,500:screenshot=shot%05d.bmp.
|
|
113
138
|
key=NAME presses a key (space, return, a, f1, run_stop, restore, and
|
|
114
|
-
on the C128 esc, help, keypad1 and the rest
|
|
139
|
+
on the C128 esc, help, keypad1 and the rest, and on the Plus/4
|
|
140
|
+
esc, help and f1 to f3) or a joystick's
|
|
115
141
|
direction or fire (joy1-up, joy2-fire) for #{Event::HOLD} frames, and
|
|
116
142
|
pushes the C128's caps_lock or forty_eighty, which lock. type=TEXT
|
|
117
143
|
types it, with \\n for RETURN. insert=FILE swaps in a disk, a list's
|
|
@@ -4,7 +4,8 @@ module Badline
|
|
|
4
4
|
class Options
|
|
5
5
|
# One option of the table: its flags and value as the help shows them
|
|
6
6
|
# (`-s, --subtune N`), what it does, the help section it's listed in, the
|
|
7
|
-
# mode it needs (:window, :headless
|
|
7
|
+
# mode it needs (:window, :headless, :either, or :any_window for the
|
|
8
|
+
# emulator's window or the SID player's), and the build that
|
|
8
9
|
# takes it (:native, :ruby or :both).
|
|
9
10
|
class Option
|
|
10
11
|
attr_reader :usage, :name, :short, :value, :text, :section, :needs, :build
|
|
@@ -14,6 +14,13 @@ module Badline
|
|
|
14
14
|
# The C128's models, as C128::Model names them.
|
|
15
15
|
C128_MODELS = %w[c128 c128ntsc c128dcr c128dcrntsc].freeze
|
|
16
16
|
|
|
17
|
+
# The 264 series' models, as Plus4::Model names them, with the C116 as
|
|
18
|
+
# another name for the C16.
|
|
19
|
+
PLUS4_MODELS = %w[plus4 plus4ntsc c16 c16ntsc c116].freeze
|
|
20
|
+
|
|
21
|
+
# The true drives --drive-type names for the Plus/4.
|
|
22
|
+
PLUS4_DRIVE_TYPES = %w[1541 1551].freeze
|
|
23
|
+
|
|
17
24
|
# The VIC-20 RAM expansions --ram takes, in the order the help lists
|
|
18
25
|
# them.
|
|
19
26
|
RAM_NAMES = %w[unexpanded 3k 8k 16k 24k 32k all].freeze
|
|
@@ -35,17 +42,35 @@ module Badline
|
|
|
35
42
|
Option.new("--sound", "Play the SID through the host's audio device (F10 mutes)",
|
|
36
43
|
section: "Window options", needs: :window),
|
|
37
44
|
Option.new("--no-sound", "Don't play the SID", section: "Window options", needs: :window, build: :native),
|
|
38
|
-
Option.new("--true-drive", "Put a true 1541 on device 8 instead of the KERNAL traps, a 1571 on the C128"
|
|
45
|
+
Option.new("--true-drive", "Put a true 1541 on device 8 instead of the KERNAL traps, a 1571 on the C128, " \
|
|
46
|
+
"a 1551 on the Plus/4",
|
|
47
|
+
section: "Window options", needs: :window),
|
|
48
|
+
Option.new("--drive-type MODEL", "The Plus/4's true drive: 1541 or 1551 (default: 1551); implies --true-drive",
|
|
39
49
|
section: "Window options", needs: :window),
|
|
40
50
|
Option.new("--reu SIZE", "Plug in an REU of SIZE K: 128, 256, 512 (a 1750) or up to 16384",
|
|
41
51
|
section: "Window options", needs: :window),
|
|
42
52
|
Option.new("--model NAME", "C64 to run: #{MODELS.join(', ')} (default: #{MODELS.first}); VIC-20: pal; " \
|
|
43
|
-
"C128: #{C128_MODELS.join(', ')}",
|
|
53
|
+
"C128: #{C128_MODELS.join(', ')}; Plus/4: #{PLUS4_MODELS.join(', ')}",
|
|
44
54
|
section: "Window options", needs: :window),
|
|
45
55
|
Option.new("--ntsc", "Run an NTSC C64, as --model ntsc does", section: "Window options", needs: :window),
|
|
46
56
|
Option.new("--c64", "Start the C128 in C64 mode, holding C= at power-on",
|
|
47
57
|
section: "Window options", needs: :window),
|
|
48
|
-
Option.new("--ram SIZE", "VIC-20 RAM expansion: #{RAM_NAMES.join(', ')} (default: unexpanded)"
|
|
58
|
+
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)",
|
|
60
|
+
section: "Window options", needs: :window),
|
|
61
|
+
Option.new("--c1lo FILE", "Put a .bin ROM image in the Plus/4's cartridge 1, low slot ($8000)",
|
|
62
|
+
section: "Window options", needs: :window),
|
|
63
|
+
Option.new("--c1hi FILE", "Put a .bin ROM image in cartridge 1's high slot ($C000)",
|
|
64
|
+
section: "Window options", needs: :window),
|
|
65
|
+
Option.new("--c2lo FILE", "Put a .bin ROM image in cartridge 2's low slot ($8000)",
|
|
66
|
+
section: "Window options", needs: :window),
|
|
67
|
+
Option.new("--c2hi FILE", "Put a .bin ROM image in cartridge 2's high slot ($C000)",
|
|
68
|
+
section: "Window options", needs: :window),
|
|
69
|
+
Option.new("--sid-card MODEL", "Fit the Plus/4 or C16 with a SID card at $FD40: 6581 or 8580",
|
|
70
|
+
section: "Window options", needs: :window),
|
|
71
|
+
Option.new("--sid-card-address ADDRESS", "Put the SID card at fd40 (the default) or fe80",
|
|
72
|
+
section: "Window options", needs: :window),
|
|
73
|
+
Option.new("--ram-expansion NAME", "Fit the Plus/4 or C16 with a RAM expansion: hannes256, hannes1m or csory256",
|
|
49
74
|
section: "Window options", needs: :window),
|
|
50
75
|
Option.new("--no-vsync", "Pace frames by the timer or the sound instead of the display",
|
|
51
76
|
section: "Window options", needs: :window),
|
|
@@ -68,7 +93,7 @@ module Badline
|
|
|
68
93
|
section: "Options without the window", needs: :headless),
|
|
69
94
|
Option.new("--no-tui", "Play without the interactive display, even on a terminal",
|
|
70
95
|
section: "Options without the window", needs: :headless),
|
|
71
|
-
Option.new("--frames N", "Quit after N frames", section: "Testing options", needs: :
|
|
96
|
+
Option.new("--frames N", "Quit after N frames", section: "Testing options", needs: :any_window),
|
|
72
97
|
Option.new("--unpaced", "Run as fast as it can, without vsync or pacing",
|
|
73
98
|
section: "Testing options", needs: :window),
|
|
74
99
|
Option.new("--screenshot FILE", "Save the last frame as a .bmp",
|
|
@@ -8,6 +8,18 @@ module Badline
|
|
|
8
8
|
module Validation
|
|
9
9
|
private
|
|
10
10
|
|
|
11
|
+
def sid_card_for(value)
|
|
12
|
+
raise Error, "invalid argument: --sid-card #{value}" unless SID_MODELS.key?(value)
|
|
13
|
+
|
|
14
|
+
SID_MODELS[value]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def sid_card_address_for(value)
|
|
18
|
+
raise Error, "invalid argument: --sid-card-address #{value}" unless SID_CARD_ADDRESSES.key?(value.downcase)
|
|
19
|
+
|
|
20
|
+
SID_CARD_ADDRESSES[value.downcase]
|
|
21
|
+
end
|
|
22
|
+
|
|
11
23
|
def sid_model_for(value)
|
|
12
24
|
return if value == "auto"
|
|
13
25
|
raise Error, "invalid argument: --sid #{value}" unless SID_MODELS.key?(value)
|
|
@@ -21,6 +33,24 @@ module Badline
|
|
|
21
33
|
RAM_CONFIGURATIONS[value]
|
|
22
34
|
end
|
|
23
35
|
|
|
36
|
+
def plus4_ram(value)
|
|
37
|
+
raise Error, "invalid argument: --ram #{value}" unless PLUS4_RAM.key?(value)
|
|
38
|
+
|
|
39
|
+
PLUS4_RAM[value]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def plus4_ram_expansion(value)
|
|
43
|
+
raise Error, "invalid argument: --ram-expansion #{value}" unless PLUS4_RAM_EXPANSIONS.key?(value)
|
|
44
|
+
|
|
45
|
+
PLUS4_RAM_EXPANSIONS[value]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def drive_type_for(value)
|
|
49
|
+
raise Error, "invalid argument: --drive-type #{value}" unless PLUS4_DRIVE_TYPES.include?(value)
|
|
50
|
+
|
|
51
|
+
value
|
|
52
|
+
end
|
|
53
|
+
|
|
24
54
|
def reu_size(value)
|
|
25
55
|
raise Error, "invalid argument: --reu #{value}" unless REU_SIZES.include?(value)
|
|
26
56
|
|
|
@@ -41,6 +71,8 @@ module Badline
|
|
|
41
71
|
|
|
42
72
|
def validate
|
|
43
73
|
validate_family
|
|
74
|
+
raise Error, "--drive-type needs plus4" unless @drive_type.nil? || @family == :plus4
|
|
75
|
+
|
|
44
76
|
validate_mode
|
|
45
77
|
validate_models
|
|
46
78
|
validate_numbers
|
|
@@ -50,6 +82,7 @@ module Badline
|
|
|
50
82
|
def validate_mode
|
|
51
83
|
if headless?
|
|
52
84
|
raise Error, "#{@window_only.first} needs the window" unless @window_only.empty?
|
|
85
|
+
raise Error, "#{@any_window_only.first} needs the window" unless player_window? || @any_window_only.empty?
|
|
53
86
|
raise Error, "no tune given" if tune_paths.empty? && !player_window?
|
|
54
87
|
|
|
55
88
|
tune_paths.each { |path| validate_tune(path) }
|
|
@@ -59,13 +92,12 @@ module Badline
|
|
|
59
92
|
end
|
|
60
93
|
|
|
61
94
|
def validate_family
|
|
62
|
-
raise Error, "the Plus/4 isn't emulated yet" if @family == :plus4
|
|
63
|
-
|
|
64
95
|
if @family == :vic20
|
|
65
96
|
validate_vic20
|
|
66
97
|
elsif !@ram.nil?
|
|
67
|
-
raise Error, "--ram needs vic20"
|
|
98
|
+
raise Error, "--ram needs vic20 or plus4"
|
|
68
99
|
end
|
|
100
|
+
@family == :plus4 ? validate_plus4 : refuse_plus4_options
|
|
69
101
|
if @family == :c128
|
|
70
102
|
validate_c128
|
|
71
103
|
elsif @c64_mode
|
|
@@ -91,6 +123,36 @@ module Badline
|
|
|
91
123
|
raise Error, "the VIC-20 needs the window" if headless?
|
|
92
124
|
end
|
|
93
125
|
|
|
126
|
+
# Only the Plus/4 has cartridge slots for raw ROM images and takes a
|
|
127
|
+
# SID card and a RAM expansion.
|
|
128
|
+
def refuse_plus4_options
|
|
129
|
+
raise Error, "--#{@cartridges.keys.first} needs plus4" unless @cartridges.empty?
|
|
130
|
+
raise Error, "--sid-card needs plus4" unless @sid_card.nil?
|
|
131
|
+
raise Error, "--sid-card-address needs plus4" unless @sid_card_address.nil?
|
|
132
|
+
raise Error, "--ram-expansion needs plus4" unless @ram_expansion.nil?
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# The Plus/4 runs in the window, without the C64's SID and REU but
|
|
136
|
+
# with a SID card if asked, its NTSC models have names of their own,
|
|
137
|
+
# and its cartridge slots take raw ROM images.
|
|
138
|
+
def validate_plus4
|
|
139
|
+
raise Error, "--ntsc needs the C64; the NTSC Plus/4 is --model plus4ntsc" if @models.include?("ntsc")
|
|
140
|
+
raise Error, "--sid needs the C64; the Plus/4's is --sid-card" unless @sid_model.nil?
|
|
141
|
+
raise Error, "--reu needs the C64" unless @reu.nil?
|
|
142
|
+
raise Error, "the Plus/4 needs the window" if headless?
|
|
143
|
+
|
|
144
|
+
validate_plus4_expansions
|
|
145
|
+
image = @cartridges.values.find { |path| !File.extname(path).casecmp?(".bin") }
|
|
146
|
+
raise Error, "not a .bin ROM image: #{image}" unless image.nil?
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# A RAM expansion goes over 64K, and a SID card's address needs a
|
|
150
|
+
# card.
|
|
151
|
+
def validate_plus4_expansions
|
|
152
|
+
raise Error, "--ram-expansion needs 64k" unless @ram_expansion.nil? || [nil, 64].include?(@ram_kb)
|
|
153
|
+
raise Error, "--sid-card-address needs --sid-card" unless @sid_card_address.nil? || @sid_card
|
|
154
|
+
end
|
|
155
|
+
|
|
94
156
|
# `sid` takes directories as well as tunes.
|
|
95
157
|
def validate_tune(path)
|
|
96
158
|
return if !File.exist?(path) || File.extname(path).casecmp?(".sid")
|
|
@@ -120,7 +182,7 @@ module Badline
|
|
|
120
182
|
end
|
|
121
183
|
|
|
122
184
|
def validate_paths
|
|
123
|
-
missing = tune_paths.find { |path| !File.exist?(path) }
|
|
185
|
+
missing = (tune_paths + @cartridges.values).find { |path| !File.exist?(path) }
|
|
124
186
|
raise Error, "no such file or directory: #{missing}" unless missing.nil?
|
|
125
187
|
raise Error, "no such file or directory: #{@songlengths}" unless @songlengths.nil? || File.exist?(@songlengths)
|
|
126
188
|
end
|
data/lib/badline/options.rb
CHANGED
|
@@ -12,9 +12,9 @@ module Badline
|
|
|
12
12
|
# Either opens the window for any media, or with --headless or
|
|
13
13
|
# --audio-out plays or renders a .sid tune without one. `sid`, given
|
|
14
14
|
# first, plays .sid tunes and directories of them in the terminal.
|
|
15
|
-
# `c64`, `vic20` or `
|
|
16
|
-
# without one the family is the C64, or the C128 for a program
|
|
17
|
-
# where C128 BASIC starts.
|
|
15
|
+
# `c64`, `vic20`, `c128` or `plus4`, given first, names the machine
|
|
16
|
+
# family, and without one the family is the C64, or the C128 for a program
|
|
17
|
+
# that loads where C128 BASIC starts. The native build adds --no-sound and
|
|
18
18
|
# --version, and badline-ruby adds --disable-jit.
|
|
19
19
|
#
|
|
20
20
|
# TABLE lists the options, and both the parser and the help read it. It
|
|
@@ -48,6 +48,19 @@ module Badline
|
|
|
48
48
|
"32k" => :"32k", "all" => :all
|
|
49
49
|
}.freeze
|
|
50
50
|
|
|
51
|
+
# The Plus/4's RAM sizes as --ram names them, in K.
|
|
52
|
+
PLUS4_RAM = { "16k" => 16, "32k" => 32, "64k" => 64 }.freeze
|
|
53
|
+
|
|
54
|
+
# Where --sid-card-address puts the Plus/4's SID card.
|
|
55
|
+
SID_CARD_ADDRESSES = { "fd40" => 0xfd40, "fe80" => 0xfe80 }.freeze
|
|
56
|
+
|
|
57
|
+
# The Plus/4's RAM expansions as --ram-expansion names them.
|
|
58
|
+
PLUS4_RAM_EXPANSIONS = { "hannes256" => :hannes256, "hannes1m" => :hannes1m, "csory256" => :csory256 }.freeze
|
|
59
|
+
|
|
60
|
+
# The Plus/4's cartridge slots, by the options that fill them and
|
|
61
|
+
# xplus4's names (Media::Plus4Media::SLOTS).
|
|
62
|
+
CARTRIDGE_SLOTS = { "--c1lo" => "c1lo", "--c1hi" => "c1hi", "--c2lo" => "c2lo", "--c2hi" => "c2hi" }.freeze
|
|
63
|
+
|
|
51
64
|
# A Float as OptionParser took one: digits with an optional sign,
|
|
52
65
|
# fraction and exponent.
|
|
53
66
|
DECIMAL = /\A[-+]?(\d+(\.\d+)?|\.\d+)([eE][-+]?\d+)?\z/
|
|
@@ -57,14 +70,34 @@ module Badline
|
|
|
57
70
|
attr_reader :program, :media_path, :tune_paths, :subtune, :sid_model, :reu, :frames, :screenshot, :save_snapshot,
|
|
58
71
|
:audio_out, :seconds, :songlengths, :filter_chunk, :timeline
|
|
59
72
|
|
|
60
|
-
# The machine family to build (Badline::Machine), :c64, :vic20
|
|
61
|
-
# :
|
|
73
|
+
# The machine family to build (Badline::Machine), :c64, :vic20, :c128
|
|
74
|
+
# or :plus4.
|
|
62
75
|
attr_reader :family
|
|
63
76
|
|
|
64
77
|
# The VIC-20's RAM expansion --ram names, a key of
|
|
65
78
|
# Vic20::Bus::RAM_CONFIGURATIONS, or nil without --ram.
|
|
66
79
|
attr_reader :ram
|
|
67
80
|
|
|
81
|
+
# The Plus/4's RAM in K --ram names, one of PLUS4_RAM's, or nil without
|
|
82
|
+
# --ram.
|
|
83
|
+
attr_reader :ram_kb
|
|
84
|
+
|
|
85
|
+
# The Plus/4's cartridge ROM images --c1lo, --c1hi, --c2lo and --c2hi
|
|
86
|
+
# name, by slot: { "c1lo" => path }.
|
|
87
|
+
attr_reader :cartridges
|
|
88
|
+
|
|
89
|
+
# The SID model of the Plus/4's SID card --sid-card names, :mos6581 or
|
|
90
|
+
# :mos8580, or nil without one.
|
|
91
|
+
attr_reader :sid_card
|
|
92
|
+
|
|
93
|
+
# Where --sid-card-address puts the Plus/4's SID card, $FD40 without
|
|
94
|
+
# it.
|
|
95
|
+
attr_reader :sid_card_address
|
|
96
|
+
|
|
97
|
+
# The Plus/4's RAM expansion --ram-expansion names, a key of
|
|
98
|
+
# Plus4::RAMExpansion::KINDS, or nil without one.
|
|
99
|
+
attr_reader :ram_expansion
|
|
100
|
+
|
|
68
101
|
def self.parse(argv, native: false) = new(native:).parse(argv)
|
|
69
102
|
|
|
70
103
|
# The native build plays the SID in the window unless --no-sound says
|
|
@@ -81,9 +114,14 @@ module Badline
|
|
|
81
114
|
@tune_paths = []
|
|
82
115
|
@subtune = nil
|
|
83
116
|
@sid_model = nil
|
|
117
|
+
@sid_card = nil
|
|
118
|
+
@sid_card_address = nil
|
|
119
|
+
@ram_expansion = nil
|
|
84
120
|
@models = []
|
|
85
121
|
@reu = nil
|
|
86
122
|
@ram = nil
|
|
123
|
+
@ram_kb = nil
|
|
124
|
+
@cartridges = {}
|
|
87
125
|
@autostart = true
|
|
88
126
|
@writable = false
|
|
89
127
|
@sound = native
|
|
@@ -92,6 +130,7 @@ module Badline
|
|
|
92
130
|
@vsync = true
|
|
93
131
|
@verbose = false
|
|
94
132
|
@true_drive = false
|
|
133
|
+
@drive_type = nil
|
|
95
134
|
@screenshot = ""
|
|
96
135
|
@save_snapshot = ""
|
|
97
136
|
@vice = false
|
|
@@ -109,6 +148,7 @@ module Badline
|
|
|
109
148
|
@tui = true
|
|
110
149
|
@all_subtunes = false
|
|
111
150
|
@window_only = []
|
|
151
|
+
@any_window_only = []
|
|
112
152
|
@headless_only = []
|
|
113
153
|
end
|
|
114
154
|
|
|
@@ -170,7 +210,10 @@ module Badline
|
|
|
170
210
|
|
|
171
211
|
def verbose? = @verbose
|
|
172
212
|
|
|
173
|
-
def true_drive? = @true_drive
|
|
213
|
+
def true_drive? = @true_drive || !@drive_type.nil?
|
|
214
|
+
|
|
215
|
+
# The Plus/4's true drive --drive-type names, "1551" without it.
|
|
216
|
+
def drive_type = @drive_type || "1551"
|
|
174
217
|
|
|
175
218
|
# Whether --save-snapshot adds VICE's modules, so x64sc opens it.
|
|
176
219
|
def vice? = @vice
|
|
@@ -178,8 +221,8 @@ module Badline
|
|
|
178
221
|
def jit? = @jit
|
|
179
222
|
|
|
180
223
|
# The name of the model to build: a C64 Badline::Model names, a VIC-20
|
|
181
|
-
# of VIC20_MODELS
|
|
182
|
-
# ntsc.
|
|
224
|
+
# of VIC20_MODELS, a C128 of C128_MODELS or a Plus/4 of PLUS4_MODELS.
|
|
225
|
+
# --ntsc is short for --model ntsc.
|
|
183
226
|
def model = @models.first || family_models.first
|
|
184
227
|
|
|
185
228
|
# Whether --c64 asks the C128 to start in C64 mode, holding C= at
|
|
@@ -201,7 +244,8 @@ module Badline
|
|
|
201
244
|
# A .sid on its own plays in the SID player, as `sid` would, unless an
|
|
202
245
|
# option of the emulator's window asks for the machine.
|
|
203
246
|
def play_lone_tune
|
|
204
|
-
return if @sid_command || @family
|
|
247
|
+
return if @sid_command || %i[vic20 plus4].include?(@family) || headless? || !@window_only.empty?
|
|
248
|
+
return if @media_path.nil?
|
|
205
249
|
|
|
206
250
|
@sid_command = File.extname(@media_path).casecmp?(".sid")
|
|
207
251
|
end
|
|
@@ -236,9 +280,11 @@ module Badline
|
|
|
236
280
|
option
|
|
237
281
|
end
|
|
238
282
|
|
|
239
|
-
# Notes an option only the window
|
|
283
|
+
# Notes an option only the window, only a window of either kind, or
|
|
284
|
+
# only the headless player takes.
|
|
240
285
|
def restrict(option, flag)
|
|
241
286
|
@window_only << flag if option.needs == :window
|
|
287
|
+
@any_window_only << flag if option.needs == :any_window
|
|
242
288
|
@headless_only << flag if option.needs == :headless
|
|
243
289
|
end
|
|
244
290
|
|
|
@@ -252,9 +298,14 @@ module Badline
|
|
|
252
298
|
case name
|
|
253
299
|
when "--subtune" then @subtune = number(flag, value)
|
|
254
300
|
when "--sid" then @sid_model = sid_model_for(value)
|
|
301
|
+
when "--sid-card" then @sid_card = sid_card_for(value)
|
|
302
|
+
when "--sid-card-address" then @sid_card_address = sid_card_address_for(value)
|
|
303
|
+
when "--ram-expansion" then @ram_expansion = plus4_ram_expansion(value)
|
|
255
304
|
when "--model" then @models << value
|
|
256
305
|
when "--reu" then @reu = reu_size(value)
|
|
257
|
-
when "--ram" then
|
|
306
|
+
when "--ram" then ram_option(value)
|
|
307
|
+
when "--drive-type" then @drive_type = drive_type_for(value)
|
|
308
|
+
when "--c1lo", "--c1hi", "--c2lo", "--c2hi" then @cartridges[CARTRIDGE_SLOTS.fetch(name)] = value
|
|
258
309
|
when "--frames" then @frames = number(flag, value)
|
|
259
310
|
when "--screenshot" then @screenshot = value
|
|
260
311
|
when "--save-snapshot" then @save_snapshot = value
|
|
@@ -267,6 +318,15 @@ module Badline
|
|
|
267
318
|
end
|
|
268
319
|
end
|
|
269
320
|
|
|
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
|
+
|
|
270
330
|
def switch(name)
|
|
271
331
|
case name
|
|
272
332
|
when "--no-autostart" then @autostart = false
|