badline 0.10.1 → 0.11.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 +23 -0
- data/CLAUDE.md +25 -6
- data/CONTRIBUTING.md +8 -3
- data/README.md +63 -4
- data/doc/pinned-behaviour.md +75 -0
- data/doc/snapshots.md +15 -0
- data/lib/badline/address_bus/pla.rb +3 -16
- data/lib/badline/address_bus.rb +14 -0
- data/lib/badline/c128/banks.rb +35 -0
- data/lib/badline/c128/bus.rb +347 -0
- data/lib/badline/c128/bus_state.rb +53 -0
- data/lib/badline/c128/color_lines.rb +39 -0
- data/lib/badline/c128/cpu.rb +10 -0
- data/lib/badline/c128/drives.rb +95 -0
- data/lib/badline/c128/fast_serial.rb +62 -0
- data/lib/badline/c128/keys.rb +35 -0
- data/lib/badline/c128/mmu.rb +197 -0
- data/lib/badline/c128/mmu_pages.rb +105 -0
- data/lib/badline/c128/model.rb +39 -0
- data/lib/badline/c128/modes.rb +48 -0
- data/lib/badline/c128/saved_state.rb +121 -0
- data/lib/badline/c128/vdc/memory.rb +54 -0
- data/lib/badline/c128/vdc/painter.rb +143 -0
- data/lib/badline/c128/vdc/raster.rb +97 -0
- data/lib/badline/c128/vdc/window.rb +215 -0
- data/lib/badline/c128/vdc.rb +268 -0
- data/lib/badline/c128.rb +322 -0
- data/lib/badline/chrout_trap.rb +3 -0
- data/lib/badline/cia/fast_forward.rb +42 -0
- data/lib/badline/cia/serial.rb +12 -0
- data/lib/badline/cia/timer.rb +39 -0
- data/lib/badline/cia.rb +2 -0
- data/lib/badline/computer/attachments.rb +10 -13
- data/lib/badline/computer/kernal_traps.rb +29 -0
- data/lib/badline/computer.rb +5 -0
- data/lib/badline/drive/core.rb +172 -0
- data/lib/badline/{drive1541 → drive}/idle.rb +18 -19
- data/lib/badline/drive/idle_cpu.rb +28 -0
- data/lib/badline/{drive1541 → drive}/orbit.rb +2 -1
- data/lib/badline/drive/serial_lines.rb +73 -0
- data/lib/badline/{drive1541 → drive}/sleep.rb +4 -3
- data/lib/badline/drive/watch.rb +150 -0
- data/lib/badline/drive.rb +9 -0
- data/lib/badline/drive1541/bus.rb +5 -129
- data/lib/badline/drive1541/byte_ready.rb +48 -0
- data/lib/badline/drive1541/cpu.rb +1 -19
- data/lib/badline/drive1541/disk.rb +77 -20
- data/lib/badline/drive1541/disk_state.rb +7 -2
- data/lib/badline/drive1541/disk_via.rb +3 -0
- data/lib/badline/drive1541/mechanism.rb +48 -14
- data/lib/badline/drive1541/mechanism_state.rb +5 -4
- data/lib/badline/drive1541/rotation.rb +5 -5
- data/lib/badline/drive1541/serial_port.rb +4 -60
- data/lib/badline/drive1541.rb +13 -172
- data/lib/badline/drive1571/bus.rb +111 -0
- data/lib/badline/drive1571/cpu.rb +11 -0
- data/lib/badline/drive1571/serial_port.rb +37 -0
- data/lib/badline/drive1571/serial_via.rb +25 -0
- data/lib/badline/drive1571/wd1770.rb +34 -0
- data/lib/badline/drive1571.rb +225 -0
- data/lib/badline/frontend/app.rb +35 -22
- data/lib/badline/frontend/boot.rb +31 -1
- data/lib/badline/frontend/controls.rb +11 -0
- data/lib/badline/frontend/drive_led.rb +1 -1
- data/lib/badline/frontend/keys.rb +28 -1
- data/lib/badline/frontend/machine_window.rb +16 -13
- data/lib/badline/frontend/menu_media.rb +3 -3
- data/lib/badline/frontend/menu_pages.rb +8 -2
- data/lib/badline/frontend/menu_ports.rb +1 -1
- data/lib/badline/frontend/pause_menu.rb +1 -0
- data/lib/badline/frontend/screen.rb +33 -8
- data/lib/badline/frontend/screens.rb +90 -0
- data/lib/badline/frontend/timeline.rb +15 -4
- data/lib/badline/frontend.rb +1 -0
- data/lib/badline/iec_bus.rb +54 -4
- data/lib/badline/kernal_trap/file.rb +1 -1
- data/lib/badline/kernal_trap/layout.rb +58 -8
- data/lib/badline/kernal_trap/load.rb +1 -1
- data/lib/badline/kernal_trap/save.rb +1 -1
- data/lib/badline/kernal_trap/serial.rb +5 -0
- data/lib/badline/machine.rb +20 -9
- data/lib/badline/media/true_drive.rb +26 -23
- data/lib/badline/media.rb +8 -6
- data/lib/badline/options/event.rb +5 -1
- data/lib/badline/options/family.rb +52 -0
- data/lib/badline/options/help.rb +7 -2
- data/lib/badline/options/table.rb +8 -2
- data/lib/badline/options/validation.rb +14 -1
- data/lib/badline/options.rb +20 -19
- data/lib/badline/roms/README +36 -0
- data/lib/badline/roms/c128/basic.rom +0 -0
- data/lib/badline/roms/c128/character.rom +0 -0
- data/lib/badline/roms/c128/kernal.rom +0 -0
- data/lib/badline/roms/dos1571.rom +0 -0
- data/lib/badline/snapshot/container.rb +2 -0
- data/lib/badline/snapshot/image.rb +14 -0
- data/lib/badline/snapshot.rb +9 -2
- data/lib/badline/storage/d64_image.rb +3 -0
- data/lib/badline/storage/d71_image.rb +9 -4
- data/lib/badline/storage/g64_image.rb +7 -2
- data/lib/badline/time_of_day.rb +9 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/via/fast_forward.rb +3 -3
- data/lib/badline/vic/display_state.rb +12 -0
- data/lib/badline/vic/registers.rb +3 -0
- data/lib/badline/vic.rb +108 -7
- data/lib/badline/vic20/attachments.rb +6 -0
- data/lib/badline/z80/arithmetic.rb +117 -0
- data/lib/badline/z80/bit_page.rb +55 -0
- data/lib/badline/z80/block_io.rb +58 -0
- data/lib/badline/z80/block_transfers.rb +59 -0
- data/lib/badline/z80/bus_cycles.rb +111 -0
- data/lib/badline/z80/core.rb +111 -0
- data/lib/badline/z80/exchanges.rb +50 -0
- data/lib/badline/z80/extended_page.rb +89 -0
- data/lib/badline/z80/flags.rb +37 -0
- data/lib/badline/z80/interrupt_acceptance.rb +46 -0
- data/lib/badline/z80/jumps.rb +82 -0
- data/lib/badline/z80/loads.rb +98 -0
- data/lib/badline/z80/main_page.rb +134 -0
- data/lib/badline/z80/registers.rb +120 -0
- data/lib/badline/z80/shifts.rb +64 -0
- data/lib/badline/z80.rb +11 -0
- data/lib/badline.rb +1 -0
- metadata +59 -4
|
@@ -2,51 +2,54 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
module Media
|
|
5
|
-
# Device 8 as a true
|
|
6
|
-
# is plugged in as device 8, disk
|
|
7
|
-
# traps, so a LOAD runs the DOS
|
|
8
|
-
# A .g64 holds the disk's raw
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
5
|
+
# Device 8 as a true drive instead of the KERNAL traps: a 1541, or on
|
|
6
|
+
# the C128 its 1571. Once a true drive is plugged in as device 8, disk
|
|
7
|
+
# media go into it rather than into the traps, so a LOAD runs the DOS
|
|
8
|
+
# and every byte crosses the serial bus. A .g64 holds the disk's raw
|
|
9
|
+
# GCR, which only a true drive reads, so it plugs one in when there's
|
|
10
|
+
# none yet. The 1541 reads only .d64 and .g64 images, and the 1571
|
|
11
|
+
# .d71 and .g71 as well, so the traps' other disk media, a .d81 or
|
|
12
|
+
# .t64 or a host directory, raise Error.
|
|
12
13
|
module TrueDrive
|
|
13
14
|
class Error < ArgumentError; end
|
|
14
15
|
|
|
15
16
|
class << self
|
|
16
|
-
# Plugs
|
|
17
|
-
# returns it.
|
|
17
|
+
# Plugs the machine's true drive in as device 8, unless one is there
|
|
18
|
+
# already, and returns it.
|
|
18
19
|
def plug(computer)
|
|
19
|
-
drive(computer) ||
|
|
20
|
+
drive(computer) || computer.plug_true_drive
|
|
20
21
|
end
|
|
21
22
|
|
|
22
|
-
# The
|
|
23
|
+
# The true drive on device 8, or nil.
|
|
23
24
|
def drive(computer)
|
|
24
|
-
drive = computer.
|
|
25
|
+
drive = computer.true_drive
|
|
25
26
|
drive if drive && drive.device == KernalTrap::Routine::DEVICE
|
|
26
27
|
end
|
|
27
28
|
|
|
28
|
-
# Whether the path goes in a true drive: a .g64 always, and
|
|
29
|
-
# disk media, which the traps would mount, when a true drive
|
|
30
|
-
# device 8.
|
|
29
|
+
# Whether the path goes in a true drive: a .g64 or .g71 always, and
|
|
30
|
+
# other disk media, which the traps would mount, when a true drive
|
|
31
|
+
# is device 8.
|
|
31
32
|
def takes?(computer, path)
|
|
32
|
-
return true if File.extname(path).
|
|
33
|
+
return true if %w[.g64 .g71].include?(File.extname(path).downcase)
|
|
33
34
|
|
|
34
35
|
!drive(computer).nil? && (File.directory?(path) || MOUNT_TYPES.key?(File.extname(path).downcase))
|
|
35
36
|
end
|
|
36
37
|
|
|
37
|
-
# Puts the
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
38
|
+
# Puts the image in the drive on device 8, plugging one in when
|
|
39
|
+
# there's none, and takes out what was mounted through the traps,
|
|
40
|
+
# so LOAD and SAVE reach the drive too. `read_only` puts it in
|
|
41
|
+
# write-protected.
|
|
41
42
|
def insert(computer, path, read_only: false)
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
name = drive(computer)&.model_name || (computer.family == :c128 ? "1571" : "1541")
|
|
44
|
+
types = name == "1571" ? %w[.d64 .g64 .d71 .g71] : %w[.d64 .g64]
|
|
45
|
+
unless types.include?(File.extname(path).downcase)
|
|
46
|
+
raise Error, "#{path} is not a #{types[0..-2].join(', ')} or #{types.last} image, which the #{name} reads"
|
|
44
47
|
end
|
|
45
48
|
|
|
46
49
|
disk = Drive1541::Disk.open(path, read_only:)
|
|
47
50
|
computer.unmount
|
|
48
51
|
plug(computer).insert(disk)
|
|
49
|
-
"Inserted #{path} in the
|
|
52
|
+
"Inserted #{path} in the #{name} as device 8"
|
|
50
53
|
end
|
|
51
54
|
end
|
|
52
55
|
end
|
data/lib/badline/media.rb
CHANGED
|
@@ -33,11 +33,11 @@ module Badline
|
|
|
33
33
|
# autostart then loads through it.
|
|
34
34
|
#
|
|
35
35
|
# An .m3u or .vfl list of disks attaches the first disk it lists
|
|
36
|
-
# (DiskList).
|
|
37
|
-
#
|
|
36
|
+
# (DiskList). The VIC-20 takes its own media (Vic20Media), and the
|
|
37
|
+
# C128 in C64 mode a C64's.
|
|
38
38
|
def attach(computer, path, autostart: true, subtune: nil, **)
|
|
39
39
|
path = DiskList.disk(path)
|
|
40
|
-
return Vic20Media.attach(computer, path, autostart:, **)
|
|
40
|
+
return Vic20Media.attach(computer, path, autostart:, **) if computer.family == :vic20
|
|
41
41
|
|
|
42
42
|
attach_c64(computer, path, autostart:, subtune:, **)
|
|
43
43
|
end
|
|
@@ -160,13 +160,15 @@ module Badline
|
|
|
160
160
|
def start_prg(computer, data, autostart:)
|
|
161
161
|
load_addr = computer.load_prg(data)
|
|
162
162
|
# Run only makes sense for programs at BASIC start, or a byte ahead
|
|
163
|
-
# of it, where BASIC keeps the zero before its first line.
|
|
164
|
-
|
|
163
|
+
# of it, where BASIC keeps the zero before its first line. BASIC 7.0
|
|
164
|
+
# starts at $1C01 and keeps the end of its text at $1210.
|
|
165
|
+
start, text_end = computer.family == :c128 && computer.mode == :c128 ? [0x1c01, 0x1210] : [BASIC_START, 0x2d]
|
|
166
|
+
return unless autostart && (load_addr == start || load_addr == start - 1)
|
|
165
167
|
|
|
166
168
|
# The end of the program, where BASIC's variables start, and where
|
|
167
169
|
# the KERNAL's LOAD leaves its end address.
|
|
168
170
|
end_addr = load_addr + data.length - 2
|
|
169
|
-
[
|
|
171
|
+
[text_end, 0xae].each { |pointer| computer.ram.write(pointer, [end_addr & 0xff, end_addr >> 8]) }
|
|
170
172
|
computer.type_text("run\r")
|
|
171
173
|
end
|
|
172
174
|
end
|
|
@@ -10,7 +10,7 @@ module Badline
|
|
|
10
10
|
# left out.
|
|
11
11
|
ACTIONS = {
|
|
12
12
|
"key" => true, "type" => true, "insert" => true, "eject" => true, "screenshot" => true, "menu" => true,
|
|
13
|
-
"reset" => false, "freeze" => false, "resume" => false, "quit" => false
|
|
13
|
+
"display" => true, "reset" => false, "freeze" => false, "resume" => false, "quit" => false
|
|
14
14
|
}.freeze
|
|
15
15
|
|
|
16
16
|
# The frames a key or the freeze button is held for.
|
|
@@ -18,6 +18,9 @@ module Badline
|
|
|
18
18
|
|
|
19
19
|
EJECTABLE = %w[disk tape cartridge].freeze
|
|
20
20
|
|
|
21
|
+
# The C128's screens display shows: the VIC-IIe's or the VDC's.
|
|
22
|
+
DISPLAYS = %w[vic vdc].freeze
|
|
23
|
+
|
|
21
24
|
# What insert takes: disk images, tapes and cartridges, or an .m3u
|
|
22
25
|
# or .vfl list of disks or a directory to mount as device 8.
|
|
23
26
|
INSERTABLE = %w[.d64 .d71 .d81 .g64 .t64 .tap .crt].freeze
|
|
@@ -83,6 +86,7 @@ module Badline
|
|
|
83
86
|
return true if action == "menu"
|
|
84
87
|
return argument.empty? unless ACTIONS[action]
|
|
85
88
|
return EJECTABLE.include?(argument) if action == "eject"
|
|
89
|
+
return DISPLAYS.include?(argument) if action == "display"
|
|
86
90
|
return insertable?(argument) if action == "insert"
|
|
87
91
|
|
|
88
92
|
!argument.empty?
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Options
|
|
5
|
+
# The machine family: the subcommand that names it, or else the media,
|
|
6
|
+
# and the models of each.
|
|
7
|
+
module Family
|
|
8
|
+
# Where C128 BASIC starts, which a program for the C128 loads at.
|
|
9
|
+
C128_BASIC_START = 0x1c01
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# Takes `sid`, `c64`, `vic20` or `c128` when given first.
|
|
14
|
+
def subcommand(args)
|
|
15
|
+
case args.first
|
|
16
|
+
when "sid" then @sid_command = true
|
|
17
|
+
when "c64" then @family = :c64
|
|
18
|
+
when "vic20" then @family = :vic20
|
|
19
|
+
when "c128" then @family = :c128
|
|
20
|
+
else return
|
|
21
|
+
end
|
|
22
|
+
@family_named = !@sid_command
|
|
23
|
+
args.shift
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def family_models
|
|
27
|
+
case @family
|
|
28
|
+
when :vic20 then VIC20_MODELS
|
|
29
|
+
when :c128 then C128_MODELS
|
|
30
|
+
else MODELS
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Without a family named, a program that loads at C128_BASIC_START is
|
|
35
|
+
# the C128's.
|
|
36
|
+
def pick_family
|
|
37
|
+
return if @family_named || @sid_command || @media_path.nil?
|
|
38
|
+
return unless %w[.prg .p00].include?(File.extname(@media_path).downcase) && File.file?(@media_path)
|
|
39
|
+
|
|
40
|
+
@family = :c128 if load_address(@media_path) == C128_BASIC_START
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# The load address of a .prg, or of the program a .p00 wraps after its
|
|
44
|
+
# 26-byte header.
|
|
45
|
+
def load_address(path)
|
|
46
|
+
offset = File.extname(path).casecmp?(".p00") ? 26 : 0
|
|
47
|
+
bytes = File.binread(path).bytes
|
|
48
|
+
bytes.size >= offset + 2 ? bytes[offset] | (bytes[offset + 1] << 8) : -1
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/badline/options/help.rb
CHANGED
|
@@ -39,6 +39,7 @@ module Badline
|
|
|
39
39
|
<<~BANNER
|
|
40
40
|
Usage: #{@program} [c64] [options] [media]
|
|
41
41
|
#{@program} vic20 [options] [media]
|
|
42
|
+
#{@program} c128 [options] [media]
|
|
42
43
|
#{@program} --headless [options] tune.sid
|
|
43
44
|
#{@program} [options] tune.sid --audio-out FILE
|
|
44
45
|
#{@program} sid [options] FILE|DIR...
|
|
@@ -46,6 +47,7 @@ module Badline
|
|
|
46
47
|
Commands:
|
|
47
48
|
c64 Run a C64, the one --model names (the default)
|
|
48
49
|
vic20 Run a PAL VIC-20, with the RAM --ram names
|
|
50
|
+
c128 Run a C128 in C128 mode, the one --model names
|
|
49
51
|
sid Play .sid tunes and directories of them
|
|
50
52
|
|
|
51
53
|
Media can be a .prg/.p00 program, a .d64/.d71/.d81 disk image, a
|
|
@@ -58,7 +60,8 @@ module Badline
|
|
|
58
60
|
window also autosaves every two minutes of play, which only the
|
|
59
61
|
pause menu loads. Device 8 answers through traps on the KERNAL's
|
|
60
62
|
disk routines, unless --true-drive puts an emulated 1541 there,
|
|
61
|
-
which runs its own DOS and reads .d64 and .g64 images only
|
|
63
|
+
which runs its own DOS and reads .d64 and .g64 images only, or on
|
|
64
|
+
the C128 the C128D's 1571, which reads .d71 and .g71 too. A .sid
|
|
62
65
|
tune opens in the SID player, as `sid` plays it, unless an option
|
|
63
66
|
of the emulator's window asks for the machine.
|
|
64
67
|
|
|
@@ -89,7 +92,9 @@ module Badline
|
|
|
89
92
|
the frame as a .bmp, with the frame's number in place of %d. menu
|
|
90
93
|
opens the pause menu, and menu=PAGE opens it on snapshots, drive,
|
|
91
94
|
datasette, expansion, ports, sound or power. Its frames still count,
|
|
92
|
-
and resume closes it.
|
|
95
|
+
and resume closes it. display=vdc or display=vic shows the C128's
|
|
96
|
+
80 or 40 column screen, as F8 switches them. reset, freeze and quit
|
|
97
|
+
take no argument.
|
|
93
98
|
BANNER
|
|
94
99
|
end
|
|
95
100
|
|
|
@@ -11,6 +11,9 @@ module Badline
|
|
|
11
11
|
# emulated.
|
|
12
12
|
VIC20_MODELS = %w[pal ntsc].freeze
|
|
13
13
|
|
|
14
|
+
# The C128's models, as C128::Model names them.
|
|
15
|
+
C128_MODELS = %w[c128 c128ntsc c128dcr c128dcrntsc].freeze
|
|
16
|
+
|
|
14
17
|
# The VIC-20 RAM expansions --ram takes, in the order the help lists
|
|
15
18
|
# them.
|
|
16
19
|
RAM_NAMES = %w[unexpanded 3k 8k 16k 24k 32k all].freeze
|
|
@@ -32,13 +35,16 @@ module Badline
|
|
|
32
35
|
Option.new("--sound", "Play the SID through the host's audio device (F10 mutes)",
|
|
33
36
|
section: "Window options", needs: :window),
|
|
34
37
|
Option.new("--no-sound", "Don't play the SID", section: "Window options", needs: :window, build: :native),
|
|
35
|
-
Option.new("--true-drive", "Put a true 1541 on device 8 instead of the KERNAL traps",
|
|
38
|
+
Option.new("--true-drive", "Put a true 1541 on device 8 instead of the KERNAL traps, a 1571 on the C128",
|
|
36
39
|
section: "Window options", needs: :window),
|
|
37
40
|
Option.new("--reu SIZE", "Plug in an REU of SIZE K: 128, 256, 512 (a 1750) or up to 16384",
|
|
38
41
|
section: "Window options", needs: :window),
|
|
39
|
-
Option.new("--model NAME", "C64 to run: #{MODELS.join(', ')} (default: #{MODELS.first}); VIC-20: pal"
|
|
42
|
+
Option.new("--model NAME", "C64 to run: #{MODELS.join(', ')} (default: #{MODELS.first}); VIC-20: pal; " \
|
|
43
|
+
"C128: #{C128_MODELS.join(', ')}",
|
|
40
44
|
section: "Window options", needs: :window),
|
|
41
45
|
Option.new("--ntsc", "Run an NTSC C64, as --model ntsc does", section: "Window options", needs: :window),
|
|
46
|
+
Option.new("--c64", "Start the C128 in C64 mode, holding C= at power-on",
|
|
47
|
+
section: "Window options", needs: :window),
|
|
42
48
|
Option.new("--ram SIZE", "VIC-20 RAM expansion: #{RAM_NAMES.join(', ')} (default: unexpanded)",
|
|
43
49
|
section: "Window options", needs: :window),
|
|
44
50
|
Option.new("--no-vsync", "Pace frames by the timer or the sound instead of the display",
|
|
@@ -64,6 +64,19 @@ module Badline
|
|
|
64
64
|
elsif !@ram.nil?
|
|
65
65
|
raise Error, "--ram needs vic20"
|
|
66
66
|
end
|
|
67
|
+
if @family == :c128
|
|
68
|
+
validate_c128
|
|
69
|
+
elsif @c64_mode
|
|
70
|
+
raise Error, "--c64 needs c128"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# The C128 runs in the window, without an REU, and its NTSC models
|
|
75
|
+
# have names of their own.
|
|
76
|
+
def validate_c128
|
|
77
|
+
raise Error, "--ntsc needs the C64; the NTSC C128 is --model c128ntsc" if @models.include?("ntsc")
|
|
78
|
+
raise Error, "--reu needs the C64" unless @reu.nil?
|
|
79
|
+
raise Error, "the C128 needs the window" if headless?
|
|
67
80
|
end
|
|
68
81
|
|
|
69
82
|
# The VIC-20 is a PAL one, in the window, without the C64's SID and
|
|
@@ -87,7 +100,7 @@ module Badline
|
|
|
87
100
|
# --model and --ntsc may be given more than once, but only for one
|
|
88
101
|
# model.
|
|
89
102
|
def validate_models
|
|
90
|
-
names =
|
|
103
|
+
names = family_models
|
|
91
104
|
unknown = @models.find { |name| !names.include?(name) }
|
|
92
105
|
raise Error, "invalid argument: --model #{unknown}" unless unknown.nil?
|
|
93
106
|
raise Error, "conflicting models: #{@models.uniq.join(' and ')}" unless @models.uniq.size <= 1
|
data/lib/badline/options.rb
CHANGED
|
@@ -5,16 +5,17 @@ require "badline/options/event"
|
|
|
5
5
|
require "badline/options/table"
|
|
6
6
|
require "badline/options/help"
|
|
7
7
|
require "badline/options/validation"
|
|
8
|
+
require "badline/options/family"
|
|
8
9
|
|
|
9
10
|
module Badline
|
|
10
11
|
# The command line of both builds, badline-ruby and the native badline.
|
|
11
12
|
# Either opens the window for any media, or with --headless or
|
|
12
13
|
# --audio-out plays or renders a .sid tune without one. `sid`, given
|
|
13
14
|
# first, plays .sid tunes and directories of them in the terminal.
|
|
14
|
-
# `c64` or `
|
|
15
|
-
#
|
|
16
|
-
# native build adds --no-sound and
|
|
17
|
-
# --disable-jit.
|
|
15
|
+
# `c64`, `vic20` or `c128`, given first, names the machine family, and
|
|
16
|
+
# without one the family is the C64, or the C128 for a program that loads
|
|
17
|
+
# where C128 BASIC starts. The native build adds --no-sound and
|
|
18
|
+
# --version, and badline-ruby adds --disable-jit.
|
|
18
19
|
#
|
|
19
20
|
# TABLE lists the options, and both the parser and the help read it. It
|
|
20
21
|
# parses by hand, inside the subset of Ruby Spinel compiles. Values come
|
|
@@ -23,6 +24,7 @@ module Badline
|
|
|
23
24
|
# arguments before paying for the emulator.
|
|
24
25
|
class Options
|
|
25
26
|
include Validation
|
|
27
|
+
include Family
|
|
26
28
|
|
|
27
29
|
class Error < StandardError; end
|
|
28
30
|
|
|
@@ -55,7 +57,8 @@ module Badline
|
|
|
55
57
|
attr_reader :program, :media_path, :tune_paths, :subtune, :sid_model, :reu, :frames, :screenshot, :save_snapshot,
|
|
56
58
|
:audio_out, :seconds, :songlengths, :filter_chunk, :timeline
|
|
57
59
|
|
|
58
|
-
# The machine family to build (Badline::Machine), :c64
|
|
60
|
+
# The machine family to build (Badline::Machine), :c64, :vic20 or
|
|
61
|
+
# :c128.
|
|
59
62
|
attr_reader :family
|
|
60
63
|
|
|
61
64
|
# The VIC-20's RAM expansion --ram names, a key of
|
|
@@ -72,6 +75,8 @@ module Badline
|
|
|
72
75
|
@program = native ? "badline" : "badline-ruby"
|
|
73
76
|
@media_path = nil
|
|
74
77
|
@family = :c64
|
|
78
|
+
@family_named = false
|
|
79
|
+
@c64_mode = false
|
|
75
80
|
@sid_command = false
|
|
76
81
|
@tune_paths = []
|
|
77
82
|
@subtune = nil
|
|
@@ -110,6 +115,7 @@ module Badline
|
|
|
110
115
|
args = argv.dup
|
|
111
116
|
subcommand(args)
|
|
112
117
|
argument(args.shift, args) until args.empty?
|
|
118
|
+
pick_family
|
|
113
119
|
play_lone_tune
|
|
114
120
|
validate unless help? || version?
|
|
115
121
|
self
|
|
@@ -167,9 +173,14 @@ module Badline
|
|
|
167
173
|
|
|
168
174
|
def jit? = @jit
|
|
169
175
|
|
|
170
|
-
# The name of the model to build: a C64 Badline::Model names,
|
|
171
|
-
#
|
|
172
|
-
|
|
176
|
+
# The name of the model to build: a C64 Badline::Model names, a VIC-20
|
|
177
|
+
# of VIC20_MODELS or a C128 of C128_MODELS. --ntsc is short for --model
|
|
178
|
+
# ntsc.
|
|
179
|
+
def model = @models.first || family_models.first
|
|
180
|
+
|
|
181
|
+
# Whether --c64 asks the C128 to start in C64 mode, holding C= at
|
|
182
|
+
# power-on.
|
|
183
|
+
def c64_mode? = @c64_mode
|
|
173
184
|
|
|
174
185
|
def help? = @help
|
|
175
186
|
|
|
@@ -183,17 +194,6 @@ module Badline
|
|
|
183
194
|
[:both, @native ? :native : :ruby].include?(option.build)
|
|
184
195
|
end
|
|
185
196
|
|
|
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
|
|
194
|
-
args.shift
|
|
195
|
-
end
|
|
196
|
-
|
|
197
197
|
# A .sid on its own plays in the SID player, as `sid` would, unless an
|
|
198
198
|
# option of the emulator's window asks for the machine.
|
|
199
199
|
def play_lone_tune
|
|
@@ -273,6 +273,7 @@ module Badline
|
|
|
273
273
|
when "--verbose" then @verbose = true
|
|
274
274
|
when "--true-drive" then @true_drive = true
|
|
275
275
|
when "--ntsc" then @models << "ntsc"
|
|
276
|
+
when "--c64" then @c64_mode = true
|
|
276
277
|
when "--unpaced" then @paced = false
|
|
277
278
|
when "--disable-jit" then @jit = false
|
|
278
279
|
when "--help" then @help = true
|
data/lib/badline/roms/README
CHANGED
|
@@ -61,6 +61,15 @@ https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/new/1541/
|
|
|
61
61
|
(1541-c000.325302-01.bin and 1541-e000.901229-05.bin) and concatenated.
|
|
62
62
|
SHA-1 ab16f56989b27d89babe5f89c5a8cb3da71a82f0.
|
|
63
63
|
|
|
64
|
+
dos1571.rom
|
|
65
|
+
-----------
|
|
66
|
+
The 1571 disk drive's DOS 3.0, 310654-05, 32 KB for $8000-$FFFF.
|
|
67
|
+
Drive1571 loads it.
|
|
68
|
+
|
|
69
|
+
Downloaded from
|
|
70
|
+
https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/new/1571/1571-rom.310654-05.bin
|
|
71
|
+
SHA-1 f1be619c106641a685f6609e4d43d6fc9eac1e70.
|
|
72
|
+
|
|
64
73
|
Commodore VIC-20 ROMs
|
|
65
74
|
=====================
|
|
66
75
|
|
|
@@ -87,3 +96,30 @@ vic20/character.rom
|
|
|
87
96
|
-------------------
|
|
88
97
|
The character generator ROM, 901460-03, for $8000-$8FFF.
|
|
89
98
|
SHA-1 4fd85ab6647ee2ac7ba40f729323f2472d35b9b4.
|
|
99
|
+
|
|
100
|
+
Commodore 128 ROMs
|
|
101
|
+
==================
|
|
102
|
+
|
|
103
|
+
Downloaded from
|
|
104
|
+
https://www.zimmers.net/anonftp/pub/cbm/firmware/computers/c128/
|
|
105
|
+
C128::Bus maps them in C128 mode. C64 mode runs basic.rom, kernal.rom
|
|
106
|
+
and character.rom above.
|
|
107
|
+
|
|
108
|
+
c128/basic.rom
|
|
109
|
+
--------------
|
|
110
|
+
BASIC 7.0, 318022-02 (basic.318022-02.bin), 32 KB: the low half for
|
|
111
|
+
$4000-$7FFF followed by the high half for $8000-$BFFF.
|
|
112
|
+
SHA-1 953dcdf5784a6b39ef84dd6fd968c7a03d8d6816.
|
|
113
|
+
|
|
114
|
+
c128/kernal.rom
|
|
115
|
+
---------------
|
|
116
|
+
318020-05 (kernal.318020-05.bin), 16 KB: the screen editor for
|
|
117
|
+
$C000-$CFFF, the Z80 BIOS for $D000-$DFFF and the KERNAL for
|
|
118
|
+
$E000-$FFFF.
|
|
119
|
+
SHA-1 ceb6e1a1bf7e08eb9cbc651afa29e26adccf38ab.
|
|
120
|
+
|
|
121
|
+
c128/character.rom
|
|
122
|
+
------------------
|
|
123
|
+
The character generator ROM, 390059-01 (characters.390059-01.bin),
|
|
124
|
+
8 KB: the C64's set, the same as character.rom, followed by the C128's.
|
|
125
|
+
SHA-1 29ed066d513f2d5c09ff26d9166ba23c2afb2b3f.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -29,6 +29,7 @@ module Badline
|
|
|
29
29
|
# warns it without a block.
|
|
30
30
|
def load(&)
|
|
31
31
|
return load_vic20(&) if vic20?
|
|
32
|
+
return load_c128(&) if c128?
|
|
32
33
|
|
|
33
34
|
computer = badline? ? opening { Computer.restored(state) } : Vice.setup(@container).build
|
|
34
35
|
tell(badline? ? state_report(computer) : Vice::Restore.apply(@container, computer), &)
|
|
@@ -38,6 +39,9 @@ module Badline
|
|
|
38
39
|
# Whether the machine is a VIC-20, as the container names it.
|
|
39
40
|
def vic20? = @container.machine == Container::VIC20
|
|
40
41
|
|
|
42
|
+
# Whether the machine is a C128, as the container names it.
|
|
43
|
+
def c128? = @container.machine == Container::C128
|
|
44
|
+
|
|
41
45
|
# Restores `computer` from the snapshot, and returns the Report. A
|
|
42
46
|
# snapshot that fails leaves the machine as it was.
|
|
43
47
|
def restore(computer, &)
|
|
@@ -60,6 +64,16 @@ module Badline
|
|
|
60
64
|
machine
|
|
61
65
|
end
|
|
62
66
|
|
|
67
|
+
# A C128 restores from its BADLINE module alone. x128's own modules
|
|
68
|
+
# aren't read.
|
|
69
|
+
def load_c128(&)
|
|
70
|
+
raise FormatError, "badline reads only its own C128 snapshots, not x128's" unless badline?
|
|
71
|
+
|
|
72
|
+
machine = C128.restored(state)
|
|
73
|
+
tell(state_report(machine), &)
|
|
74
|
+
machine
|
|
75
|
+
end
|
|
76
|
+
|
|
63
77
|
def tell(report)
|
|
64
78
|
report.ignored.each { |line| block_given? ? yield(line) : warn("badline: #{line}") }
|
|
65
79
|
end
|
data/lib/badline/snapshot.rb
CHANGED
|
@@ -27,11 +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
|
|
31
|
-
# named as xvic names its machine.
|
|
30
|
+
# A VIC-20's or a C128's snapshot holds the BADLINE module alone, in a
|
|
31
|
+
# container named as xvic or x128 names its machine.
|
|
32
32
|
def dump(computer)
|
|
33
33
|
state = computer.snapshot
|
|
34
34
|
return Container.new([MachineState.section(state)], machine: Container::VIC20).to_s if computer.family == :vic20
|
|
35
|
+
return Container.new([MachineState.section(state)], machine: Container::C128).to_s if computer.family == :c128
|
|
35
36
|
|
|
36
37
|
Container.new(Vice.export(state) + [MachineState.section(state)]).to_s
|
|
37
38
|
end
|
|
@@ -59,4 +60,10 @@ module Badline
|
|
|
59
60
|
|
|
60
61
|
def restore_snapshot(path, &) = Snapshot.restore(self, path, &)
|
|
61
62
|
end
|
|
63
|
+
|
|
64
|
+
class C128
|
|
65
|
+
def save_snapshot(path) = Snapshot.save(self, path)
|
|
66
|
+
|
|
67
|
+
def restore_snapshot(path, &) = Snapshot.restore(self, path, &)
|
|
68
|
+
end
|
|
62
69
|
end
|
|
@@ -7,6 +7,15 @@ module Badline
|
|
|
7
7
|
|
|
8
8
|
def storage_kind = D71
|
|
9
9
|
|
|
10
|
+
# Tracks 1-35 on the first side and 36-70 on the second.
|
|
11
|
+
def track_count = 70
|
|
12
|
+
|
|
13
|
+
def sides = 2
|
|
14
|
+
|
|
15
|
+
def sectors_in(track)
|
|
16
|
+
track > 35 ? super(track - 35) : super
|
|
17
|
+
end
|
|
18
|
+
|
|
10
19
|
private
|
|
11
20
|
|
|
12
21
|
def error_tables = ERROR_TABLES
|
|
@@ -22,10 +31,6 @@ module Badline
|
|
|
22
31
|
def bam_bitmap(track) = track > 35 ? [53, 0, 3 * (track - 36)] : super
|
|
23
32
|
|
|
24
33
|
def double_sided? = @bytes[sector_offset(18, 0) + 3].anybits?(0x80)
|
|
25
|
-
|
|
26
|
-
def sectors_in(track)
|
|
27
|
-
track > 35 ? super(track - 35) : super
|
|
28
|
-
end
|
|
29
34
|
end
|
|
30
35
|
end
|
|
31
36
|
end
|
|
@@ -5,7 +5,9 @@ module Badline
|
|
|
5
5
|
# A .g64 image: a 1541 disk's flux as raw GCR, track by track, for the
|
|
6
6
|
# true drive to read the way it reads a real disk, copy protection and
|
|
7
7
|
# all. It has no file system of its own, so only the true drive
|
|
8
|
-
# (Drive1541::Disk.from_g64) can use it.
|
|
8
|
+
# (Drive1541::Disk.from_g64) can use it. A .g71 holds a 1571 disk the
|
|
9
|
+
# same way, with "GCR-1571" for its signature and 168 half tracks,
|
|
10
|
+
# the first side's 84 and then the second's.
|
|
9
11
|
#
|
|
10
12
|
# The header is the signature "GCR-1541", a version byte ($00), the
|
|
11
13
|
# number of half tracks the tables cover (84, tracks 1 to 42.5) and
|
|
@@ -36,6 +38,7 @@ module Badline
|
|
|
36
38
|
class FormatError < StandardError; end
|
|
37
39
|
|
|
38
40
|
SIGNATURE = "GCR-1541"
|
|
41
|
+
SIGNATURE_1571 = "GCR-1571"
|
|
39
42
|
HEADER_SIZE = 12
|
|
40
43
|
HALF_TRACKS = 84
|
|
41
44
|
MAX_TRACK_SIZE = 7928
|
|
@@ -124,7 +127,9 @@ module Badline
|
|
|
124
127
|
private
|
|
125
128
|
|
|
126
129
|
def parse
|
|
127
|
-
|
|
130
|
+
unless @data.start_with?(SIGNATURE) || @data.start_with?(SIGNATURE_1571)
|
|
131
|
+
raise FormatError, "Missing G64 signature"
|
|
132
|
+
end
|
|
128
133
|
raise FormatError, "G64 header is cut short" if @data.bytesize < HEADER_SIZE
|
|
129
134
|
|
|
130
135
|
version, @half_tracks, @max_track_size = @data.byteslice(8, 4).unpack("CCv")
|
data/lib/badline/time_of_day.rb
CHANGED
|
@@ -52,6 +52,15 @@ module Badline
|
|
|
52
52
|
@latch = input.boolean? ? FIELDS.to_h { |field| [field, input.int] } : nil
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
# Whether the clock is stopped with no alarm to raise, so its cycles
|
|
56
|
+
# move only the divider's phase.
|
|
57
|
+
def quiet? = @stopped && !@alarm_pending
|
|
58
|
+
|
|
59
|
+
# Runs +cycles+ quiet cycles at once.
|
|
60
|
+
def fast_forward(cycles)
|
|
61
|
+
@accumulator = (@accumulator + (cycles * @mains_hz)) % @cycles_per_pulse
|
|
62
|
+
end
|
|
63
|
+
|
|
55
64
|
def cycle!
|
|
56
65
|
@accumulator += @mains_hz
|
|
57
66
|
pulse! if @accumulator >= @cycles_per_pulse
|
data/lib/badline/version.rb
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
module Badline
|
|
4
4
|
class VIA
|
|
5
5
|
# Running a VIA a stretch of cycles at once while nothing but its
|
|
6
|
-
# counters would change, for
|
|
6
|
+
# counters would change, for Drive::Idle.
|
|
7
7
|
module FastForward
|
|
8
8
|
# quiet_cycles for a VIA that will never set a flag of its own.
|
|
9
9
|
QUIET = 1 << 40
|
|
10
10
|
|
|
11
11
|
# How many cycles the VIA can run with nothing but its counters
|
|
12
12
|
# changing: no flag set, no pulse on a control line and no bit
|
|
13
|
-
# shifted. See
|
|
13
|
+
# shifted. See Drive::Idle.
|
|
14
14
|
def quiet_cycles
|
|
15
15
|
return 0 if @sr_uses_t2 || @shift_register.clocking? || @ca.pulsing? || @cb.pulsing?
|
|
16
16
|
|
|
@@ -26,7 +26,7 @@ module Badline
|
|
|
26
26
|
@t2.fast_forward(cycles)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
# The counters an orbit (see
|
|
29
|
+
# The counters an orbit (see Drive::Idle) has to bring back: those
|
|
30
30
|
# of the armed timers, and of the timers +touched+ names (bit 0 for
|
|
31
31
|
# timer 1, bit 1 for timer 2). The others only count.
|
|
32
32
|
def counter_state(touched)
|