badline 0.9.0 → 0.10.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 +29 -0
- data/CLAUDE.md +6 -4
- data/README.md +39 -4
- data/doc/pinned-behaviour.md +146 -0
- data/doc/snapshots.md +13 -0
- data/lib/badline/address_bus/pla.rb +130 -0
- data/lib/badline/address_bus.rb +9 -88
- 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 +44 -29
- 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 +17 -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/options/event.rb +5 -3
- data/lib/badline/options/help.rb +11 -3
- data/lib/badline/options/table.rb +11 -1
- data/lib/badline/options/validation.rb +55 -4
- data/lib/badline/options.rb +33 -33
- data/lib/badline/roms/README +27 -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/state.rb +4 -3
- 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 +53 -16
- data/lib/badline/vic/registers.rb +20 -4
- data/lib/badline/vic.rb +34 -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 +28 -2
- data/lib/badline/address_bus/ultimax_pages.rb +0 -38
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Media
|
|
5
|
+
# Media on a VIC-20. A program loads once the KERNAL has booted: a
|
|
6
|
+
# BASIC program at the start of BASIC, wherever the RAM puts it, and
|
|
7
|
+
# RUNs. A program that loads into BLK5 at $A000 is a cartridge's ROM
|
|
8
|
+
# instead, as are the parts of a set that includes one
|
|
9
|
+
# (Vic20Cartridge), and goes in with a power cycle, as a .crt does.
|
|
10
|
+
# Disks mount on device 8 through the KERNAL traps, or go in a true
|
|
11
|
+
# 1541 (TrueDrive), and tapes play on the datasette.
|
|
12
|
+
module Vic20Media
|
|
13
|
+
# The expansions that fill BLK1, BLK2 and BLK3 one block after
|
|
14
|
+
# another, each with the last address it fills.
|
|
15
|
+
BLOCK_EXPANSIONS = [[0x3fff, :"8k"], [0x5fff, :"16k"], [0x7fff, :"24k"]].freeze
|
|
16
|
+
|
|
17
|
+
# What a VIC-20 doesn't take.
|
|
18
|
+
UNSUPPORTED = %w[.sid].freeze
|
|
19
|
+
|
|
20
|
+
# The platform byte of a VIC-20 tape (Storage::TAP#platform).
|
|
21
|
+
TAPE_PLATFORM = 1
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
def attach(machine, path, autostart:, **options)
|
|
25
|
+
extension = File.extname(path).downcase
|
|
26
|
+
raise ArgumentError, "#{path} doesn't go in a VIC-20" if UNSUPPORTED.include?(extension)
|
|
27
|
+
|
|
28
|
+
if TrueDrive.takes?(machine, path)
|
|
29
|
+
attach_true_drive(machine, path, options.fetch(:disk, {}), autostart:)
|
|
30
|
+
elsif File.directory?(path)
|
|
31
|
+
machine.mount(Storage::HostDirectory.new(path))
|
|
32
|
+
"Mounted #{path} as device 8"
|
|
33
|
+
elsif extension == ".crt"
|
|
34
|
+
attach_cartridge(machine, path, Storage::CRTFile.new(path, machine: :vic20).chips)
|
|
35
|
+
elsif extension == ".tap"
|
|
36
|
+
attach_tape(machine, path, autostart:)
|
|
37
|
+
elsif MOUNT_TYPES.key?(extension)
|
|
38
|
+
attach_storage(machine, path, options.fetch(:disk, {}), autostart:)
|
|
39
|
+
else
|
|
40
|
+
attach_prg(machine, path, autostart:)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The RAM expansion (Vic20::Bus::RAM_CONFIGURATIONS) a VIC-20 needs
|
|
45
|
+
# for the program at `path`, or for the first program on the disk
|
|
46
|
+
# or in the directory there, by where it loads. A BASIC program
|
|
47
|
+
# needs the expansion its start of BASIC comes with, and more of
|
|
48
|
+
# BLK1 to BLK3 when it runs past $3FFF. So does a machine-language
|
|
49
|
+
# program loading into those blocks. Anything else, cartridges
|
|
50
|
+
# included, runs unexpanded.
|
|
51
|
+
def ram_for(path)
|
|
52
|
+
data = first_program(path)
|
|
53
|
+
return :unexpanded unless data && data.length > 2 && !Vic20Cartridge.chips(path, data)
|
|
54
|
+
|
|
55
|
+
load_addr = data[0] | (data[1] << 8)
|
|
56
|
+
if load_addr == 0x1201 || load_addr.between?(0x2000, 0x7fff)
|
|
57
|
+
expansion_reaching(load_addr + data.length - 3)
|
|
58
|
+
else
|
|
59
|
+
Vic20Basic::BASIC_STARTS.fetch(load_addr, :unexpanded)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def expansion_reaching(last)
|
|
66
|
+
BLOCK_EXPANSIONS.each { |reach, ram| return ram if last <= reach }
|
|
67
|
+
BLOCK_EXPANSIONS.last.last
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def first_program(path)
|
|
71
|
+
extension = File.extname(path).downcase
|
|
72
|
+
if File.directory?(path) || MOUNT_TYPES.key?(extension)
|
|
73
|
+
open_storage(path).read_file("*")
|
|
74
|
+
elsif File.file?(path) && !%w[.sid .crt .tap .g64].include?(extension)
|
|
75
|
+
Vic20Cartridge.program_bytes(path)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def open_storage(path, disk = {})
|
|
80
|
+
return Storage::HostDirectory.new(path) if File.directory?(path)
|
|
81
|
+
|
|
82
|
+
storage = MOUNT_TYPES.fetch(File.extname(path).downcase)
|
|
83
|
+
storage < Storage::DiskImage ? storage.new(path, **disk) : storage.new(path)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def attach_cartridge(machine, path, chips)
|
|
87
|
+
machine.attach_cartridge(chips)
|
|
88
|
+
"Attached cartridge #{path}"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# A tape made for another machine plays all the same, and says so.
|
|
92
|
+
def attach_tape(machine, path, autostart:)
|
|
93
|
+
tape = Storage::TAP.new(path)
|
|
94
|
+
machine.datasette.insert(tape)
|
|
95
|
+
machine.datasette.play!
|
|
96
|
+
machine.type_text(TAPE_AUTOSTART) if autostart
|
|
97
|
+
message = "Inserted #{path} in the datasette"
|
|
98
|
+
tape.platform == TAPE_PLATFORM ? message : "#{message}, a tape for another machine than the VIC-20"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def attach_true_drive(machine, path, disk, autostart:)
|
|
102
|
+
message = TrueDrive.insert(machine, path, read_only: disk.fetch(:read_only, false))
|
|
103
|
+
machine.type_text(disk_autostart(first_program(path))) if autostart
|
|
104
|
+
message
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def attach_storage(machine, path, disk, autostart:)
|
|
108
|
+
storage = open_storage(path, disk)
|
|
109
|
+
machine.mount(storage)
|
|
110
|
+
machine.type_text(disk_autostart(storage.read_file("*"))) if autostart
|
|
111
|
+
"Mounted #{path} as device 8"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# A BASIC program loads relocated to the start of BASIC, and anything
|
|
115
|
+
# else where it says.
|
|
116
|
+
def disk_autostart(data)
|
|
117
|
+
basic = data && data.length > 2 && Vic20Basic::BASIC_STARTS.key?(data[0] | (data[1] << 8))
|
|
118
|
+
basic ? %(lO"*",8\rrun\r) : AUTOSTART
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def attach_prg(machine, path, autostart:)
|
|
122
|
+
bytes = Vic20Cartridge.program_bytes(path)
|
|
123
|
+
chips = Vic20Cartridge.chips(path, bytes)
|
|
124
|
+
return attach_cartridge(machine, path, chips) if chips
|
|
125
|
+
|
|
126
|
+
machine.on_init { Vic20Basic.load_program(machine, bytes, autostart:) }
|
|
127
|
+
"Loading #{path}"
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
data/lib/badline/media.rb
CHANGED
|
@@ -4,6 +4,9 @@ require "badline/media/true_drive"
|
|
|
4
4
|
require "badline/media/queue"
|
|
5
5
|
require "badline/media/disk_set"
|
|
6
6
|
require "badline/media/disk_list"
|
|
7
|
+
require "badline/media/vic20_basic"
|
|
8
|
+
require "badline/media/vic20_cartridge"
|
|
9
|
+
require "badline/media/vic20_media"
|
|
7
10
|
|
|
8
11
|
module Badline
|
|
9
12
|
module Media
|
|
@@ -30,25 +33,13 @@ module Badline
|
|
|
30
33
|
# autostart then loads through it.
|
|
31
34
|
#
|
|
32
35
|
# An .m3u or .vfl list of disks attaches the first disk it lists
|
|
33
|
-
# (DiskList).
|
|
34
|
-
|
|
36
|
+
# (DiskList). A machine other than the C64 takes its own media
|
|
37
|
+
# (Vic20Media).
|
|
38
|
+
def attach(computer, path, autostart: true, subtune: nil, **)
|
|
35
39
|
path = DiskList.disk(path)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
computer.mount(Storage::HostDirectory.new(path))
|
|
40
|
-
"Mounted #{path} as device 8"
|
|
41
|
-
elsif File.extname(path).downcase == ".crt"
|
|
42
|
-
attach_cartridge(computer, path, options.fetch(:cartridge, {}))
|
|
43
|
-
elsif File.extname(path).downcase == ".sid"
|
|
44
|
-
attach_sid(computer, path, autostart:, subtune:)
|
|
45
|
-
elsif File.extname(path).downcase == ".tap"
|
|
46
|
-
attach_tape(computer, path, autostart:)
|
|
47
|
-
elsif MOUNT_TYPES.key?(File.extname(path).downcase)
|
|
48
|
-
attach_storage(computer, path, options.fetch(:disk, {}), autostart:)
|
|
49
|
-
else
|
|
50
|
-
attach_prg(computer, path, autostart:)
|
|
51
|
-
end
|
|
40
|
+
return Vic20Media.attach(computer, path, autostart:, **) unless computer.is_a?(Computer)
|
|
41
|
+
|
|
42
|
+
attach_c64(computer, path, autostart:, subtune:, **)
|
|
52
43
|
end
|
|
53
44
|
|
|
54
45
|
# Swaps the disk in device 8 for a disk image or a host directory,
|
|
@@ -69,6 +60,10 @@ module Badline
|
|
|
69
60
|
"Inserted #{path} in device 8"
|
|
70
61
|
end
|
|
71
62
|
|
|
63
|
+
# The RAM expansion a VIC-20 for `path` should be built with
|
|
64
|
+
# (Vic20Media.ram_for), a key of Vic20::Bus::RAM_CONFIGURATIONS.
|
|
65
|
+
def vic20_ram_for(path) = Vic20Media.ram_for(path)
|
|
66
|
+
|
|
72
67
|
# The SID a machine for `path` should be built with. A .sid tune names
|
|
73
68
|
# its own; everything else gets `otherwise`.
|
|
74
69
|
def sid_model(path, otherwise: :mos6581)
|
|
@@ -79,6 +74,25 @@ module Badline
|
|
|
79
74
|
|
|
80
75
|
private
|
|
81
76
|
|
|
77
|
+
def attach_c64(computer, path, autostart:, subtune:, **options)
|
|
78
|
+
if TrueDrive.takes?(computer, path)
|
|
79
|
+
attach_true_drive(computer, path, options.fetch(:disk, {}), autostart:)
|
|
80
|
+
elsif File.directory?(path)
|
|
81
|
+
computer.mount(Storage::HostDirectory.new(path))
|
|
82
|
+
"Mounted #{path} as device 8"
|
|
83
|
+
elsif File.extname(path).downcase == ".crt"
|
|
84
|
+
attach_cartridge(computer, path, options.fetch(:cartridge, {}))
|
|
85
|
+
elsif File.extname(path).downcase == ".sid"
|
|
86
|
+
attach_sid(computer, path, autostart:, subtune:)
|
|
87
|
+
elsif File.extname(path).downcase == ".tap"
|
|
88
|
+
attach_tape(computer, path, autostart:)
|
|
89
|
+
elsif MOUNT_TYPES.key?(File.extname(path).downcase)
|
|
90
|
+
attach_storage(computer, path, options.fetch(:disk, {}), autostart:)
|
|
91
|
+
else
|
|
92
|
+
attach_prg(computer, path, autostart:)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
82
96
|
def disk?(path)
|
|
83
97
|
storage = MOUNT_TYPES[File.extname(path).downcase]
|
|
84
98
|
File.directory?(path) || (!storage.nil? && storage < Storage::DiskImage)
|
|
@@ -6,10 +6,11 @@ module Badline
|
|
|
6
6
|
# `frame` frames have run. An event given at several frames becomes
|
|
7
7
|
# one Event per frame.
|
|
8
8
|
class Event
|
|
9
|
-
# The actions, and whether each takes an argument.
|
|
9
|
+
# The actions, and whether each takes an argument. menu's page may be
|
|
10
|
+
# left out.
|
|
10
11
|
ACTIONS = {
|
|
11
|
-
"key" => true, "type" => true, "insert" => true, "eject" => true, "screenshot" => true,
|
|
12
|
-
"reset" => false, "freeze" => false, "quit" => false
|
|
12
|
+
"key" => true, "type" => true, "insert" => true, "eject" => true, "screenshot" => true, "menu" => true,
|
|
13
|
+
"reset" => false, "freeze" => false, "resume" => false, "quit" => false
|
|
13
14
|
}.freeze
|
|
14
15
|
|
|
15
16
|
# The frames a key or the freeze button is held for.
|
|
@@ -79,6 +80,7 @@ module Badline
|
|
|
79
80
|
|
|
80
81
|
def self.valid?(action, argument)
|
|
81
82
|
return false unless ACTIONS.key?(action)
|
|
83
|
+
return true if action == "menu"
|
|
82
84
|
return argument.empty? unless ACTIONS[action]
|
|
83
85
|
return EJECTABLE.include?(argument) if action == "eject"
|
|
84
86
|
return insertable?(argument) if action == "insert"
|
data/lib/badline/options/help.rb
CHANGED
|
@@ -37,11 +37,17 @@ module Badline
|
|
|
37
37
|
|
|
38
38
|
def banner
|
|
39
39
|
<<~BANNER
|
|
40
|
-
Usage: #{@program} [options] [media]
|
|
40
|
+
Usage: #{@program} [c64] [options] [media]
|
|
41
|
+
#{@program} vic20 [options] [media]
|
|
41
42
|
#{@program} --headless [options] tune.sid
|
|
42
43
|
#{@program} [options] tune.sid --audio-out FILE
|
|
43
44
|
#{@program} sid [options] FILE|DIR...
|
|
44
45
|
|
|
46
|
+
Commands:
|
|
47
|
+
c64 Run a C64, the one --model names (the default)
|
|
48
|
+
vic20 Run a PAL VIC-20, with the RAM --ram names
|
|
49
|
+
sid Play .sid tunes and directories of them
|
|
50
|
+
|
|
45
51
|
Media can be a .prg/.p00 program, a .d64/.d71/.d81 disk image, a
|
|
46
52
|
.g64 disk image for the true 1541, an .m3u or .vfl list of disk
|
|
47
53
|
images, a .t64 tape archive, a .tap tape, a .crt cartridge, a .sid
|
|
@@ -80,8 +86,10 @@ module Badline
|
|
|
80
86
|
frames. type=TEXT types it, with \\n for RETURN. insert=FILE swaps
|
|
81
87
|
in a disk, a list's first disk, a tape or a cartridge, and
|
|
82
88
|
eject=disk, tape or cartridge takes one out. screenshot=FILE saves
|
|
83
|
-
the frame as a .bmp, with the frame's number in place of %d.
|
|
84
|
-
|
|
89
|
+
the frame as a .bmp, with the frame's number in place of %d. menu
|
|
90
|
+
opens the pause menu, and menu=PAGE opens it on snapshots, drive,
|
|
91
|
+
datasette, expansion, ports, sound or power. Its frames still count,
|
|
92
|
+
and resume closes it. reset, freeze and quit take no argument.
|
|
85
93
|
BANNER
|
|
86
94
|
end
|
|
87
95
|
|
|
@@ -7,6 +7,14 @@ module Badline
|
|
|
7
7
|
# The names Badline::Model builds, in the order the help lists them.
|
|
8
8
|
MODELS = %w[c64 c64c ntsc newntsc oldntsc drean sx64].freeze
|
|
9
9
|
|
|
10
|
+
# The VIC-20's models, as xvic names its video standards. Only PAL is
|
|
11
|
+
# emulated.
|
|
12
|
+
VIC20_MODELS = %w[pal ntsc].freeze
|
|
13
|
+
|
|
14
|
+
# The VIC-20 RAM expansions --ram takes, in the order the help lists
|
|
15
|
+
# them.
|
|
16
|
+
RAM_NAMES = %w[unexpanded 3k 8k 16k 24k 32k all].freeze
|
|
17
|
+
|
|
10
18
|
SECTIONS = ["Options", "Window options", "Options without the window", "Testing options"].freeze
|
|
11
19
|
|
|
12
20
|
# Every option of both builds, in the order the help lists them.
|
|
@@ -28,9 +36,11 @@ module Badline
|
|
|
28
36
|
section: "Window options", needs: :window),
|
|
29
37
|
Option.new("--reu SIZE", "Plug in an REU of SIZE K: 128, 256, 512 (a 1750) or up to 16384",
|
|
30
38
|
section: "Window options", needs: :window),
|
|
31
|
-
Option.new("--model NAME", "C64 to run: #{MODELS.join(', ')} (default: #{MODELS.first})",
|
|
39
|
+
Option.new("--model NAME", "C64 to run: #{MODELS.join(', ')} (default: #{MODELS.first}); VIC-20: pal",
|
|
32
40
|
section: "Window options", needs: :window),
|
|
33
41
|
Option.new("--ntsc", "Run an NTSC C64, as --model ntsc does", section: "Window options", needs: :window),
|
|
42
|
+
Option.new("--ram SIZE", "VIC-20 RAM expansion: #{RAM_NAMES.join(', ')} (default: unexpanded)",
|
|
43
|
+
section: "Window options", needs: :window),
|
|
34
44
|
Option.new("--no-vsync", "Pace frames by the timer or the sound instead of the display",
|
|
35
45
|
section: "Window options", needs: :window),
|
|
36
46
|
Option.new("--verbose", "Print the display, sound and gamepad setup and the frame timing",
|
|
@@ -2,13 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
class Options
|
|
5
|
-
# The checks Options#parse makes
|
|
6
|
-
#
|
|
7
|
-
# exist.
|
|
5
|
+
# The checks Options#parse makes: that each value reads as its option
|
|
6
|
+
# takes it, and once every argument has been read, that the options
|
|
7
|
+
# fit the mode, the numbers are positive and the files exist.
|
|
8
8
|
module Validation
|
|
9
9
|
private
|
|
10
10
|
|
|
11
|
+
def sid_model_for(value)
|
|
12
|
+
return if value == "auto"
|
|
13
|
+
raise Error, "invalid argument: --sid #{value}" unless SID_MODELS.key?(value)
|
|
14
|
+
|
|
15
|
+
SID_MODELS[value]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def ram_configuration(value)
|
|
19
|
+
raise Error, "invalid argument: --ram #{value}" unless RAM_CONFIGURATIONS.key?(value)
|
|
20
|
+
|
|
21
|
+
RAM_CONFIGURATIONS[value]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def reu_size(value)
|
|
25
|
+
raise Error, "invalid argument: --reu #{value}" unless REU_SIZES.include?(value)
|
|
26
|
+
|
|
27
|
+
value.to_i
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def number(flag, value)
|
|
31
|
+
raise Error, "invalid argument: #{flag} #{value}" unless value.match?(/\A\d+\z/)
|
|
32
|
+
|
|
33
|
+
value.to_i
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def decimal(flag, value)
|
|
37
|
+
raise Error, "invalid argument: #{flag} #{value}" unless value.match?(DECIMAL)
|
|
38
|
+
|
|
39
|
+
value.to_f
|
|
40
|
+
end
|
|
41
|
+
|
|
11
42
|
def validate
|
|
43
|
+
validate_family
|
|
12
44
|
validate_mode
|
|
13
45
|
validate_models
|
|
14
46
|
validate_numbers
|
|
@@ -26,6 +58,24 @@ module Badline
|
|
|
26
58
|
end
|
|
27
59
|
end
|
|
28
60
|
|
|
61
|
+
def validate_family
|
|
62
|
+
if @family == :vic20
|
|
63
|
+
validate_vic20
|
|
64
|
+
elsif !@ram.nil?
|
|
65
|
+
raise Error, "--ram needs vic20"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# The VIC-20 is a PAL one, in the window, without the C64's SID and
|
|
70
|
+
# REU.
|
|
71
|
+
def validate_vic20
|
|
72
|
+
raise Error, "the NTSC VIC-20 isn't emulated yet" if @models.include?("ntsc")
|
|
73
|
+
|
|
74
|
+
raise Error, "--sid needs the C64" unless @sid_model.nil?
|
|
75
|
+
raise Error, "--reu needs the C64" unless @reu.nil?
|
|
76
|
+
raise Error, "the VIC-20 needs the window" if headless?
|
|
77
|
+
end
|
|
78
|
+
|
|
29
79
|
# `sid` takes directories as well as tunes.
|
|
30
80
|
def validate_tune(path)
|
|
31
81
|
return if !File.exist?(path) || File.extname(path).casecmp?(".sid")
|
|
@@ -37,7 +87,8 @@ module Badline
|
|
|
37
87
|
# --model and --ntsc may be given more than once, but only for one
|
|
38
88
|
# model.
|
|
39
89
|
def validate_models
|
|
40
|
-
|
|
90
|
+
names = @family == :vic20 ? VIC20_MODELS : MODELS
|
|
91
|
+
unknown = @models.find { |name| !names.include?(name) }
|
|
41
92
|
raise Error, "invalid argument: --model #{unknown}" unless unknown.nil?
|
|
42
93
|
raise Error, "conflicting models: #{@models.uniq.join(' and ')}" unless @models.uniq.size <= 1
|
|
43
94
|
end
|
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
|
@@ -36,3 +36,30 @@ Both halves downloaded from
|
|
|
36
36
|
https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/new/1541/
|
|
37
37
|
(1541-c000.325302-01.bin and 1541-e000.901229-05.bin) and concatenated.
|
|
38
38
|
SHA-1 ab16f56989b27d89babe5f89c5a8cb3da71a82f0.
|
|
39
|
+
|
|
40
|
+
Commodore VIC-20 ROMs
|
|
41
|
+
=====================
|
|
42
|
+
|
|
43
|
+
Downloaded from
|
|
44
|
+
https://www.zimmers.net/anonftp/pub/cbm/firmware/computers/vic20/
|
|
45
|
+
Vic20::Bus maps them.
|
|
46
|
+
|
|
47
|
+
vic20/basic.rom
|
|
48
|
+
---------------
|
|
49
|
+
VIC-20 BASIC V2, 901486-01, for $C000-$DFFF.
|
|
50
|
+
SHA-1 587d1e90950675ab6b12d91248a3f0d640d02e8d.
|
|
51
|
+
|
|
52
|
+
vic20/kernal-pal.rom
|
|
53
|
+
--------------------
|
|
54
|
+
The PAL KERNAL, 901486-07, for $E000-$FFFF.
|
|
55
|
+
SHA-1 ce0137ed69f003a299f43538fa9eee27898e621e.
|
|
56
|
+
|
|
57
|
+
vic20/kernal-ntsc.rom
|
|
58
|
+
---------------------
|
|
59
|
+
The NTSC KERNAL, 901486-06, for $E000-$FFFF.
|
|
60
|
+
SHA-1 06de7ec017a5e78bd6746d89c2ecebb646efeb19.
|
|
61
|
+
|
|
62
|
+
vic20/character.rom
|
|
63
|
+
-------------------
|
|
64
|
+
The character generator ROM, 901460-03, for $8000-$8FFF.
|
|
65
|
+
SHA-1 4fd85ab6647ee2ac7ba40f729323f2472d35b9b4.
|
|
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
|
|
@@ -14,9 +14,10 @@ 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, and layout 4, without the VIA
|
|
18
|
+
# shift register's clock delay, still read.
|
|
19
|
+
SCHEMA = 5
|
|
20
|
+
READABLE = [3, 4, SCHEMA].freeze
|
|
20
21
|
|
|
21
22
|
attr_reader :values, :strings
|
|
22
23
|
|
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
|