badline 0.15.1 → 0.16.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 +55 -0
- data/CLAUDE.md +8 -7
- data/README.md +128 -9
- data/doc/media.md +9 -0
- data/doc/pinned-behaviour.md +67 -0
- data/doc/snapshot-format.md +215 -18
- data/doc/snapshots.md +55 -0
- data/lib/badline/c64/common.rb +1 -1
- data/lib/badline/chips/crtc/keyed_state.rb +46 -0
- data/lib/badline/chips/crtc.rb +163 -0
- data/lib/badline/chips/pia/control_lines.rb +138 -0
- data/lib/badline/chips/pia/keyed_parts.rb +56 -0
- data/lib/badline/chips/pia.rb +138 -0
- data/lib/badline/chips/vic/register_log.rb +1 -4
- data/lib/badline/core.rb +3 -0
- data/lib/badline/drives/ieee_bus.rb +74 -0
- data/lib/badline/drives/model1551/cable.rb +5 -2
- data/lib/badline/drives/model1551/tia.rb +13 -34
- data/lib/badline/drives/model1551.rb +26 -4
- data/lib/badline/drives/model2031/ieee_port.rb +133 -0
- data/lib/badline/drives/model2031/keyed_state.rb +66 -0
- data/lib/badline/drives/model2031/parts.rb +25 -0
- data/lib/badline/drives/model2031.rb +92 -0
- data/lib/badline/drives.rb +1 -0
- data/lib/badline/frontend/app.rb +1 -1
- data/lib/badline/frontend/controls.rb +23 -3
- data/lib/badline/frontend/gamepads.rb +10 -3
- data/lib/badline/frontend/keys.rb +88 -0
- data/lib/badline/frontend/media_slots.rb +1 -0
- data/lib/badline/frontend/menu/menu_dialogs.rb +2 -1
- data/lib/badline/frontend/menu/menu_media.rb +27 -28
- data/lib/badline/frontend/menu/menu_pages.rb +24 -2
- data/lib/badline/frontend/menu/menu_ports.rb +22 -1
- data/lib/badline/frontend/menu/pause_menu.rb +1 -0
- data/lib/badline/frontend/pet_keyboard.rb +124 -0
- data/lib/badline/frontend/screens.rb +2 -1
- data/lib/badline/frontend/timeline.rb +16 -9
- data/lib/badline/frontend.rb +1 -0
- data/lib/badline/kernal_trap/dos/writes.rb +3 -1
- data/lib/badline/kernal_trap/dos.rb +6 -1
- data/lib/badline/kernal_trap/frames.rb +117 -0
- data/lib/badline/kernal_trap/ieee.rb +151 -0
- data/lib/badline/kernal_trap/ieee_layout.rb +113 -0
- data/lib/badline/kernal_trap/keyed_state.rb +4 -4
- data/lib/badline/kernal_trap/routine.rb +3 -53
- data/lib/badline/kernal_trap/serial.rb +4 -102
- data/lib/badline/kernal_trap/steps.rb +64 -0
- data/lib/badline/kernal_trap.rb +4 -0
- data/lib/badline/machine/keyboard_buffer.rb +8 -0
- data/lib/badline/machine/settling.rb +1 -1
- data/lib/badline/machine.rb +29 -6
- data/lib/badline/media/extensions.rb +5 -2
- data/lib/badline/media/pet_media.rb +40 -0
- data/lib/badline/media/program.rb +14 -2
- data/lib/badline/media/true_drive.rb +19 -6
- data/lib/badline/media/vic20_media.rb +2 -2
- data/lib/badline/media.rb +41 -17
- data/lib/badline/options/family.rb +17 -6
- data/lib/badline/options/help.rb +23 -3
- data/lib/badline/options/table.rb +22 -4
- data/lib/badline/options/validation.rb +84 -5
- data/lib/badline/options.rb +47 -16
- data/lib/badline/pet/bus/expansion.rb +200 -0
- data/lib/badline/pet/bus.rb +222 -0
- data/lib/badline/pet/canvas.rb +97 -0
- data/lib/badline/pet/cassettes.rb +63 -0
- data/lib/badline/pet/characters.rb +32 -0
- data/lib/badline/pet/cpu.rb +10 -0
- data/lib/badline/pet/crtc_video.rb +155 -0
- data/lib/badline/pet/discrete_video.rb +83 -0
- data/lib/badline/pet/ieee_drive.rb +76 -0
- data/lib/badline/pet/ieee_ports.rb +77 -0
- data/lib/badline/pet/ieee_traps.rb +80 -0
- data/lib/badline/pet/keyboard_ports.rb +74 -0
- data/lib/badline/pet/keyed_modules.rb +88 -0
- data/lib/badline/pet/keyed_parts.rb +127 -0
- data/lib/badline/pet/keyed_state.rb +42 -0
- data/lib/badline/pet/model.rb +163 -0
- data/lib/badline/pet/rom_sets.rb +89 -0
- data/lib/badline/pet/sound.rb +128 -0
- data/lib/badline/pet/via.rb +10 -0
- data/lib/badline/pet/via_ports.rb +19 -0
- data/lib/badline/pet.rb +244 -0
- data/lib/badline/plus4/keyed_state.rb +5 -3
- data/lib/badline/plus4/tcbm_drive.rb +14 -0
- data/lib/badline/plus4.rb +1 -1
- data/lib/badline/roms/README +169 -0
- data/lib/badline/roms/dos2031.rom +0 -0
- data/lib/badline/roms/pet/basic1.rom +0 -0
- data/lib/badline/roms/pet/basic2.rom +0 -0
- data/lib/badline/roms/pet/basic4.rom +0 -0
- data/lib/badline/roms/pet/characters-din.rom +0 -0
- data/lib/badline/roms/pet/characters1.rom +0 -0
- data/lib/badline/roms/pet/characters2.rom +0 -0
- data/lib/badline/roms/pet/editor1.rom +0 -0
- data/lib/badline/roms/pet/editor2-b.rom +0 -0
- data/lib/badline/roms/pet/editor2-n.rom +0 -0
- data/lib/badline/roms/pet/editor4-40-n-50hz.rom +0 -0
- data/lib/badline/roms/pet/editor4-40-n-60hz.rom +0 -0
- data/lib/badline/roms/pet/editor4-80-b-50hz-0283.rom +0 -0
- data/lib/badline/roms/pet/editor4-80-b-50hz-8296.rom +0 -0
- data/lib/badline/roms/pet/editor4-80-b-50hz.rom +0 -0
- data/lib/badline/roms/pet/editor4-80-b-60hz.rom +0 -0
- data/lib/badline/roms/pet/editor4-b.rom +0 -0
- data/lib/badline/roms/pet/editor4-n.rom +0 -0
- data/lib/badline/roms/pet/kernal1.rom +0 -0
- data/lib/badline/roms/pet/kernal2.rom +0 -0
- data/lib/badline/roms/pet/kernal4.rom +0 -0
- data/lib/badline/snapshot/container.rb +6 -1
- data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
- data/lib/badline/snapshot/field_doc.rb +17 -3
- data/lib/badline/snapshot/image.rb +28 -10
- data/lib/badline/snapshot/pet_setup.rb +88 -0
- data/lib/badline/snapshot/storage_setup.rb +7 -1
- data/lib/badline/snapshot/vice/pet.rb +117 -0
- data/lib/badline/snapshot/vice/pet_cpu.rb +67 -0
- data/lib/badline/snapshot/vice/pet_crtc.rb +246 -0
- data/lib/badline/snapshot/vice/pet_mem.rb +93 -0
- data/lib/badline/snapshot/vice/pet_pias.rb +56 -0
- data/lib/badline/snapshot/vice/pet_restore.rb +150 -0
- data/lib/badline/snapshot/vice/pet_via.rb +164 -0
- data/lib/badline/snapshot/vice.rb +1 -0
- data/lib/badline/snapshot.rb +9 -5
- data/lib/badline/storage/d80_image.rb +48 -0
- data/lib/badline/storage/d82_image.rb +21 -0
- data/lib/badline/storage/tap.rb +16 -3
- data/lib/badline/storage.rb +2 -0
- data/lib/badline/support/pet_checkpoint.rb +38 -0
- data/lib/badline/timing.rb +5 -4
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic20/attachments.rb +7 -2
- data/lib/badline/vic20/bus/keyed_state.rb +5 -4
- data/lib/badline/vic20/bus.rb +5 -0
- data/lib/badline/vic20.rb +1 -1
- metadata +70 -1
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model2031
|
|
6
|
+
# VIA 1's side of the IEEE-488 bus (Drives::IEEEBus), through a 75160
|
|
7
|
+
# for the data lines and a 75161 for the control lines (schematic
|
|
8
|
+
# 2031038, sheet 2):
|
|
9
|
+
#
|
|
10
|
+
# PA0-7 DIO1-8 PB4 T/R, high to talk
|
|
11
|
+
# PB0 ATN acknowledge (ATNA) PB5 HD SEL, unused
|
|
12
|
+
# PB1 NRFD PB6 DAV
|
|
13
|
+
# PB2 NDAC PB7 ATN in, high while ATN is low
|
|
14
|
+
# PB3 EOI CA1 ATN in, as PB7
|
|
15
|
+
#
|
|
16
|
+
# The ports read and drive the lines' levels, 0 for a low line. T/R
|
|
17
|
+
# turns the transceivers: talking, the drive drives the data lines,
|
|
18
|
+
# DAV and EOI, and reads NRFD and NDAC; listening, the other way round.
|
|
19
|
+
# EOI turns back to the bus while ATN is low. A pin the transceiver
|
|
20
|
+
# drives the bus from reads the drive's own side, high but for what
|
|
21
|
+
# the VIA or a jumper pulls low.
|
|
22
|
+
#
|
|
23
|
+
# An XOR of ATN in and ATNA drives two 7406s that pull NRFD and NDAC
|
|
24
|
+
# low on the bus, so the drive answers ATN before the DOS gets to it,
|
|
25
|
+
# and holds the bus until the DOS acknowledges. ATNA reaches the XOR
|
|
26
|
+
# only while PB0 is an output, and is low there otherwise, which
|
|
27
|
+
# leaves the bus alone while the DOS reads the jumpers.
|
|
28
|
+
#
|
|
29
|
+
# The device number jumpers are diodes from PB0 and PB1 to CA2: with
|
|
30
|
+
# CA2 low, a fitted diode pulls its line low, and the DOS reads the
|
|
31
|
+
# device number off the two at reset, 8 with both fitted. Lifting the
|
|
32
|
+
# one on PB0 adds 1, and the one on PB1 2.
|
|
33
|
+
#
|
|
34
|
+
# The drive takes the controller's lines a host cycle late, as the
|
|
35
|
+
# serial drives do (Engine::SerialLines): latch_host takes them at the
|
|
36
|
+
# end of each host cycle.
|
|
37
|
+
class IEEEPort
|
|
38
|
+
ATNA = 0x01
|
|
39
|
+
NRFD = 0x02
|
|
40
|
+
NDAC = 0x04
|
|
41
|
+
EOI = 0x08
|
|
42
|
+
TALK = 0x10
|
|
43
|
+
DAV = 0x40
|
|
44
|
+
ATN_IN = 0x80
|
|
45
|
+
|
|
46
|
+
attr_reader :bus
|
|
47
|
+
|
|
48
|
+
# The lines the controller pulled, as latch_host took them.
|
|
49
|
+
attr_reader :host
|
|
50
|
+
|
|
51
|
+
def initialize(device:, bus:)
|
|
52
|
+
raise ArgumentError, "a 2031 is device 8 to 11, not #{device}" unless device.between?(8, 11)
|
|
53
|
+
|
|
54
|
+
@diodes = ~(device - 8) & 0x03
|
|
55
|
+
@via = nil
|
|
56
|
+
@host = 0
|
|
57
|
+
self.bus = bus
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# The drive's VIA 1, whose outputs it drives the bus with.
|
|
61
|
+
attr_writer :via
|
|
62
|
+
|
|
63
|
+
def bus=(bus)
|
|
64
|
+
@bus = bus
|
|
65
|
+
latch_host
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Takes the controller's lines for the drive cycles of the next host
|
|
69
|
+
# cycle.
|
|
70
|
+
def latch_host
|
|
71
|
+
@host = @bus.host_lines
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Whether the controller has moved ATN since the last latch_host.
|
|
75
|
+
def atn_moved? = (@bus.host_lines ^ @host).anybits?(IEEEBus::ATN)
|
|
76
|
+
|
|
77
|
+
def atn_low? = @host.anybits?(IEEEBus::ATN)
|
|
78
|
+
|
|
79
|
+
# The data lines, while the transceiver listens.
|
|
80
|
+
def read_a(_lines) = talking? ? 0xff : ~@bus.low_lines(@host) & 0xff
|
|
81
|
+
|
|
82
|
+
# The control lines the transceiver turns towards the drive, and
|
|
83
|
+
# ATN.
|
|
84
|
+
def read_b(_lines)
|
|
85
|
+
low = @bus.low_lines(@host)
|
|
86
|
+
value = 0x7f
|
|
87
|
+
if talking?
|
|
88
|
+
value &= ~NRFD if low.anybits?(IEEEBus::NRFD)
|
|
89
|
+
value &= ~NDAC if low.anybits?(IEEEBus::NDAC)
|
|
90
|
+
value &= ~EOI if low.anybits?(IEEEBus::EOI) && low.anybits?(IEEEBus::ATN)
|
|
91
|
+
else
|
|
92
|
+
value &= ~EOI if low.anybits?(IEEEBus::EOI)
|
|
93
|
+
value &= ~DAV if low.anybits?(IEEEBus::DAV)
|
|
94
|
+
end
|
|
95
|
+
value |= ATN_IN if low.anybits?(IEEEBus::ATN)
|
|
96
|
+
value & jumpers
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# The lines the drive pulls low while ATN is low or not, as bus bits.
|
|
100
|
+
def ieee_low_lines(atn)
|
|
101
|
+
via = @via
|
|
102
|
+
lines = via.port_b_output & jumpers
|
|
103
|
+
low = 0
|
|
104
|
+
if lines.anybits?(TALK)
|
|
105
|
+
low |= ~via.port_a_output & IEEEBus::DIO
|
|
106
|
+
low |= IEEEBus::DAV if lines.nobits?(DAV)
|
|
107
|
+
low |= IEEEBus::EOI if lines.nobits?(EOI) && !atn
|
|
108
|
+
else
|
|
109
|
+
low |= IEEEBus::NRFD if lines.nobits?(NRFD)
|
|
110
|
+
low |= IEEEBus::NDAC if lines.nobits?(NDAC)
|
|
111
|
+
end
|
|
112
|
+
low |= IEEEBus::NRFD | IEEEBus::NDAC if atn != (lines & via.port_b_directions).anybits?(ATNA)
|
|
113
|
+
low
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# The controller's lines as the port latched them, under `prefix`.
|
|
117
|
+
def save_fields(out, prefix) = out.int("#{prefix}host", @host)
|
|
118
|
+
|
|
119
|
+
def load_fields(input, prefix)
|
|
120
|
+
@host = input.int("#{prefix}host", @host)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
private
|
|
124
|
+
|
|
125
|
+
def talking? = @via.port_b_output.anybits?(TALK)
|
|
126
|
+
|
|
127
|
+
# PB0 and PB1 as the jumper diodes leave them: pulled low through
|
|
128
|
+
# each fitted one while CA2 is low.
|
|
129
|
+
def jumpers = @via.ca2_output ? 0xff : ~@diodes & 0xff
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model2031
|
|
6
|
+
# The drive's own fields in a keyed snapshot module (BL-D8): its
|
|
7
|
+
# model, its clock phase and cycle count, the SO pulse due, and the
|
|
8
|
+
# controller's lines as the IEEE-488 port latched them under ieee.*. A
|
|
9
|
+
# save settles the drive first, so it is awake; a load forgets its
|
|
10
|
+
# orbits.
|
|
11
|
+
module KeyedState
|
|
12
|
+
def save_fields(out)
|
|
13
|
+
settle!
|
|
14
|
+
out.text("model", model_name).int("phase", @phase).int("cycles", @cycles)
|
|
15
|
+
out.bool("so_pending", @mechanism.so_pending)
|
|
16
|
+
@serial_port.save_fields(out, "ieee.")
|
|
17
|
+
out
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def load_fields(input)
|
|
21
|
+
settle!
|
|
22
|
+
input.choice("model", [model_name], model_name)
|
|
23
|
+
@phase = input.int("phase", @phase)
|
|
24
|
+
@cycles = input.int("cycles", @cycles)
|
|
25
|
+
@mechanism.so_pending = input.bool("so_pending", @mechanism.so_pending)
|
|
26
|
+
@serial_port.load_fields(input, "ieee.")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The drive as modules of its own, named from `name` (BL-D8 for
|
|
30
|
+
# device 8): its own fields, its RAM, each VIA, the CPU, the
|
|
31
|
+
# mechanism and the disk, when one is in, as the 1541's.
|
|
32
|
+
def save_modules(sections, name)
|
|
33
|
+
sections << Snapshot::Keyed.section(name) { |out| save_fields(out) }
|
|
34
|
+
sections << Snapshot::Keyed.section("#{name}-RAM") { |out| @bus.save_fields(out) }
|
|
35
|
+
sections << Snapshot::Keyed.section("#{name}-VIA1") { |out| @via1.save_fields(out) }
|
|
36
|
+
sections << Snapshot::Keyed.section("#{name}-VIA2") { |out| @via2.save_fields(out) }
|
|
37
|
+
sections << Snapshot::Keyed.section("#{name}-CPU") { |out| @cpu.save_fields(out) }
|
|
38
|
+
sections << Snapshot::Keyed.section("#{name}-MECH") { |out| @mechanism.save_fields(out) }
|
|
39
|
+
disk = @mechanism.disk
|
|
40
|
+
sections << Snapshot::Keyed.section("#{name}-DISK") { |out| disk.save_fields(out) } if disk
|
|
41
|
+
sections
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The drive from its modules, `input` being its own. No disk module
|
|
45
|
+
# means no disk in the drive.
|
|
46
|
+
def load_modules(modules, input, name)
|
|
47
|
+
load_fields(input)
|
|
48
|
+
@bus.load_fields(modules.required("#{name}-RAM"))
|
|
49
|
+
@via1.load_fields(modules.required("#{name}-VIA1"))
|
|
50
|
+
@via2.load_fields(modules.required("#{name}-VIA2"))
|
|
51
|
+
@cpu.load_fields(modules.required("#{name}-CPU"))
|
|
52
|
+
disk = modules.optional("#{name}-DISK")
|
|
53
|
+
mechanism = modules.required("#{name}-MECH")
|
|
54
|
+
@mechanism.load_fields(mechanism,
|
|
55
|
+
disk.nil? ? nil : Model1541::Disk.from_fields(disk, detached: modules.detached?))
|
|
56
|
+
@via1.restored!
|
|
57
|
+
@via2.restored!
|
|
58
|
+
@cpu.restored!
|
|
59
|
+
forget_orbits
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
include KeyedState
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model2031
|
|
6
|
+
# VIA 1, facing the IEEE-488 bus (IEEEPort), a class of its own so
|
|
7
|
+
# its peripheral keeps one type, apart from the serial drives'.
|
|
8
|
+
class IEEEVIA < Chips::VIA
|
|
9
|
+
# DDRB, whose PB0 says whether the VIA drives ATNA (IEEEPort).
|
|
10
|
+
def port_b_directions = @ddrb
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# The drive CPU's bus, decoded as the 1541's (Model1541::Bus), with
|
|
14
|
+
# VIA 1 an IEEEVIA.
|
|
15
|
+
class Bus < Model1541::Bus
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# The drive's 6502, on the drive's Bus.
|
|
19
|
+
class CPU
|
|
20
|
+
include Badline::Chips::CPU::Core
|
|
21
|
+
include Engine::IdleCPU
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/drives/model1541"
|
|
4
|
+
require "badline/drives/ieee_bus"
|
|
5
|
+
require "badline/drives/model2031/parts"
|
|
6
|
+
require "badline/drives/model2031/ieee_port"
|
|
7
|
+
require "badline/drives/model2031/keyed_state"
|
|
8
|
+
|
|
9
|
+
module Badline
|
|
10
|
+
module Drives
|
|
11
|
+
# The 2031 disk drive of the PET and CBM: the 1541's board with an
|
|
12
|
+
# IEEE-488 interface in place of the serial one (Commodore schematic
|
|
13
|
+
# 2031038). A 6502 at 1 MHz runs DOS 2.6 from 16 KB of ROM, with 2 KB
|
|
14
|
+
# of RAM, VIA 1 at $1800 on the IEEE-488 bus (IEEEPort) and VIA 2 at
|
|
15
|
+
# $1C00 running the 1541's GCR Mechanism, decoded as the 1541's.
|
|
16
|
+
#
|
|
17
|
+
# Drives::Engine::Core clocks it against the host and sleeps through
|
|
18
|
+
# the DOS's idle loop, as the 1541's, until ATN moves or a timer is due
|
|
19
|
+
# (Engine::VIAs). The bus reads the lines it pulls without waking it.
|
|
20
|
+
class Model2031
|
|
21
|
+
include Engine::Core
|
|
22
|
+
include Engine::VIAs
|
|
23
|
+
|
|
24
|
+
CLOCK_HZ = 1_000_000
|
|
25
|
+
|
|
26
|
+
# Where the DOS's idle loop starts over (see Drives::Engine::Idle).
|
|
27
|
+
IDLE_LOOP = 0xec4d
|
|
28
|
+
|
|
29
|
+
# The host cycles by which a 2031 switched on with the machine has run
|
|
30
|
+
# its DOS's RAM test and ROM checksums and reads its jumpers.
|
|
31
|
+
BOOT_CYCLES = 1_300_000
|
|
32
|
+
|
|
33
|
+
attr_reader :ieee_bus
|
|
34
|
+
|
|
35
|
+
def model_name = "2031"
|
|
36
|
+
|
|
37
|
+
# +rom+ covers $C000-$FFFF, and defaults to the DOS image in the ROM
|
|
38
|
+
# path. +device+ is the number the jumpers set, 8 to 11.
|
|
39
|
+
def initialize(rom: nil, host_clock_hz: CLOCK_HZ, device: 8, debug: false)
|
|
40
|
+
@device = device
|
|
41
|
+
@ieee_bus = IEEEBus.new
|
|
42
|
+
@serial_port = IEEEPort.new(device:, bus: @ieee_bus)
|
|
43
|
+
@via1 = IEEEVIA.new(start: 0x1800, peripheral: @serial_port)
|
|
44
|
+
@serial_port.via = @via1
|
|
45
|
+
@mechanism = Model1541::Mechanism.new
|
|
46
|
+
@via2 = Model1541::DiskVIA.new(start: 0x1c00, mechanism: @mechanism)
|
|
47
|
+
@bus = Bus.new(rom: rom || ROM.load("dos2031.rom", 0xc000), via1: @via1, via2: @via2)
|
|
48
|
+
@cpu = CPU.new(@bus, debug:)
|
|
49
|
+
@clock_hz = CLOCK_HZ
|
|
50
|
+
@idle_loop = IDLE_LOOP
|
|
51
|
+
@host_clock_hz = host_clock_hz
|
|
52
|
+
@phase = 0
|
|
53
|
+
@cycles = 0
|
|
54
|
+
init_idle(debug)
|
|
55
|
+
@via1.ca1 = false
|
|
56
|
+
@via1.reset!
|
|
57
|
+
connect(@ieee_bus)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Plugs the drive into an IEEE-488 bus, leaving the one it was on.
|
|
61
|
+
def connect(ieee_bus)
|
|
62
|
+
settle!
|
|
63
|
+
@ieee_bus.detach(self)
|
|
64
|
+
@ieee_bus = ieee_bus
|
|
65
|
+
@serial_port.bus = ieee_bus
|
|
66
|
+
ieee_bus.attach(self)
|
|
67
|
+
hear_atn
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# The lines the drive pulls on the bus while ATN is low or not
|
|
71
|
+
# (IEEEPort). They hold still while it sleeps, so reading them leaves
|
|
72
|
+
# it asleep.
|
|
73
|
+
def ieee_low_lines(atn) = @serial_port.ieee_low_lines(atn)
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
# One drive cycle, as the 1541's (Model1541#step): the mechanism, then
|
|
78
|
+
# ATN on VIA 1's CA1, the VIAs and the CPU.
|
|
79
|
+
def step
|
|
80
|
+
so = @mechanism.take_so
|
|
81
|
+
@mechanism.cycle!
|
|
82
|
+
@via1.ca1 = @serial_port.atn_low?
|
|
83
|
+
@via1.cycle!
|
|
84
|
+
@via2.cycle!
|
|
85
|
+
@cpu.irq = @via1.irq? || @via2.irq?
|
|
86
|
+
@cpu.so! if so
|
|
87
|
+
@cpu.cycle!
|
|
88
|
+
@cycles += 1
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
data/lib/badline/drives.rb
CHANGED
data/lib/badline/frontend/app.rb
CHANGED
|
@@ -112,7 +112,7 @@ module Badline
|
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
def handle_key(scancode, down)
|
|
115
|
-
if [Keys::
|
|
115
|
+
if [Keys::F8, Keys::F9, Keys::F10].include?(scancode) || (scancode == Keys::TAB && @controls.tab_toggles?)
|
|
116
116
|
handle_toggle(scancode) if down
|
|
117
117
|
elsif down && @snapshots.key(scancode)
|
|
118
118
|
swap(@snapshots.computer)
|
|
@@ -7,7 +7,9 @@ module Badline
|
|
|
7
7
|
# - The keys: every host key goes to the machine's keyboard, or, as Tab
|
|
8
8
|
# switches, the arrow cluster and space drive one joystick and WASD
|
|
9
9
|
# and left shift the other. The arrows start on joystick 2, and
|
|
10
|
-
# #swap_ports swaps them. The VIC-20's one joystick takes both.
|
|
10
|
+
# #swap_ports swaps them. The VIC-20's one joystick takes both. The
|
|
11
|
+
# PET has no joystick, and its keys go by the character they type
|
|
12
|
+
# (PETKeyboard).
|
|
11
13
|
# - A pot device the pause menu plugs into a C64 control port: a 1351
|
|
12
14
|
# mouse or a pair of paddles, which the host mouse drives. Motion
|
|
13
15
|
# turns the paddles or moves the mouse, and the host's left and right
|
|
@@ -30,6 +32,7 @@ module Badline
|
|
|
30
32
|
@mouse = Input::Mouse1351.new
|
|
31
33
|
@paddles = Input::Paddles.new
|
|
32
34
|
@held = {}
|
|
35
|
+
@pet_keyboard = pet_keyboard(computer)
|
|
33
36
|
end
|
|
34
37
|
|
|
35
38
|
# Routes the input to another machine, the pot device going into its
|
|
@@ -37,6 +40,7 @@ module Badline
|
|
|
37
40
|
def computer=(computer)
|
|
38
41
|
release_all
|
|
39
42
|
@computer = computer
|
|
43
|
+
@pet_keyboard = pet_keyboard(computer)
|
|
40
44
|
attach_pot_device
|
|
41
45
|
end
|
|
42
46
|
|
|
@@ -48,10 +52,16 @@ module Badline
|
|
|
48
52
|
release_all
|
|
49
53
|
end
|
|
50
54
|
|
|
55
|
+
# Switches between the keyboard and the joysticks, on a machine with
|
|
56
|
+
# joysticks.
|
|
51
57
|
def toggle_keys
|
|
52
|
-
self.joystick_mode = !@joystick
|
|
58
|
+
self.joystick_mode = !@joystick if tab_toggles?
|
|
53
59
|
end
|
|
54
60
|
|
|
61
|
+
# Whether Tab switches the keys to the joysticks, or on the PET, which
|
|
62
|
+
# has none, is a key of its own.
|
|
63
|
+
def tab_toggles? = @computer.family != :pet
|
|
64
|
+
|
|
55
65
|
# Plugs in one of POTS, in place of the one before.
|
|
56
66
|
def plug(pot)
|
|
57
67
|
@pot = pot
|
|
@@ -106,6 +116,8 @@ module Badline
|
|
|
106
116
|
# A host key lets go of the key it pressed, though the C128 has
|
|
107
117
|
# switched between C64 and C128 mode since and maps it elsewhere.
|
|
108
118
|
def machine_key(scancode, down)
|
|
119
|
+
return @pet_keyboard.key(scancode, down) if @computer.family == :pet
|
|
120
|
+
|
|
109
121
|
@held[scancode] = host_key(scancode) if down || !@held.key?(scancode)
|
|
110
122
|
key = @held[scancode]
|
|
111
123
|
@held.delete(scancode) unless down
|
|
@@ -147,7 +159,7 @@ module Badline
|
|
|
147
159
|
# A fresh pot device on its port, and none on the other. A machine
|
|
148
160
|
# without the ports takes none.
|
|
149
161
|
def attach_pot_device
|
|
150
|
-
ports = @computer.control_ports
|
|
162
|
+
ports = @computer.family == :pet ? nil : @computer.control_ports
|
|
151
163
|
if ports.nil?
|
|
152
164
|
@pot = :none
|
|
153
165
|
return
|
|
@@ -199,7 +211,15 @@ module Badline
|
|
|
199
211
|
down ? joystick.press(direction) : joystick.release(direction)
|
|
200
212
|
end
|
|
201
213
|
|
|
214
|
+
# The PET's keyboard for a PET, and for any other machine one it never
|
|
215
|
+
# uses.
|
|
216
|
+
def pet_keyboard(computer)
|
|
217
|
+
PETKeyboard.new(computer.keyboard, computer.family == :pet ? computer.model.keyboard : :graphics)
|
|
218
|
+
end
|
|
219
|
+
|
|
202
220
|
def release_all
|
|
221
|
+
return @pet_keyboard.release_all if @computer.family == :pet
|
|
222
|
+
|
|
203
223
|
Keys::ARROWS.each_key { |scancode| @computer.keyboard.release(host_key(scancode)) }
|
|
204
224
|
Keys::WASD.each_key { |scancode| @computer.keyboard.release(host_key(scancode)) }
|
|
205
225
|
Keys::ARROWS.each_value do |direction|
|
|
@@ -7,14 +7,15 @@ module Badline
|
|
|
7
7
|
# second one joystick 1. PadPort holds the mapping.
|
|
8
8
|
#
|
|
9
9
|
# The controllers are polled every frame, and rescanned whenever SDL
|
|
10
|
-
# reports one added or removed.
|
|
10
|
+
# reports one added or removed. The PET has no joysticks, and the
|
|
11
|
+
# controllers steer two that nothing reads.
|
|
11
12
|
class Gamepads
|
|
12
13
|
PORTS = [2, 1].freeze
|
|
13
14
|
|
|
14
15
|
def initialize(computer, verbose)
|
|
15
16
|
@verbose = verbose
|
|
16
17
|
@controllers = []
|
|
17
|
-
@ports = [PadPort.new(computer
|
|
18
|
+
@ports = [PadPort.new(joystick(computer, 2)), PadPort.new(joystick(computer, 1))]
|
|
18
19
|
@buttons = Array.new(PadPort::BUTTON_COUNT, 0)
|
|
19
20
|
@available = SDL.SDL_InitSubSystem(SDL::INIT_GAMECONTROLLER).zero?
|
|
20
21
|
puts "No game controllers: #{SDL.SDL_GetError}" if @verbose && !@available
|
|
@@ -24,7 +25,7 @@ module Badline
|
|
|
24
25
|
# what they hold on the next poll.
|
|
25
26
|
def computer=(computer)
|
|
26
27
|
@ports.each(&:release)
|
|
27
|
-
@ports = [PadPort.new(computer
|
|
28
|
+
@ports = [PadPort.new(joystick(computer, 2)), PadPort.new(joystick(computer, 1))]
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
def rescan
|
|
@@ -51,6 +52,12 @@ module Badline
|
|
|
51
52
|
|
|
52
53
|
private
|
|
53
54
|
|
|
55
|
+
def joystick(computer, port)
|
|
56
|
+
return Input::Joystick.new if computer.family == :pet
|
|
57
|
+
|
|
58
|
+
port == 1 ? computer.joystick1 : computer.joystick2
|
|
59
|
+
end
|
|
60
|
+
|
|
54
61
|
def open_controller(index)
|
|
55
62
|
controller = SDL.SDL_GameControllerOpen(index)
|
|
56
63
|
# SDL returns NULL when the controller won't open, which Spinel's FFI
|
|
@@ -65,6 +65,59 @@ module Badline
|
|
|
65
65
|
79 => :crsr_right, 80 => :crsr_left, 81 => :crsr_down, 82 => :crsr_up, 229 => :lshift, 75 => :none
|
|
66
66
|
}.freeze
|
|
67
67
|
|
|
68
|
+
# The characters the keys other than the letters type on a US
|
|
69
|
+
# layout, unshifted and then shifted, and the keypad's.
|
|
70
|
+
US_CHARACTERS = {
|
|
71
|
+
30 => "1!", 31 => "2@", 32 => "3#", 33 => "4$", 34 => "5%", 35 => "6^", 36 => "7&", 37 => "8*", 38 => "9(",
|
|
72
|
+
39 => "0)", 45 => "-_", 46 => "=+", 47 => "[{", 48 => "]}", 49 => "\\|", 51 => ";:", 52 => "'\"", 53 => "`~",
|
|
73
|
+
54 => ",<", 55 => ".>", 56 => "/?", 84 => "//", 85 => "**", 86 => "--", 87 => "++", 89 => "11", 90 => "22",
|
|
74
|
+
91 => "33", 92 => "44", 93 => "55", 94 => "66", 95 => "77", 96 => "88", 97 => "99", 98 => "00", 99 => "..",
|
|
75
|
+
103 => "=="
|
|
76
|
+
}.freeze
|
|
77
|
+
|
|
78
|
+
# The PET's keys for the characters a host key types, on either
|
|
79
|
+
# keyboard without SHIFT: each has a key of its own on the graphics
|
|
80
|
+
# keyboard, where SHIFT turns it into a graphics character. ↑ and ←
|
|
81
|
+
# are on ^, and on _ and `.
|
|
82
|
+
PET_GRAPHICS_CHARACTERS = {
|
|
83
|
+
"!" => :!, "\"" => :'"', "#" => :"#", "$" => :"$", "%" => :%, "&" => :&, "'" => :"'", "(" => :"(",
|
|
84
|
+
")" => :")", "\\" => :"\\", "^" => :up, "_" => :left, "`" => :left, "/" => :/, ":" => :":", "*" => :*,
|
|
85
|
+
";" => :";", "?" => :"?", "," => :",", "+" => :+, "@" => :"@", "[" => :"[", "]" => :"]", "<" => :<,
|
|
86
|
+
">" => :>, "-" => :-, "=" => :"=", "." => :".", "1" => :"1", "2" => :"2", "3" => :"3", "4" => :"4",
|
|
87
|
+
"5" => :"5", "6" => :"6", "7" => :"7", "8" => :"8", "9" => :"9", "0" => :"0"
|
|
88
|
+
}.freeze
|
|
89
|
+
|
|
90
|
+
# The business keyboard's: the characters it types without SHIFT,
|
|
91
|
+
# then those it types with it.
|
|
92
|
+
PET_BUSINESS_CHARACTERS = {
|
|
93
|
+
"1" => :"1", "2" => :"2", "3" => :"3", "4" => :"4", "5" => :"5", "6" => :"6", "7" => :"7", "8" => :"8",
|
|
94
|
+
"9" => :"9", "0" => :"0", "-" => :-, ":" => :":", ";" => :";", "," => :",", "." => :".", "/" => :/,
|
|
95
|
+
"@" => :"@", "[" => :"[", "]" => :"]", "\\" => :"\\", "^" => :up, "_" => :left, "`" => :left
|
|
96
|
+
}.freeze
|
|
97
|
+
|
|
98
|
+
PET_BUSINESS_SHIFTED = {
|
|
99
|
+
"!" => :"1", "\"" => :"2", "#" => :"3", "$" => :"4", "%" => :"5", "&" => :"6", "'" => :"7", "(" => :"8",
|
|
100
|
+
")" => :"9", "=" => :-, "*" => :":", "+" => :";", "<" => :",", ">" => :".", "?" => :/
|
|
101
|
+
}.freeze
|
|
102
|
+
|
|
103
|
+
# The PET's keys that don't type a character, which keep the host's
|
|
104
|
+
# SHIFT: RETURN, INST/DEL on Backspace, CLR/HOME on Home, RUN/STOP on
|
|
105
|
+
# Page Down, and RVS on Control. Esc is RUN/STOP on the graphics
|
|
106
|
+
# keyboard. The business keyboard's ESC, TAB, REPEAT and keypad are
|
|
107
|
+
# on Esc, Tab, Page Up and the keypad.
|
|
108
|
+
PET_KEYS = {
|
|
109
|
+
40 => :return, 88 => :return, 42 => :delete, 44 => :space, 74 => :clr_home, 78 => :run_stop, 224 => :rvs,
|
|
110
|
+
228 => :rvs
|
|
111
|
+
}.freeze
|
|
112
|
+
|
|
113
|
+
PET_BUSINESS_KEYS = {
|
|
114
|
+
41 => :escape, 43 => :tab, 75 => :repeat, 89 => :kp1, 90 => :kp2, 91 => :kp3, 92 => :kp4, 93 => :kp5,
|
|
115
|
+
94 => :kp6, 95 => :kp7, 96 => :kp8, 97 => :kp9, 98 => :kp0, 99 => :kp_period
|
|
116
|
+
}.freeze
|
|
117
|
+
|
|
118
|
+
# The cursor keys, CRSR ⇔ and CRSR ⇕, each with SHIFT for left or up.
|
|
119
|
+
PET_CURSORS = { RIGHT => :cursor_h, LEFT => :cursor_h, DOWN => :cursor_v, UP => :cursor_v }.freeze
|
|
120
|
+
|
|
68
121
|
ARROWS = { 44 => :fire, 228 => :fire, 79 => :right, 80 => :left, 81 => :down, 82 => :up }.freeze
|
|
69
122
|
WASD = { 225 => :fire, 7 => :right, 4 => :left, 22 => :down, 26 => :up }.freeze
|
|
70
123
|
|
|
@@ -89,6 +142,41 @@ module Badline
|
|
|
89
142
|
# The 264's key for the scancode: one of PLUS4_OTHERS or the C64's.
|
|
90
143
|
def self.plus4_key(scancode) = PLUS4_OTHERS[scancode] || c64_key(scancode)
|
|
91
144
|
|
|
145
|
+
# The PET key for the scancode on the +keyboard+, :graphics or
|
|
146
|
+
# :business, with the host's SHIFT down if +shift+, and how the
|
|
147
|
+
# PET's SHIFT goes with it: :on or :off for a key that types the host
|
|
148
|
+
# key's character or moves the cursor its way only so, and :host to
|
|
149
|
+
# follow the host's. A letter is the PET's letter, and any other key
|
|
150
|
+
# the PET key that types the same character, or :none.
|
|
151
|
+
def self.pet_key(scancode, shift, keyboard)
|
|
152
|
+
return [LETTERS[scancode - 4], :host] if scancode.between?(4, 29)
|
|
153
|
+
|
|
154
|
+
cursor = PET_CURSORS[scancode]
|
|
155
|
+
return [cursor, [LEFT, UP].include?(scancode) ? :on : :off] unless cursor.nil?
|
|
156
|
+
|
|
157
|
+
business = keyboard == :business
|
|
158
|
+
key = business ? PET_BUSINESS_KEYS[scancode] : nil
|
|
159
|
+
key = PET_KEYS[scancode] if key.nil?
|
|
160
|
+
key = :run_stop if key.nil? && scancode == ESCAPE
|
|
161
|
+
return [key, :host] unless key.nil?
|
|
162
|
+
|
|
163
|
+
pet_character(scancode, shift, business)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# The PET key that types the character the scancode types on a US
|
|
167
|
+
# layout, and the SHIFT it takes.
|
|
168
|
+
def self.pet_character(scancode, shift, business)
|
|
169
|
+
characters = US_CHARACTERS[scancode]
|
|
170
|
+
return %i[none host] if characters.nil?
|
|
171
|
+
|
|
172
|
+
character = characters[shift ? 1 : 0]
|
|
173
|
+
unshifted = business ? PET_BUSINESS_CHARACTERS[character] : PET_GRAPHICS_CHARACTERS[character]
|
|
174
|
+
return [unshifted, :off] unless unshifted.nil?
|
|
175
|
+
|
|
176
|
+
shifted = business ? PET_BUSINESS_SHIFTED[character] : nil
|
|
177
|
+
shifted.nil? ? %i[none host] : [shifted, :on]
|
|
178
|
+
end
|
|
179
|
+
|
|
92
180
|
# The character the key types on a US layout: a letter, a capital
|
|
93
181
|
# with `shift`, or a digit, and "" for any other key.
|
|
94
182
|
def self.character(scancode, shift)
|
|
@@ -39,6 +39,7 @@ module Badline
|
|
|
39
39
|
@browsing = kind
|
|
40
40
|
extensions = case kind
|
|
41
41
|
when :cartridge then @media.cartridge_extensions
|
|
42
|
+
when :disk then @media.disk_extensions
|
|
42
43
|
when :program then programs
|
|
43
44
|
else EXTENSIONS.fetch(kind)
|
|
44
45
|
end
|
|
@@ -64,7 +65,7 @@ module Badline
|
|
|
64
65
|
# starts, and says whether it asks. Other files are left alone.
|
|
65
66
|
def drop(path)
|
|
66
67
|
extension = File.extname(path).downcase
|
|
67
|
-
return @media.insert(:disk, path) && false if
|
|
68
|
+
return @media.insert(:disk, path) && false if @media.disk_extensions.include?(extension)
|
|
68
69
|
return @media.insert(:tape, path) && false if EXTENSIONS[:tape].include?(extension)
|
|
69
70
|
return false unless programs.include?(extension)
|
|
70
71
|
|