badline 0.9.0 → 0.10.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 +48 -0
- data/CLAUDE.md +6 -4
- data/README.md +39 -4
- data/doc/library.md +15 -1
- data/doc/pinned-behaviour.md +146 -0
- data/doc/snapshots.md +17 -3
- data/lib/badline/address_bus/fittings.rb +38 -0
- data/lib/badline/address_bus/pla.rb +160 -0
- data/lib/badline/address_bus/roms.rb +12 -5
- data/lib/badline/address_bus.rb +15 -90
- data/lib/badline/chrout_trap.rb +4 -3
- data/lib/badline/computer/attachments.rb +47 -7
- data/lib/badline/computer/saved_state.rb +1 -0
- data/lib/badline/computer.rb +52 -37
- data/lib/badline/control_ports.rb +10 -3
- data/lib/badline/core.rb +3 -0
- data/lib/badline/datasette.rb +39 -1
- data/lib/badline/drive1541/serial_port.rb +5 -8
- data/lib/badline/drive1541/sleep.rb +5 -6
- data/lib/badline/drive1541.rb +3 -3
- data/lib/badline/frontend/app.rb +62 -64
- data/lib/badline/frontend/boot.rb +33 -8
- data/lib/badline/frontend/controls.rb +12 -6
- data/lib/badline/frontend/drive_led.rb +9 -10
- data/lib/badline/frontend/frame_rate.rb +4 -4
- data/lib/badline/frontend/machine_window.rb +87 -0
- data/lib/badline/frontend/menu_media.rb +49 -24
- data/lib/badline/frontend/menu_pages.rb +23 -40
- data/lib/badline/frontend/menu_ports.rb +69 -0
- data/lib/badline/frontend/pacer.rb +6 -5
- data/lib/badline/frontend/pause_menu.rb +30 -33
- data/lib/badline/frontend/player_screen.rb +1 -1
- data/lib/badline/frontend/player_window.rb +1 -1
- data/lib/badline/frontend/screen.rb +39 -37
- data/lib/badline/frontend/sid_view.rb +1 -1
- data/lib/badline/frontend/sound.rb +11 -9
- data/lib/badline/frontend/timeline.rb +36 -5
- data/lib/badline/frontend.rb +2 -0
- data/lib/badline/iec_bus.rb +16 -28
- data/lib/badline/kernal_trap/drive.rb +3 -0
- data/lib/badline/kernal_trap/file.rb +1 -1
- data/lib/badline/kernal_trap/layout.rb +112 -0
- data/lib/badline/kernal_trap/load.rb +16 -27
- data/lib/badline/kernal_trap/routine.rb +11 -27
- data/lib/badline/kernal_trap/save.rb +13 -23
- data/lib/badline/kernal_trap/serial.rb +21 -19
- data/lib/badline/kernal_trap.rb +1 -0
- data/lib/badline/keyboard.rb +28 -14
- data/lib/badline/machine.rb +60 -0
- data/lib/badline/media/vic20_basic.rb +54 -0
- data/lib/badline/media/vic20_cartridge.rb +74 -0
- data/lib/badline/media/vic20_media.rb +132 -0
- data/lib/badline/media.rb +32 -18
- data/lib/badline/model.rb +29 -12
- data/lib/badline/options/event.rb +5 -3
- data/lib/badline/options/help.rb +11 -3
- data/lib/badline/options/table.rb +12 -2
- data/lib/badline/options/validation.rb +55 -4
- data/lib/badline/options.rb +33 -33
- data/lib/badline/roms/README +51 -0
- data/lib/badline/roms/c64gs.rom +0 -0
- data/lib/badline/roms/kernal-pet64.rom +0 -0
- data/lib/badline/roms/vic20/basic.rom +0 -0
- data/lib/badline/roms/vic20/character.rom +0 -0
- data/lib/badline/roms/vic20/kernal-ntsc.rom +0 -0
- data/lib/badline/roms/vic20/kernal-pal.rom +0 -0
- data/lib/badline/snapshot/container.rb +2 -0
- data/lib/badline/snapshot/image.rb +15 -0
- data/lib/badline/snapshot/setup.rb +15 -10
- data/lib/badline/snapshot/state.rb +5 -3
- data/lib/badline/snapshot/vice.rb +2 -1
- data/lib/badline/snapshot.rb +10 -0
- data/lib/badline/storage/crt_file.rb +16 -5
- data/lib/badline/storage/disk_image.rb +3 -0
- data/lib/badline/storage/host_directory.rb +3 -0
- data/lib/badline/storage/t64.rb +3 -0
- data/lib/badline/storage/tap.rb +38 -4
- data/lib/badline/storage/unavailable.rb +2 -0
- data/lib/badline/timing.rb +12 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/via/shift_register.rb +43 -16
- data/lib/badline/vic/bank.rb +55 -17
- data/lib/badline/vic/registers.rb +20 -4
- data/lib/badline/vic.rb +48 -15
- data/lib/badline/vic20/attachments.rb +103 -0
- data/lib/badline/vic20/bus.rb +308 -0
- data/lib/badline/vic20/cpu.rb +10 -0
- data/lib/badline/vic20/keyboard_via_ports.rb +53 -0
- data/lib/badline/vic20/port_wiring.rb +50 -0
- data/lib/badline/vic20/saved_state.rb +152 -0
- data/lib/badline/vic20/sound/output.rb +83 -0
- data/lib/badline/vic20/sound.rb +233 -0
- data/lib/badline/vic20/user_via_ports.rb +47 -0
- data/lib/badline/vic20/vic/painter.rb +239 -0
- data/lib/badline/vic20/vic/saved_state.rb +55 -0
- data/lib/badline/vic20/vic/text_window.rb +71 -0
- data/lib/badline/vic20/vic.rb +267 -0
- data/lib/badline/vic20.rb +220 -0
- metadata +31 -2
- data/lib/badline/address_bus/ultimax_pages.rb +0 -38
data/lib/badline/options.rb
CHANGED
|
@@ -10,7 +10,9 @@ module Badline
|
|
|
10
10
|
# The command line of both builds, badline-ruby and the native badline.
|
|
11
11
|
# Either opens the window for any media, or with --headless or
|
|
12
12
|
# --audio-out plays or renders a .sid tune without one. `sid`, given
|
|
13
|
-
# first, plays .sid tunes and directories of them in the terminal.
|
|
13
|
+
# first, plays .sid tunes and directories of them in the terminal.
|
|
14
|
+
# `c64` or `vic20`, given first, names the machine family, and without
|
|
15
|
+
# either the family is the C64. The
|
|
14
16
|
# native build adds --no-sound and --version, and badline-ruby adds
|
|
15
17
|
# --disable-jit.
|
|
16
18
|
#
|
|
@@ -37,6 +39,13 @@ module Badline
|
|
|
37
39
|
|
|
38
40
|
REU_SIZES = %w[128 256 512 1024 2048 4096 8192 16384].freeze
|
|
39
41
|
|
|
42
|
+
# The VIC-20's RAM expansions as --ram names them (RAM_NAMES), and the
|
|
43
|
+
# names Vic20::Bus::RAM_CONFIGURATIONS gives them.
|
|
44
|
+
RAM_CONFIGURATIONS = {
|
|
45
|
+
"unexpanded" => :unexpanded, "3k" => :"3k", "8k" => :"8k", "16k" => :"16k", "24k" => :"24k",
|
|
46
|
+
"32k" => :"32k", "all" => :all
|
|
47
|
+
}.freeze
|
|
48
|
+
|
|
40
49
|
# A Float as OptionParser took one: digits with an optional sign,
|
|
41
50
|
# fraction and exponent.
|
|
42
51
|
DECIMAL = /\A[-+]?(\d+(\.\d+)?|\.\d+)([eE][-+]?\d+)?\z/
|
|
@@ -46,6 +55,13 @@ module Badline
|
|
|
46
55
|
attr_reader :program, :media_path, :tune_paths, :subtune, :sid_model, :reu, :frames, :screenshot, :save_snapshot,
|
|
47
56
|
:audio_out, :seconds, :songlengths, :filter_chunk, :timeline
|
|
48
57
|
|
|
58
|
+
# The machine family to build (Badline::Machine), :c64 or :vic20.
|
|
59
|
+
attr_reader :family
|
|
60
|
+
|
|
61
|
+
# The VIC-20's RAM expansion --ram names, a key of
|
|
62
|
+
# Vic20::Bus::RAM_CONFIGURATIONS, or nil without --ram.
|
|
63
|
+
attr_reader :ram
|
|
64
|
+
|
|
49
65
|
def self.parse(argv, native: false) = new(native:).parse(argv)
|
|
50
66
|
|
|
51
67
|
# The native build plays the SID in the window unless --no-sound says
|
|
@@ -55,12 +71,14 @@ module Badline
|
|
|
55
71
|
@native = native
|
|
56
72
|
@program = native ? "badline" : "badline-ruby"
|
|
57
73
|
@media_path = nil
|
|
74
|
+
@family = :c64
|
|
58
75
|
@sid_command = false
|
|
59
76
|
@tune_paths = []
|
|
60
77
|
@subtune = nil
|
|
61
78
|
@sid_model = nil
|
|
62
79
|
@models = []
|
|
63
80
|
@reu = nil
|
|
81
|
+
@ram = nil
|
|
64
82
|
@autostart = true
|
|
65
83
|
@writable = false
|
|
66
84
|
@sound = native
|
|
@@ -90,7 +108,7 @@ module Badline
|
|
|
90
108
|
|
|
91
109
|
def parse(argv)
|
|
92
110
|
args = argv.dup
|
|
93
|
-
|
|
111
|
+
subcommand(args)
|
|
94
112
|
argument(args.shift, args) until args.empty?
|
|
95
113
|
play_lone_tune
|
|
96
114
|
validate unless help? || version?
|
|
@@ -149,9 +167,9 @@ module Badline
|
|
|
149
167
|
|
|
150
168
|
def jit? = @jit
|
|
151
169
|
|
|
152
|
-
# The name of the
|
|
153
|
-
# --model ntsc.
|
|
154
|
-
def model = @models.
|
|
170
|
+
# The name of the model to build: a C64 Badline::Model names, or a
|
|
171
|
+
# VIC-20 of VIC20_MODELS. --ntsc is short for --model ntsc.
|
|
172
|
+
def model = @models.first || (@family == :vic20 ? VIC20_MODELS.first : MODELS.first)
|
|
155
173
|
|
|
156
174
|
def help? = @help
|
|
157
175
|
|
|
@@ -165,15 +183,21 @@ module Badline
|
|
|
165
183
|
[:both, @native ? :native : :ruby].include?(option.build)
|
|
166
184
|
end
|
|
167
185
|
|
|
168
|
-
|
|
186
|
+
# Takes `sid`, `c64` or `vic20` when given first.
|
|
187
|
+
def subcommand(args)
|
|
188
|
+
case args.first
|
|
189
|
+
when "sid" then @sid_command = true
|
|
190
|
+
when "c64" then @family = :c64
|
|
191
|
+
when "vic20" then @family = :vic20
|
|
192
|
+
else return
|
|
193
|
+
end
|
|
169
194
|
args.shift
|
|
170
|
-
@sid_command = true
|
|
171
195
|
end
|
|
172
196
|
|
|
173
197
|
# A .sid on its own plays in the SID player, as `sid` would, unless an
|
|
174
198
|
# option of the emulator's window asks for the machine.
|
|
175
199
|
def play_lone_tune
|
|
176
|
-
return if @sid_command || headless? || !@window_only.empty? || @media_path.nil?
|
|
200
|
+
return if @sid_command || @family == :vic20 || headless? || !@window_only.empty? || @media_path.nil?
|
|
177
201
|
|
|
178
202
|
@sid_command = File.extname(@media_path).casecmp?(".sid")
|
|
179
203
|
end
|
|
@@ -226,6 +250,7 @@ module Badline
|
|
|
226
250
|
when "--sid" then @sid_model = sid_model_for(value)
|
|
227
251
|
when "--model" then @models << value
|
|
228
252
|
when "--reu" then @reu = reu_size(value)
|
|
253
|
+
when "--ram" then @ram = ram_configuration(value)
|
|
229
254
|
when "--frames" then @frames = number(flag, value)
|
|
230
255
|
when "--screenshot" then @screenshot = value
|
|
231
256
|
when "--save-snapshot" then @save_snapshot = value
|
|
@@ -259,31 +284,6 @@ module Badline
|
|
|
259
284
|
end
|
|
260
285
|
end
|
|
261
286
|
|
|
262
|
-
def sid_model_for(value)
|
|
263
|
-
return if value == "auto"
|
|
264
|
-
raise Error, "invalid argument: --sid #{value}" unless SID_MODELS.key?(value)
|
|
265
|
-
|
|
266
|
-
SID_MODELS[value]
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
def reu_size(value)
|
|
270
|
-
raise Error, "invalid argument: --reu #{value}" unless REU_SIZES.include?(value)
|
|
271
|
-
|
|
272
|
-
value.to_i
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
def number(flag, value)
|
|
276
|
-
raise Error, "invalid argument: #{flag} #{value}" unless value.match?(/\A\d+\z/)
|
|
277
|
-
|
|
278
|
-
value.to_i
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
def decimal(flag, value)
|
|
282
|
-
raise Error, "invalid argument: #{flag} #{value}" unless value.match?(DECIMAL)
|
|
283
|
-
|
|
284
|
-
value.to_f
|
|
285
|
-
end
|
|
286
|
-
|
|
287
287
|
def media_argument(arg)
|
|
288
288
|
raise Error, "unexpected argument: #{arg}" unless @sid_command || @media_path.nil?
|
|
289
289
|
|
data/lib/badline/roms/README
CHANGED
|
@@ -27,6 +27,30 @@ Downloaded from
|
|
|
27
27
|
https://www.zimmers.net/anonftp/pub/cbm/firmware/computers/c64/kernal.sx.251104-04.bin
|
|
28
28
|
SHA-1 aa136e91ecf3c5ac64f696b3dbcbfc5ba0871c98.
|
|
29
29
|
|
|
30
|
+
kernal-pet64.rom
|
|
31
|
+
----------------
|
|
32
|
+
The KERNAL of the PET 64, 4064 and Educator 64, 901246-01. It boots
|
|
33
|
+
with the "**** COMMODORE 4064 BASIC V2.0 ****" banner and a white
|
|
34
|
+
cursor, and its interrupt routine keeps the border, background and
|
|
35
|
+
sprite colours black unless bit 7 of $0286 is set. Computer.new(kernal:
|
|
36
|
+
:pet64) fits it, as --model pet64 does.
|
|
37
|
+
|
|
38
|
+
Downloaded from
|
|
39
|
+
https://www.zimmers.net/anonftp/pub/cbm/firmware/computers/c64/kernal.4064.901246-01.bin
|
|
40
|
+
MD5 da92801e3a03b005b746a4dd0b639c7c.
|
|
41
|
+
|
|
42
|
+
c64gs.rom
|
|
43
|
+
---------
|
|
44
|
+
The C64GS's BASIC and KERNAL, 390852-01, 16 KB: BASIC for $A000-$BFFF
|
|
45
|
+
followed by the KERNAL for $E000-$FFFF. Its BASIC is 901226-01's but
|
|
46
|
+
for 30 unused bytes. Its KERNAL is 901227-03's with most changes in
|
|
47
|
+
$F72C-$FC92, the cassette routines' place, and many jumps redirected to
|
|
48
|
+
I/O error 5. Computer.new(kernal: :gs) fits both, as --model c64gs does.
|
|
49
|
+
|
|
50
|
+
Downloaded from
|
|
51
|
+
https://www.zimmers.net/anonftp/pub/cbm/firmware/computers/c64/64gs.390852-01.bin
|
|
52
|
+
MD5 049e657afafbdd802e8c6a4f88f3d730.
|
|
53
|
+
|
|
30
54
|
dos1541.rom
|
|
31
55
|
-----------
|
|
32
56
|
The 1541 disk drive's DOS 2.6, 16 KB for $C000-$FFFF: 325302-01
|
|
@@ -36,3 +60,30 @@ Both halves downloaded from
|
|
|
36
60
|
https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/new/1541/
|
|
37
61
|
(1541-c000.325302-01.bin and 1541-e000.901229-05.bin) and concatenated.
|
|
38
62
|
SHA-1 ab16f56989b27d89babe5f89c5a8cb3da71a82f0.
|
|
63
|
+
|
|
64
|
+
Commodore VIC-20 ROMs
|
|
65
|
+
=====================
|
|
66
|
+
|
|
67
|
+
Downloaded from
|
|
68
|
+
https://www.zimmers.net/anonftp/pub/cbm/firmware/computers/vic20/
|
|
69
|
+
Vic20::Bus maps them.
|
|
70
|
+
|
|
71
|
+
vic20/basic.rom
|
|
72
|
+
---------------
|
|
73
|
+
VIC-20 BASIC V2, 901486-01, for $C000-$DFFF.
|
|
74
|
+
SHA-1 587d1e90950675ab6b12d91248a3f0d640d02e8d.
|
|
75
|
+
|
|
76
|
+
vic20/kernal-pal.rom
|
|
77
|
+
--------------------
|
|
78
|
+
The PAL KERNAL, 901486-07, for $E000-$FFFF.
|
|
79
|
+
SHA-1 ce0137ed69f003a299f43538fa9eee27898e621e.
|
|
80
|
+
|
|
81
|
+
vic20/kernal-ntsc.rom
|
|
82
|
+
---------------------
|
|
83
|
+
The NTSC KERNAL, 901486-06, for $E000-$FFFF.
|
|
84
|
+
SHA-1 06de7ec017a5e78bd6746d89c2ecebb646efeb19.
|
|
85
|
+
|
|
86
|
+
vic20/character.rom
|
|
87
|
+
-------------------
|
|
88
|
+
The character generator ROM, 901460-03, for $8000-$8FFF.
|
|
89
|
+
SHA-1 4fd85ab6647ee2ac7ba40f729323f2472d35b9b4.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -26,6 +26,8 @@ module Badline
|
|
|
26
26
|
FORMAT_MINOR = 0
|
|
27
27
|
# x64sc names its machine C64SC, and refuses a snapshot naming another.
|
|
28
28
|
MACHINE = "C64SC"
|
|
29
|
+
# xvic names its machine VIC20.
|
|
30
|
+
VIC20 = "VIC20"
|
|
29
31
|
GZIP_MAGIC = "\x1f\x8b".b
|
|
30
32
|
|
|
31
33
|
# The VICE version block, when VICE wrote the file: four version
|
|
@@ -28,11 +28,16 @@ module Badline
|
|
|
28
28
|
# modules badline reads. Yields a line for each thing left out, or
|
|
29
29
|
# warns it without a block.
|
|
30
30
|
def load(&)
|
|
31
|
+
return load_vic20(&) if vic20?
|
|
32
|
+
|
|
31
33
|
computer = badline? ? opening { Computer.restored(state) } : Vice.setup(@container).build
|
|
32
34
|
tell(badline? ? state_report(computer) : Vice::Restore.apply(@container, computer), &)
|
|
33
35
|
computer
|
|
34
36
|
end
|
|
35
37
|
|
|
38
|
+
# Whether the machine is a VIC-20, as the container names it.
|
|
39
|
+
def vic20? = @container.machine == Container::VIC20
|
|
40
|
+
|
|
36
41
|
# Restores `computer` from the snapshot, and returns the Report. A
|
|
37
42
|
# snapshot that fails leaves the machine as it was.
|
|
38
43
|
def restore(computer, &)
|
|
@@ -45,6 +50,16 @@ module Badline
|
|
|
45
50
|
|
|
46
51
|
def section = @container[MachineState::NAME]
|
|
47
52
|
|
|
53
|
+
# A VIC-20 restores from its BADLINE module alone. xvic's own modules
|
|
54
|
+
# aren't read.
|
|
55
|
+
def load_vic20(&)
|
|
56
|
+
raise FormatError, "badline reads only its own VIC-20 snapshots, not xvic's" unless badline?
|
|
57
|
+
|
|
58
|
+
machine = Vic20.restored(state)
|
|
59
|
+
tell(state_report(machine), &)
|
|
60
|
+
machine
|
|
61
|
+
end
|
|
62
|
+
|
|
48
63
|
def tell(report)
|
|
49
64
|
report.ignored.each { |line| block_given? ? yield(line) : warn("badline: #{line}") }
|
|
50
65
|
end
|
|
@@ -3,32 +3,36 @@
|
|
|
3
3
|
module Badline
|
|
4
4
|
module Snapshot
|
|
5
5
|
# How a machine was built: its VIC, CIA and SID models, its region, its
|
|
6
|
-
# RAM expansion, the size of its REU, its KERNAL
|
|
7
|
-
# datasette, the arguments Computer.new takes. A State
|
|
8
|
-
# be built first.
|
|
9
|
-
Setup = Data.define(:vic_model, :cia_model, :sid_model, :region, :ram_expansion, :reu, :kernal, :datasette
|
|
6
|
+
# RAM expansion, the size of its REU, its KERNAL, whether it has a
|
|
7
|
+
# datasette and its board, the arguments Computer.new takes. A State
|
|
8
|
+
# starts with them, so a machine to restore it into can be built first.
|
|
9
|
+
Setup = Data.define(:vic_model, :cia_model, :sid_model, :region, :ram_expansion, :reu, :kernal, :datasette,
|
|
10
|
+
:board)
|
|
10
11
|
|
|
11
12
|
class Setup
|
|
12
13
|
SID_MODELS = %i[mos6581 mos8580].freeze
|
|
13
14
|
REGIONS = [Region::PAL, Region::NTSC, Region::NTSC_OLD, Region::DREAN].freeze
|
|
14
15
|
RAM_EXPANSIONS = [:none, *RAMExpansion::TYPES.keys].freeze
|
|
15
16
|
KERNALS = AddressBus::ROMs::KERNALS.keys.freeze
|
|
17
|
+
BOARDS = AddressBus::Fittings::BOARDS
|
|
16
18
|
|
|
17
19
|
def self.of(address_bus)
|
|
18
20
|
new(vic_model: address_bus.vic.model, cia_model: address_bus.cia1.model, sid_model: address_bus.sid.model,
|
|
19
21
|
region: address_bus.region, ram_expansion: address_bus.ram_expansion.type, reu: address_bus.reu&.size_kb,
|
|
20
|
-
kernal: address_bus.kernal, datasette: address_bus.datasette.connected
|
|
22
|
+
kernal: address_bus.kernal, datasette: address_bus.datasette.connected?, board: address_bus.board)
|
|
21
23
|
end
|
|
22
24
|
|
|
23
|
-
# Layout 3 has no KERNAL or datasette, and stands for the C64's
|
|
25
|
+
# Layout 3 has no KERNAL or datasette, and stands for the C64's, and
|
|
26
|
+
# layouts before 6 have no board, and stand for the C64's.
|
|
24
27
|
def self.read(input)
|
|
25
28
|
new(vic_model: VIC::MODELS.fetch(input.int), cia_model: CIA::MODELS.fetch(input.int),
|
|
26
29
|
sid_model: SID_MODELS.fetch(input.int), region: REGIONS.fetch(input.int),
|
|
27
30
|
ram_expansion: ram_expansion(RAM_EXPANSIONS.fetch(input.int)), reu: reu(input.int),
|
|
28
31
|
kernal: input.schema > 3 ? KERNALS.fetch(input.int) : :c64,
|
|
29
|
-
datasette: input.schema > 3 ? input.boolean? : true
|
|
32
|
+
datasette: input.schema > 3 ? input.boolean? : true,
|
|
33
|
+
board: input.schema > 5 ? BOARDS.fetch(input.int) : :c64)
|
|
30
34
|
rescue IndexError
|
|
31
|
-
raise FormatError, "the state names a chip model, region, RAM expansion or
|
|
35
|
+
raise FormatError, "the state names a chip model, region, RAM expansion, KERNAL or board badline doesn't know"
|
|
32
36
|
end
|
|
33
37
|
|
|
34
38
|
def self.ram_expansion(name) = name == :none ? nil : name
|
|
@@ -45,12 +49,12 @@ module Badline
|
|
|
45
49
|
out.int(VIC::MODELS.index(vic_model)).int(CIA::MODELS.index(cia_model))
|
|
46
50
|
out.int(SID_MODELS.index(sid_model)).int(REGIONS.index(region))
|
|
47
51
|
out.int(RAM_EXPANSIONS.index(ram_expansion || :none)).int(reu || 0)
|
|
48
|
-
out.int(KERNALS.index(kernal)).boolean(datasette)
|
|
52
|
+
out.int(KERNALS.index(kernal)).boolean(datasette).int(BOARDS.index(board))
|
|
49
53
|
end
|
|
50
54
|
|
|
51
55
|
# A machine built this way, at power-on.
|
|
52
56
|
def build
|
|
53
|
-
Computer.new(vic_model:, cia_model:, sid_model:, region:, ram_expansion:, reu:, kernal:, datasette:)
|
|
57
|
+
Computer.new(vic_model:, cia_model:, sid_model:, region:, ram_expansion:, reu:, kernal:, datasette:, board:)
|
|
54
58
|
end
|
|
55
59
|
|
|
56
60
|
# The chips, led by the model's name when they make one of
|
|
@@ -63,6 +67,7 @@ module Badline
|
|
|
63
67
|
parts << "a #{reu}K REU" if reu
|
|
64
68
|
parts << "the #{kernal} KERNAL" unless kernal == :c64
|
|
65
69
|
parts << "no datasette" unless datasette
|
|
70
|
+
parts << "the #{board} board" unless board == :c64
|
|
66
71
|
parts.join(", ")
|
|
67
72
|
end
|
|
68
73
|
end
|
|
@@ -14,9 +14,11 @@ module Badline
|
|
|
14
14
|
# The layout the chips write their fields in. It goes up whenever a
|
|
15
15
|
# save_state writes something else, so a State in another layout
|
|
16
16
|
# fails before anything is read into a machine. Layout 3, without the
|
|
17
|
-
# KERNAL and datasette in the setup,
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
# KERNAL and datasette in the setup, layout 4, without the VIA shift
|
|
18
|
+
# register's clock delay, and layout 5, without the board in the
|
|
19
|
+
# setup, still read.
|
|
20
|
+
SCHEMA = 6
|
|
21
|
+
READABLE = [3, 4, 5, SCHEMA].freeze
|
|
20
22
|
|
|
21
23
|
attr_reader :values, :strings
|
|
22
24
|
|
|
@@ -69,7 +69,8 @@ module Badline
|
|
|
69
69
|
reu = nil unless reu && REU1764.reads?(reu)
|
|
70
70
|
Setup.new(vic_model: model, cia_model: model == :mos8565 ? :mos6526a : :mos6526, sid_model: sid,
|
|
71
71
|
region: vic ? VICII.region(vic) : Region::PAL, ram_expansion: nil,
|
|
72
|
-
reu: reu ? REU1764.size_kb(reu) : nil, kernal: :c64, datasette: true
|
|
72
|
+
reu: reu ? REU1764.size_kb(reu) : nil, kernal: :c64, datasette: true,
|
|
73
|
+
board: :c64)
|
|
73
74
|
end
|
|
74
75
|
end
|
|
75
76
|
end
|
data/lib/badline/snapshot.rb
CHANGED
|
@@ -27,8 +27,12 @@ module Badline
|
|
|
27
27
|
path
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
# A VIC-20's snapshot holds the BADLINE module alone, in a container
|
|
31
|
+
# named as xvic names its machine.
|
|
30
32
|
def dump(computer)
|
|
31
33
|
state = computer.snapshot
|
|
34
|
+
return Container.new([MachineState.section(state)], machine: Container::VIC20).to_s if computer.family == :vic20
|
|
35
|
+
|
|
32
36
|
Container.new(Vice.export(state) + [MachineState.section(state)]).to_s
|
|
33
37
|
end
|
|
34
38
|
|
|
@@ -49,4 +53,10 @@ module Badline
|
|
|
49
53
|
|
|
50
54
|
def restore_snapshot(path, &) = Snapshot.restore(self, path, &)
|
|
51
55
|
end
|
|
56
|
+
|
|
57
|
+
class Vic20
|
|
58
|
+
def save_snapshot(path) = Snapshot.save(self, path)
|
|
59
|
+
|
|
60
|
+
def restore_snapshot(path, &) = Snapshot.restore(self, path, &)
|
|
61
|
+
end
|
|
52
62
|
end
|
|
@@ -6,13 +6,14 @@ module Badline
|
|
|
6
6
|
class FormatError < StandardError; end
|
|
7
7
|
|
|
8
8
|
SIGNATURE = "C64 CARTRIDGE ".b
|
|
9
|
+
VIC20_SIGNATURE = "VIC20 CARTRIDGE ".b
|
|
9
10
|
CHIP_SIGNATURE = "CHIP".b
|
|
10
11
|
CHIP_HEADER_SIZE = 0x10
|
|
11
12
|
RAM_CHIP = 1
|
|
12
13
|
|
|
13
14
|
Chip = Data.define(:chip_type, :bank, :address, :data)
|
|
14
15
|
|
|
15
|
-
attr_reader :hardware_type, :subtype, :exrom, :game, :name, :chips
|
|
16
|
+
attr_reader :machine, :hardware_type, :subtype, :exrom, :game, :name, :chips
|
|
16
17
|
|
|
17
18
|
# What .write needs of an image: the fields a parsed CRTFile reads.
|
|
18
19
|
Image = Data.define(:hardware_type, :subtype, :exrom, :game, :name, :chips)
|
|
@@ -33,16 +34,19 @@ module Badline
|
|
|
33
34
|
header + packets.join
|
|
34
35
|
end
|
|
35
36
|
|
|
36
|
-
# `bytes` stands in for the file's contents.
|
|
37
|
-
|
|
37
|
+
# `bytes` stands in for the file's contents. `machine` is the one the
|
|
38
|
+
# image has to be for, :c64 or :vic20, or nil for either.
|
|
39
|
+
def initialize(path = nil, bytes: nil, machine: :c64)
|
|
38
40
|
parse(bytes || File.binread(path))
|
|
41
|
+
return if machine.nil? || @machine == machine
|
|
42
|
+
|
|
43
|
+
raise FormatError, "Cartridge image is for the #{@machine}, not the #{machine}"
|
|
39
44
|
end
|
|
40
45
|
|
|
41
46
|
private
|
|
42
47
|
|
|
43
48
|
def parse(bytes)
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
@machine = signed_machine(bytes)
|
|
46
50
|
@hardware_type = bytes[0x16, 2].unpack1("n")
|
|
47
51
|
@exrom = bytes.getbyte(0x18)
|
|
48
52
|
@game = bytes.getbyte(0x19)
|
|
@@ -51,6 +55,13 @@ module Badline
|
|
|
51
55
|
@chips = parse_chips(bytes, bytes[0x10, 4].unpack1("N"))
|
|
52
56
|
end
|
|
53
57
|
|
|
58
|
+
def signed_machine(bytes)
|
|
59
|
+
return :c64 if bytes.start_with?(SIGNATURE)
|
|
60
|
+
return :vic20 if bytes.start_with?(VIC20_SIGNATURE)
|
|
61
|
+
|
|
62
|
+
raise FormatError, "Missing CRT signature"
|
|
63
|
+
end
|
|
64
|
+
|
|
54
65
|
# Trailing bytes too short to hold a CHIP header are ignored.
|
|
55
66
|
def parse_chips(bytes, offset)
|
|
56
67
|
chips = []
|
data/lib/badline/storage/t64.rb
CHANGED
|
@@ -20,6 +20,9 @@ module Badline
|
|
|
20
20
|
raise FormatError, "Missing T64 signature" unless @bytes[0, 3] == SIGNATURE
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
# The host file's path, as it was opened.
|
|
24
|
+
attr_reader :path
|
|
25
|
+
|
|
23
26
|
# What Storage.reopen needs to open the archive again: its path and
|
|
24
27
|
# its bytes.
|
|
25
28
|
def save_setup(out)
|
data/lib/badline/storage/tap.rb
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
module Badline
|
|
4
4
|
module Storage
|
|
5
5
|
# Raw datasette pulse stream. The header holds the signature, the
|
|
6
|
-
# version at $0c
|
|
7
|
-
#
|
|
8
|
-
# of 8 cycles.
|
|
6
|
+
# version at $0c, the platform at $0d (0 C64, 1 VIC-20, 2 C16) and the
|
|
7
|
+
# data size at $10; the data runs from $14, every byte the time to the
|
|
8
|
+
# next falling edge on the tape read line, in units of 8 cycles.
|
|
9
9
|
class TAP
|
|
10
10
|
class FormatError < StandardError; end
|
|
11
11
|
|
|
@@ -14,7 +14,14 @@ module Badline
|
|
|
14
14
|
OVERFLOW = 0x100 * 8
|
|
15
15
|
|
|
16
16
|
# The file it was read from, expanded, and the file's bytes.
|
|
17
|
-
attr_reader :version, :path, :bytes
|
|
17
|
+
attr_reader :version, :platform, :path, :bytes
|
|
18
|
+
|
|
19
|
+
# A blank version 1 tape for `platform` (0 C64, 1 VIC-20) at `path`,
|
|
20
|
+
# written to the host file.
|
|
21
|
+
def self.create(path, platform:)
|
|
22
|
+
File.binwrite(path, (SIGNATURE + [1, platform, 0, 0, 0, 0, 0, 0]).pack("C*"))
|
|
23
|
+
new(path)
|
|
24
|
+
end
|
|
18
25
|
|
|
19
26
|
# `bytes` stands in for the host file's contents, as a snapshot
|
|
20
27
|
# restores the tape it holds.
|
|
@@ -26,6 +33,8 @@ module Badline
|
|
|
26
33
|
@version = @bytes[0x0c].to_i
|
|
27
34
|
raise FormatError, "Unsupported TAP version #{@version}" if @version > 1
|
|
28
35
|
|
|
36
|
+
@platform = @bytes[0x0d].to_i
|
|
37
|
+
|
|
29
38
|
@end = tape_end
|
|
30
39
|
rewind
|
|
31
40
|
end
|
|
@@ -43,6 +52,31 @@ module Badline
|
|
|
43
52
|
|
|
44
53
|
def end? = @pos >= @end
|
|
45
54
|
|
|
55
|
+
# Records a pulse `cycles` long at the position, in place of the rest
|
|
56
|
+
# of the tape: a byte of units of 8 cycles, or a zero and the 24-bit
|
|
57
|
+
# cycle count for a long one (a lone zero, an overflow, on a version 0
|
|
58
|
+
# tape).
|
|
59
|
+
def record_pulse(cycles)
|
|
60
|
+
units = (cycles + 4) / 8
|
|
61
|
+
encoded = if units.between?(1, 0xff) then [units]
|
|
62
|
+
elsif version.zero? then [0]
|
|
63
|
+
else
|
|
64
|
+
count = [cycles, 0xffffff].min
|
|
65
|
+
[0, count & 0xff, (count >> 8) & 0xff, count >> 16]
|
|
66
|
+
end
|
|
67
|
+
@bytes.slice!(@pos..)
|
|
68
|
+
@bytes.concat(encoded)
|
|
69
|
+
@pos = @end = @bytes.length
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Writes the tape back to its host file, with the data size the
|
|
73
|
+
# header states.
|
|
74
|
+
def save
|
|
75
|
+
size = @bytes.length - HEADER_SIZE
|
|
76
|
+
@bytes[0x10, 4] = [size & 0xff, (size >> 8) & 0xff, (size >> 16) & 0xff, size >> 24]
|
|
77
|
+
File.binwrite(@path, @bytes.pack("C*"))
|
|
78
|
+
end
|
|
79
|
+
|
|
46
80
|
# Cycles until the next falling edge, or nil once the tape has run out.
|
|
47
81
|
def next_pulse
|
|
48
82
|
return if end?
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
# = Timing
|
|
5
|
+
#
|
|
6
|
+
# What a front end needs to run a machine's frames and show them: the CPU
|
|
7
|
+
# clock, the raster's cycles per line and lines per frame, and the
|
|
8
|
+
# [left, top, width, height] of the raster it shows, and how many of the
|
|
9
|
+
# window's square pixels wide each of its pixels shows. Each machine
|
|
10
|
+
# derives it from its region.
|
|
11
|
+
Timing = Data.define(:clock_hz, :cycles_per_line, :lines_per_frame, :crop, :pixel_width)
|
|
12
|
+
end
|
data/lib/badline/version.rb
CHANGED