badline 0.10.0 → 0.11.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 +34 -0
- data/CLAUDE.md +5 -3
- data/README.md +60 -5
- data/doc/library.md +15 -1
- data/doc/pinned-behaviour.md +75 -0
- data/doc/snapshots.md +19 -3
- data/lib/badline/address_bus/fittings.rb +38 -0
- data/lib/badline/address_bus/pla.rb +38 -21
- data/lib/badline/address_bus/roms.rb +12 -5
- data/lib/badline/address_bus.rb +20 -2
- 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/keys.rb +35 -0
- data/lib/badline/c128/mmu.rb +182 -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 +120 -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 +320 -0
- data/lib/badline/chrout_trap.rb +3 -0
- data/lib/badline/computer/attachments.rb +4 -13
- data/lib/badline/computer/kernal_traps.rb +29 -0
- data/lib/badline/computer.rb +13 -8
- data/lib/badline/frontend/app.rb +34 -21
- data/lib/badline/frontend/boot.rb +31 -1
- data/lib/badline/frontend/controls.rb +11 -0
- data/lib/badline/frontend/keys.rb +28 -1
- data/lib/badline/frontend/machine_window.rb +16 -13
- data/lib/badline/frontend/menu_pages.rb +6 -1
- 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/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/keyboard.rb +12 -1
- data/lib/badline/machine.rb +16 -7
- data/lib/badline/media.rb +8 -6
- data/lib/badline/model.rb +29 -12
- data/lib/badline/options/event.rb +5 -1
- data/lib/badline/options/family.rb +52 -0
- data/lib/badline/options/help.rb +5 -1
- 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 +51 -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/c64gs.rom +0 -0
- data/lib/badline/roms/kernal-pet64.rom +0 -0
- data/lib/badline/snapshot/container.rb +2 -0
- data/lib/badline/snapshot/image.rb +14 -0
- data/lib/badline/snapshot/setup.rb +15 -10
- data/lib/badline/snapshot/state.rb +5 -4
- data/lib/badline/snapshot/vice.rb +2 -1
- data/lib/badline/snapshot.rb +9 -2
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic/bank.rb +2 -1
- data/lib/badline/vic/display_state.rb +12 -0
- data/lib/badline/vic/registers.rb +3 -0
- data/lib/badline/vic.rb +124 -9
- data/lib/badline.rb +1 -0
- metadata +27 -1
data/lib/badline/model.rb
CHANGED
|
@@ -8,39 +8,56 @@ module Badline
|
|
|
8
8
|
# a datasette. The names are x64sc's.
|
|
9
9
|
module Model
|
|
10
10
|
# Each field but the name is a keyword Computer.new takes.
|
|
11
|
-
Profile = Data.define(:name, :vic_model, :cia_model, :sid_model, :region, :kernal, :datasette)
|
|
11
|
+
Profile = Data.define(:name, :vic_model, :cia_model, :sid_model, :region, :kernal, :datasette, :board)
|
|
12
12
|
|
|
13
13
|
# The breadbin PAL C64: the 6569 VIC-II, 6526 CIAs and the 6581 SID.
|
|
14
14
|
C64 = Profile.new(name: "c64", vic_model: :mos6569, cia_model: :mos6526, sid_model: :mos6581,
|
|
15
|
-
region: Region::PAL, kernal: :c64, datasette: true)
|
|
15
|
+
region: Region::PAL, kernal: :c64, datasette: true, board: :c64)
|
|
16
16
|
|
|
17
17
|
# The PAL C64C: the HMOS 8565 VIC-II, 6526A CIAs and the 8580 SID.
|
|
18
18
|
C64C = Profile.new(name: "c64c", vic_model: :mos8565, cia_model: :mos6526a, sid_model: :mos8580,
|
|
19
|
-
region: Region::PAL, kernal: :c64, datasette: true)
|
|
19
|
+
region: Region::PAL, kernal: :c64, datasette: true, board: :c64)
|
|
20
20
|
|
|
21
21
|
# The NTSC C64 with the 6567R8 VIC-II.
|
|
22
22
|
NTSC = Profile.new(name: "ntsc", vic_model: :mos6569, cia_model: :mos6526, sid_model: :mos6581,
|
|
23
|
-
region: Region::NTSC, kernal: :c64, datasette: true)
|
|
23
|
+
region: Region::NTSC, kernal: :c64, datasette: true, board: :c64)
|
|
24
24
|
|
|
25
25
|
# The NTSC C64C: the 8562, the 6567R8's HMOS successor, with the
|
|
26
26
|
# C64C's CIAs and SID.
|
|
27
27
|
NEW_NTSC = Profile.new(name: "newntsc", vic_model: :mos8565, cia_model: :mos6526a, sid_model: :mos8580,
|
|
28
|
-
region: Region::NTSC, kernal: :c64, datasette: true)
|
|
28
|
+
region: Region::NTSC, kernal: :c64, datasette: true, board: :c64)
|
|
29
29
|
|
|
30
30
|
# The first NTSC C64s, with the 6567R56A VIC-II.
|
|
31
31
|
OLD_NTSC = Profile.new(name: "oldntsc", vic_model: :mos6569, cia_model: :mos6526, sid_model: :mos6581,
|
|
32
|
-
region: Region::NTSC_OLD, kernal: :c64, datasette: true)
|
|
32
|
+
region: Region::NTSC_OLD, kernal: :c64, datasette: true, board: :c64)
|
|
33
33
|
|
|
34
34
|
# The Drean C64 of Argentina, on PAL-N with the 6572 VIC-II.
|
|
35
35
|
DREAN = Profile.new(name: "drean", vic_model: :mos6569, cia_model: :mos6526, sid_model: :mos6581,
|
|
36
|
-
region: Region::DREAN, kernal: :c64, datasette: true)
|
|
36
|
+
region: Region::DREAN, kernal: :c64, datasette: true, board: :c64)
|
|
37
37
|
|
|
38
38
|
# The portable SX-64: a PAL C64's chips with its own KERNAL, a 1541
|
|
39
39
|
# built in as device 8 and no cassette port.
|
|
40
40
|
SX64 = Profile.new(name: "sx64", vic_model: :mos6569, cia_model: :mos6526, sid_model: :mos6581,
|
|
41
|
-
region: Region::PAL, kernal: :sx64, datasette: false)
|
|
41
|
+
region: Region::PAL, kernal: :sx64, datasette: false, board: :c64)
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
# The PET 64, or Educator 64: a PAL C64's board in a PET's case, with
|
|
44
|
+
# its own KERNAL and a green monochrome monitor built in.
|
|
45
|
+
PET64 = Profile.new(name: "pet64", vic_model: :mos6569, cia_model: :mos6526, sid_model: :mos6581,
|
|
46
|
+
region: Region::PAL, kernal: :pet64, datasette: true, board: :pet64)
|
|
47
|
+
|
|
48
|
+
# The C64GS games console: a PAL C64C's board with its own BASIC and
|
|
49
|
+
# KERNAL, which show an animation asking for a cartridge when it has
|
|
50
|
+
# none, in a case with no keyboard and no cassette port.
|
|
51
|
+
C64GS = Profile.new(name: "c64gs", vic_model: :mos8565, cia_model: :mos6526a, sid_model: :mos8580,
|
|
52
|
+
region: Region::PAL, kernal: :gs, datasette: false, board: :gs)
|
|
53
|
+
|
|
54
|
+
# The MAX Machine, or Ultimax, of Japan: the 6566, an NTSC VIC-II
|
|
55
|
+
# for static RAM, which runs here as the first NTSC C64s' 6567R56A,
|
|
56
|
+
# one CIA, 2K of RAM and no ROMs, so it runs only a cartridge's.
|
|
57
|
+
ULTIMAX = Profile.new(name: "ultimax", vic_model: :mos6569, cia_model: :mos6526, sid_model: :mos6581,
|
|
58
|
+
region: Region::NTSC_OLD, kernal: :c64, datasette: true, board: :max)
|
|
59
|
+
|
|
60
|
+
ALL = [C64, C64C, NTSC, NEW_NTSC, OLD_NTSC, DREAN, SX64, PET64, C64GS, ULTIMAX].freeze
|
|
44
61
|
|
|
45
62
|
def self.named(name)
|
|
46
63
|
model = ALL.find { |candidate| candidate.name == name }
|
|
@@ -49,13 +66,13 @@ module Badline
|
|
|
49
66
|
model
|
|
50
67
|
end
|
|
51
68
|
|
|
52
|
-
# The model whose chips, region, KERNAL and
|
|
53
|
-
# nil for none.
|
|
69
|
+
# The model whose chips, region, KERNAL, datasette and board `machine`
|
|
70
|
+
# has, or nil for none.
|
|
54
71
|
def self.of(machine)
|
|
55
72
|
ALL.find do |model|
|
|
56
73
|
model.vic_model == machine.vic_model && model.cia_model == machine.cia_model &&
|
|
57
74
|
model.sid_model == machine.sid_model && model.region == machine.region &&
|
|
58
|
-
model.kernal == machine.kernal && model.datasette == machine.datasette
|
|
75
|
+
model.kernal == machine.kernal && model.datasette == machine.datasette && model.board == machine.board
|
|
59
76
|
end
|
|
60
77
|
end
|
|
61
78
|
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
|
|
@@ -89,7 +91,9 @@ module Badline
|
|
|
89
91
|
the frame as a .bmp, with the frame's number in place of %d. menu
|
|
90
92
|
opens the pause menu, and menu=PAGE opens it on snapshots, drive,
|
|
91
93
|
datasette, expansion, ports, sound or power. Its frames still count,
|
|
92
|
-
and resume closes it.
|
|
94
|
+
and resume closes it. display=vdc or display=vic shows the C128's
|
|
95
|
+
80 or 40 column screen, as F8 switches them. reset, freeze and quit
|
|
96
|
+
take no argument.
|
|
93
97
|
BANNER
|
|
94
98
|
end
|
|
95
99
|
|
|
@@ -5,12 +5,15 @@ module Badline
|
|
|
5
5
|
DEFAULT_RATE = 44_100
|
|
6
6
|
|
|
7
7
|
# The names Badline::Model builds, in the order the help lists them.
|
|
8
|
-
MODELS = %w[c64 c64c ntsc newntsc oldntsc drean sx64].freeze
|
|
8
|
+
MODELS = %w[c64 c64c ntsc newntsc oldntsc drean sx64 pet64 c64gs ultimax].freeze
|
|
9
9
|
|
|
10
10
|
# The VIC-20's models, as xvic names its video standards. Only PAL is
|
|
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
|
|
@@ -36,9 +39,12 @@ module Badline
|
|
|
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
|
@@ -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
|
|
@@ -63,3 +87,30 @@ vic20/character.rom
|
|
|
63
87
|
-------------------
|
|
64
88
|
The character generator ROM, 901460-03, for $8000-$8FFF.
|
|
65
89
|
SHA-1 4fd85ab6647ee2ac7ba40f729323f2472d35b9b4.
|
|
90
|
+
|
|
91
|
+
Commodore 128 ROMs
|
|
92
|
+
==================
|
|
93
|
+
|
|
94
|
+
Downloaded from
|
|
95
|
+
https://www.zimmers.net/anonftp/pub/cbm/firmware/computers/c128/
|
|
96
|
+
C128::Bus maps them in C128 mode. C64 mode runs basic.rom, kernal.rom
|
|
97
|
+
and character.rom above.
|
|
98
|
+
|
|
99
|
+
c128/basic.rom
|
|
100
|
+
--------------
|
|
101
|
+
BASIC 7.0, 318022-02 (basic.318022-02.bin), 32 KB: the low half for
|
|
102
|
+
$4000-$7FFF followed by the high half for $8000-$BFFF.
|
|
103
|
+
SHA-1 953dcdf5784a6b39ef84dd6fd968c7a03d8d6816.
|
|
104
|
+
|
|
105
|
+
c128/kernal.rom
|
|
106
|
+
---------------
|
|
107
|
+
318020-05 (kernal.318020-05.bin), 16 KB: the screen editor for
|
|
108
|
+
$C000-$CFFF, the Z80 BIOS for $D000-$DFFF and the KERNAL for
|
|
109
|
+
$E000-$FFFF.
|
|
110
|
+
SHA-1 ceb6e1a1bf7e08eb9cbc651afa29e26adccf38ab.
|
|
111
|
+
|
|
112
|
+
c128/character.rom
|
|
113
|
+
------------------
|
|
114
|
+
The character generator ROM, 390059-01 (characters.390059-01.bin),
|
|
115
|
+
8 KB: the C64's set, the same as character.rom, followed by the C128's.
|
|
116
|
+
SHA-1 29ed066d513f2d5c09ff26d9166ba23c2afb2b3f.
|
|
Binary file
|
|
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
|
|
@@ -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,10 +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
|
-
|
|
20
|
-
|
|
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
|
|
21
22
|
|
|
22
23
|
attr_reader :values, :strings
|
|
23
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,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
|
data/lib/badline/version.rb
CHANGED
data/lib/badline/vic/bank.rb
CHANGED
|
@@ -110,7 +110,8 @@ module Badline
|
|
|
110
110
|
private
|
|
111
111
|
|
|
112
112
|
# In Ultimax mode the cartridge ROMH replaces the character ROM
|
|
113
|
-
# shadow, visible at $3000-$3FFF of the window.
|
|
113
|
+
# shadow, visible at $3000-$3FFF of the window. The MAX is in
|
|
114
|
+
# Ultimax mode with no cartridge too.
|
|
114
115
|
def ultimax_peek(offset)
|
|
115
116
|
romh = @romh
|
|
116
117
|
if romh && offset.allbits?(0x3000)
|
|
@@ -68,6 +68,12 @@ module Badline
|
|
|
68
68
|
# True once AEC has followed BA down and the VIC owns the bus.
|
|
69
69
|
def bus_taken?(column) = @ba ? column >= @ba + BA_DELAY : false
|
|
70
70
|
|
|
71
|
+
# The CPU kept the bus through the cycle before +column+, so on a bad
|
|
72
|
+
# line AEC falls BA_DELAY cycles after it at the earliest.
|
|
73
|
+
def keep_bus(column)
|
|
74
|
+
@ba &&= column
|
|
75
|
+
end
|
|
76
|
+
|
|
71
77
|
def fetching?(column)
|
|
72
78
|
@matched && column >= FETCH_FIRST && column <= DMA_LAST
|
|
73
79
|
end
|
|
@@ -98,6 +104,12 @@ module Badline
|
|
|
98
104
|
|
|
99
105
|
def new_line(line)
|
|
100
106
|
@ba = nil
|
|
107
|
+
raster_step(line)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# The raster counter moved on to +line+, at the line's start or, under
|
|
111
|
+
# the VIC-IIe's TEST bit, within it.
|
|
112
|
+
def raster_step(line)
|
|
101
113
|
@line_bits = line & 0b111
|
|
102
114
|
@den_line = line == FIRST_LINE
|
|
103
115
|
@in_window = line.between?(FIRST_LINE, LAST_LINE)
|
|
@@ -150,6 +150,9 @@ module Badline
|
|
|
150
150
|
def fast? = @iie && @bytes[0x30].anybits?(0x01)
|
|
151
151
|
def test? = @iie && @bytes[0x30].anybits?(0x02)
|
|
152
152
|
|
|
153
|
+
# FAST and TEST as bits 0 and 1.
|
|
154
|
+
def clock_bits = @iie ? @bytes[0x30] & 0x03 : 0
|
|
155
|
+
|
|
153
156
|
def raster_target = uint16(@bytes[0x12], (@bytes[0x11] & 0x80) >> 7)
|
|
154
157
|
|
|
155
158
|
def latch_irq!(bit)
|