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
|
@@ -85,6 +85,9 @@ module Badline
|
|
|
85
85
|
end
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
+
# Whether the machine is a C128, whose F8 switches its screens.
|
|
89
|
+
def c128? = @computer.family == :c128
|
|
90
|
+
|
|
88
91
|
private
|
|
89
92
|
|
|
90
93
|
def vic20? = @computer.family == :vic20
|
|
@@ -109,7 +112,8 @@ module Badline
|
|
|
109
112
|
end
|
|
110
113
|
toggle("WRITABLE", :writable, [%w[OFF ON], %i[protect unprotect], media.writable? ? 1 : 0])
|
|
111
114
|
skip
|
|
112
|
-
|
|
115
|
+
drive = @computer.true_drive
|
|
116
|
+
info("DEVICE", drive.nil? ? "FAST LOADING" : "#{drive.model_name}, TRUE DRIVE")
|
|
113
117
|
end
|
|
114
118
|
|
|
115
119
|
def draw_datasette
|
|
@@ -131,7 +135,7 @@ module Badline
|
|
|
131
135
|
info("CARTRIDGE", cartridge.nil? ? "" : cartridge.name)
|
|
132
136
|
row("INSERT...", :insert_cartridge)
|
|
133
137
|
row("REMOVE", :remove_cartridge)
|
|
134
|
-
row("FREEZE", :freeze) if !cartridge.nil? && cartridge.freezer?
|
|
138
|
+
row("FREEZE", :freeze) if !cartridge.nil? && cartridge.freezer? && @computer.family == :c64
|
|
135
139
|
return if @reu_kb.nil?
|
|
136
140
|
|
|
137
141
|
skip
|
|
@@ -166,7 +170,9 @@ module Badline
|
|
|
166
170
|
end
|
|
167
171
|
end
|
|
168
172
|
|
|
173
|
+
# The C128 names its model and the mode it runs in first.
|
|
169
174
|
def draw_power
|
|
175
|
+
info("MACHINE", "#{@computer.model.name.upcase}, #{@computer.mode.to_s.upcase} MODE") if c128?
|
|
170
176
|
row("RESET", :reset)
|
|
171
177
|
row("POWER CYCLE", :power_cycle)
|
|
172
178
|
skip
|
|
@@ -19,7 +19,7 @@ module Badline
|
|
|
19
19
|
|
|
20
20
|
toggle("PORT 1", :port1, [PORT_NAMES, PORT_ACTIONS[0], PORT_DEVICES.index(port_device(1))])
|
|
21
21
|
toggle("PORT 2", :port2, [PORT_NAMES, PORT_ACTIONS[1], PORT_DEVICES.index(port_device(2))])
|
|
22
|
-
return unless draw_keys("C64")
|
|
22
|
+
return unless draw_keys(@computer.family == :c128 ? "C128" : "C64")
|
|
23
23
|
|
|
24
24
|
row("SWAP JOYSTICKS", :swap)
|
|
25
25
|
skip
|
|
@@ -145,6 +145,7 @@ module Badline
|
|
|
145
145
|
draw_sections
|
|
146
146
|
@buttons.plain(@left + 6, @top + HEIGHT - 28, "QUICK OPEN...", :quick_open)
|
|
147
147
|
painter.text(@left + 8, @top + HEIGHT - 12, "F9/ESC: RESUME", DIM)
|
|
148
|
+
painter.text(@left + BODY, @top + HEIGHT - 12, "F6: 40/80 KEY F8: SCREEN", DIM) if @pages.c128?
|
|
148
149
|
@pages.draw(PAGES[@section], @left + BODY, @top + 34)
|
|
149
150
|
end
|
|
150
151
|
|
|
@@ -8,30 +8,44 @@ module Badline
|
|
|
8
8
|
# words, as badline/ffi does on CRuby, so each word carries two
|
|
9
9
|
# neighbouring pixels, the left one in the low half.
|
|
10
10
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
11
|
+
# Each pixel shows `pixel_width` of the window's square pixels wide,
|
|
12
|
+
# or with `dots`, that many pixels share one, as the VDC's dots do. The
|
|
13
|
+
# texture is as wide as the crop, an even number of pixels, and at
|
|
14
|
+
# least MIN_WIDTH window pixels wide, and at least as high as PAL's C64
|
|
15
|
+
# crop, 272 lines, so a C64 shows in the same window whatever its
|
|
16
|
+
# region. A crop with fewer lines or pixels sits in the middle of the
|
|
17
|
+
# texture, between black bands. The VIC-20's 284 by 284 crop fills its
|
|
18
|
+
# own.
|
|
15
19
|
class Screen
|
|
20
|
+
MIN_WIDTH = Region::PAL.crop[2]
|
|
16
21
|
MIN_HEIGHT = Region::PAL.crop[3]
|
|
17
22
|
|
|
18
23
|
attr_reader :pixels, :width, :height
|
|
19
24
|
|
|
20
|
-
def initialize(video, crop)
|
|
25
|
+
def initialize(video, crop, pixel_width: 1, dots: 1)
|
|
21
26
|
@video = video
|
|
22
27
|
@col_offset = crop[0]
|
|
23
28
|
@row_offset = crop[1]
|
|
24
29
|
@lines = crop[3]
|
|
25
|
-
@
|
|
30
|
+
@columns = crop[2] & ~1
|
|
31
|
+
@pixel_width = pixel_width
|
|
32
|
+
@dots = dots
|
|
26
33
|
@stride = video.width.to_i
|
|
34
|
+
@width = [@columns, ((MIN_WIDTH * dots / pixel_width) + 1) & ~1].max
|
|
27
35
|
@height = [@lines, MIN_HEIGHT].max
|
|
28
36
|
@row_words = @width / 2
|
|
37
|
+
@pair_count = @columns / 2
|
|
38
|
+
@margin = (@row_words - @pair_count) / 2
|
|
29
39
|
@band = (@height - @lines) / 2
|
|
30
40
|
palette = video.palette
|
|
31
41
|
@pairs = Array.new(256) { |pair| palette[pair & 0x0f] | (palette[pair >> 4] << 32) }
|
|
32
42
|
@pixels = Array.new(@height * @row_words, 0)
|
|
43
|
+
pack_all
|
|
33
44
|
end
|
|
34
45
|
|
|
46
|
+
# The width in the window's square pixels.
|
|
47
|
+
def window_width = @width * @pixel_width / @dots
|
|
48
|
+
|
|
35
49
|
# The bytes of one row of the texture.
|
|
36
50
|
def row_bytes = @width * 4
|
|
37
51
|
|
|
@@ -50,12 +64,23 @@ module Badline
|
|
|
50
64
|
|
|
51
65
|
private
|
|
52
66
|
|
|
67
|
+
# Repacks every line, as a screen built for a display the chip has
|
|
68
|
+
# already drawn starts with all of it.
|
|
69
|
+
def pack_all
|
|
70
|
+
display = @video.display
|
|
71
|
+
row = 0
|
|
72
|
+
while row < @lines
|
|
73
|
+
pack_row(display, row)
|
|
74
|
+
row += 1
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
53
78
|
def pack_row(display, row)
|
|
54
79
|
pixels = @pixels
|
|
55
80
|
pairs = @pairs
|
|
56
81
|
from = ((row + @row_offset) * @stride) + @col_offset
|
|
57
|
-
to = (row + @band) * @row_words
|
|
58
|
-
last = to + @
|
|
82
|
+
to = ((row + @band) * @row_words) + @margin
|
|
83
|
+
last = to + @pair_count
|
|
59
84
|
while to < last
|
|
60
85
|
pixels[to] = pairs[(display[from] & 0x0f) | ((display[from + 1] & 0x0f) << 4)]
|
|
61
86
|
from += 2
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Frontend
|
|
5
|
+
# The Screen the window shows: the machine's video chip's, or on the
|
|
6
|
+
# C128 the VDC's, two of its dots to a window pixel, once #show_vdc
|
|
7
|
+
# switches to it. Only the chip shown renders. The VDC's display changes
|
|
8
|
+
# size with its registers, and the screen follows it.
|
|
9
|
+
#
|
|
10
|
+
# On the C128, #follow switches to the screen the machine prints to
|
|
11
|
+
# (C128#active_screen) each time it moves there, and #show_vdc's choice
|
|
12
|
+
# holds until the next move.
|
|
13
|
+
class Screens
|
|
14
|
+
# The frames a move holds before #follow takes it. The C128 KERNAL's
|
|
15
|
+
# reset clears the 80 column screen with the editor on it for over a
|
|
16
|
+
# frame.
|
|
17
|
+
FOLLOW_FRAMES = 3
|
|
18
|
+
|
|
19
|
+
attr_reader :screen
|
|
20
|
+
|
|
21
|
+
def initialize(computer)
|
|
22
|
+
@computer = computer
|
|
23
|
+
@vdc_shown = false
|
|
24
|
+
@vdc_shape = []
|
|
25
|
+
forget_screen
|
|
26
|
+
build
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def vdc_shown? = @vdc_shown
|
|
30
|
+
|
|
31
|
+
# Shows another machine's video chip, once #build builds its screen.
|
|
32
|
+
def computer=(computer)
|
|
33
|
+
@computer = computer
|
|
34
|
+
@vdc_shown = false
|
|
35
|
+
forget_screen
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Shows the screen a C128 has moved its output to once the move has
|
|
39
|
+
# held for FOLLOW_FRAMES calls, one a frame. #build then builds it.
|
|
40
|
+
def follow
|
|
41
|
+
return unless @computer.family == :c128
|
|
42
|
+
|
|
43
|
+
vdc = @computer.active_screen == :vdc
|
|
44
|
+
@held = vdc == @seen ? @held + 1 : 1
|
|
45
|
+
@seen = vdc
|
|
46
|
+
return if @held < FOLLOW_FRAMES || vdc == @followed
|
|
47
|
+
|
|
48
|
+
@followed = vdc
|
|
49
|
+
show_vdc(vdc)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Shows the C128's VDC, or with false its VIC-IIe, once #build builds
|
|
53
|
+
# its screen. Other machines have the one screen.
|
|
54
|
+
def show_vdc(shown)
|
|
55
|
+
return unless @computer.family == :c128
|
|
56
|
+
|
|
57
|
+
@vdc_shown = shown
|
|
58
|
+
@computer.vdc_shown = shown
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Whether the screen was built for the other chip, or the VDC's
|
|
62
|
+
# display has changed size or crop since the screen was built for it.
|
|
63
|
+
def stale? = @built_vdc != @vdc_shown || (@vdc_shown && vdc_shape(@computer.vdc) != @vdc_shape)
|
|
64
|
+
|
|
65
|
+
def build
|
|
66
|
+
@built_vdc = @vdc_shown
|
|
67
|
+
if @vdc_shown
|
|
68
|
+
vdc = @computer.vdc
|
|
69
|
+
@vdc_shape = vdc_shape(vdc)
|
|
70
|
+
@screen = Screen.new(vdc, vdc.crop, dots: 2)
|
|
71
|
+
else
|
|
72
|
+
timing = @computer.timing
|
|
73
|
+
@screen = Screen.new(@computer.video, timing.crop, pixel_width: timing.pixel_width)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
# The machine's output counts as on the VIC-IIe's screen, the one
|
|
80
|
+
# shown.
|
|
81
|
+
def forget_screen
|
|
82
|
+
@followed = false
|
|
83
|
+
@seen = false
|
|
84
|
+
@held = 0
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def vdc_shape(vdc) = [vdc.width, vdc.height] + vdc.crop
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -87,6 +87,17 @@ module Badline
|
|
|
87
87
|
section
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
+
# Whether the frame has a display event.
|
|
91
|
+
def display?(frame) = @events.any? { |event| event.frame == frame && event.action == "display" }
|
|
92
|
+
|
|
93
|
+
# The C128 screen the frame's display event shows, vic or vdc, or an
|
|
94
|
+
# empty string without one.
|
|
95
|
+
def display_at(frame)
|
|
96
|
+
chip = ""
|
|
97
|
+
@events.each { |event| chip = event.argument if event.frame == frame && event.action == "display" }
|
|
98
|
+
chip
|
|
99
|
+
end
|
|
100
|
+
|
|
90
101
|
# Whether the pause menu closes at the frame.
|
|
91
102
|
def resume?(frame) = @events.any? { |event| event.frame == frame && event.action == "resume" }
|
|
92
103
|
|
|
@@ -133,9 +144,9 @@ module Badline
|
|
|
133
144
|
hold(event)
|
|
134
145
|
end
|
|
135
146
|
|
|
136
|
-
# The VIC-20
|
|
147
|
+
# The VIC-20 and the C128 have no cartridge with a button yet.
|
|
137
148
|
def freeze(computer, event)
|
|
138
|
-
return
|
|
149
|
+
return unless computer.family == :c64
|
|
139
150
|
|
|
140
151
|
computer.press_cartridge_button
|
|
141
152
|
hold(event)
|
|
@@ -146,7 +157,7 @@ module Badline
|
|
|
146
157
|
end
|
|
147
158
|
|
|
148
159
|
def release(computer, event)
|
|
149
|
-
return computer.release_cartridge_button if event.action == "freeze"
|
|
160
|
+
return computer.release_cartridge_button if event.action == "freeze" && computer.family == :c64
|
|
150
161
|
|
|
151
162
|
name = event.argument
|
|
152
163
|
return computer.release_restore if name == "restore"
|
|
@@ -183,7 +194,7 @@ module Badline
|
|
|
183
194
|
end
|
|
184
195
|
|
|
185
196
|
# Whether there's one in to eject. A VIC-20 has no cartridge to take out yet.
|
|
186
|
-
def ejectable?(computer, what) = (computer.family
|
|
197
|
+
def ejectable?(computer, what) = (computer.family != :vic20 || what != "cartridge") && inserted?(computer, what)
|
|
187
198
|
|
|
188
199
|
def inserted?(computer, what)
|
|
189
200
|
return !computer.datasette.tape.nil? if what == "tape"
|
data/lib/badline/frontend.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "badline/sdl"
|
|
|
4
4
|
require "badline/frontend/boot"
|
|
5
5
|
require "badline/frontend/frame_rate"
|
|
6
6
|
require "badline/frontend/screen"
|
|
7
|
+
require "badline/frontend/screens"
|
|
7
8
|
require "badline/frontend/drive_led"
|
|
8
9
|
require "badline/frontend/keys"
|
|
9
10
|
require "badline/frontend/snapshots"
|
data/lib/badline/iec_bus.rb
CHANGED
|
@@ -16,6 +16,14 @@ module Badline
|
|
|
16
16
|
# also pulls DATA while ATN IN differs from its ATN acknowledge on PB4
|
|
17
17
|
# (an XOR gate), which answers ATN in hardware before the DOS gets to it.
|
|
18
18
|
# SerialPort reads the lines back into VIA 1.
|
|
19
|
+
#
|
|
20
|
+
# Fast serial adds SRQ, which the C64 leaves alone. A C128 and a 1571
|
|
21
|
+
# clock bytes over it from a CIA's CNT, with the bits on DATA from its
|
|
22
|
+
# SP, through buffers each side turns towards the bus to send. A pin low
|
|
23
|
+
# pulls its line low. Each side pushes the lines it pulls when they
|
|
24
|
+
# move: the host through host_fast_lines=, which tells the drives
|
|
25
|
+
# (fast_lines_moved), and a drive through drives_fast_moved!, which
|
|
26
|
+
# tells the host (on_fast_change).
|
|
19
27
|
class IECBus
|
|
20
28
|
# CIA 2 port A
|
|
21
29
|
HOST_ATN_OUT = 0x08
|
|
@@ -29,16 +37,56 @@ module Badline
|
|
|
29
37
|
DRIVE_CLK_OUT = 0x08
|
|
30
38
|
DRIVE_ATNA = 0x10
|
|
31
39
|
|
|
32
|
-
# The lines pulled low, as ATN, CLK and
|
|
40
|
+
# The lines pulled low, as ATN, CLK, DATA and SRQ bits
|
|
33
41
|
ATN = 0x01
|
|
34
42
|
CLK = 0x02
|
|
35
43
|
DATA = 0x04
|
|
44
|
+
SRQ = 0x08
|
|
45
|
+
|
|
46
|
+
# A drive's serial_output bits above VIA 1's port B: its fast serial
|
|
47
|
+
# pins pulling DATA and SRQ.
|
|
48
|
+
DRIVE_FAST_DATA = 0x100
|
|
49
|
+
DRIVE_FAST_SRQ = 0x200
|
|
36
50
|
|
|
37
51
|
attr_reader :drives, :host_lines
|
|
38
52
|
|
|
53
|
+
# The lines the host's fast serial pins pull, as DATA and SRQ bits.
|
|
54
|
+
attr_reader :host_fast_lines
|
|
55
|
+
|
|
39
56
|
def initialize
|
|
40
57
|
@drives = []
|
|
41
58
|
@host_lines = 0
|
|
59
|
+
@host_fast_lines = 0
|
|
60
|
+
@drive_fast_lines = 0
|
|
61
|
+
@on_fast_change = nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The host's fast serial pins moved: +low+ is the lines they pull, as
|
|
65
|
+
# DATA and SRQ bits.
|
|
66
|
+
def host_fast_lines=(low)
|
|
67
|
+
return if low == @host_fast_lines
|
|
68
|
+
|
|
69
|
+
@host_fast_lines = low
|
|
70
|
+
@drives.each(&:fast_lines_moved)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# A drive's fast serial pins moved (serial_output's fast bits).
|
|
74
|
+
def drives_fast_moved!
|
|
75
|
+
low = 0
|
|
76
|
+
@drives.each do |drive|
|
|
77
|
+
output = drive.serial_output
|
|
78
|
+
low |= DATA if output.anybits?(DRIVE_FAST_DATA)
|
|
79
|
+
low |= SRQ if output.anybits?(DRIVE_FAST_SRQ)
|
|
80
|
+
end
|
|
81
|
+
return if low == @drive_fast_lines
|
|
82
|
+
|
|
83
|
+
@drive_fast_lines = low
|
|
84
|
+
@on_fast_change&.call
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Calls the block whenever the drives move their fast serial pins.
|
|
88
|
+
def on_fast_change(&block)
|
|
89
|
+
@on_fast_change = block
|
|
42
90
|
end
|
|
43
91
|
|
|
44
92
|
# The machine pushes the lines it pulls, as port A bits, whenever they
|
|
@@ -58,11 +106,13 @@ module Badline
|
|
|
58
106
|
end
|
|
59
107
|
|
|
60
108
|
# One pass over everything on the bus, for a reader that wants more
|
|
61
|
-
# than one line. +host+ is the C64's port A lines as the
|
|
62
|
-
# them, which a drive takes a cycle late (see
|
|
109
|
+
# than one line, SRQ included. +host+ is the C64's port A lines as the
|
|
110
|
+
# reader sees them, which a drive takes a cycle late (see
|
|
111
|
+
# Drive::SerialLines).
|
|
63
112
|
def low_lines(host = @host_lines)
|
|
64
113
|
atn = host.anybits?(HOST_ATN_OUT)
|
|
65
|
-
low =
|
|
114
|
+
low = @host_fast_lines | @drive_fast_lines
|
|
115
|
+
low |= ATN if atn
|
|
66
116
|
low |= CLK if host.anybits?(HOST_CLK_OUT)
|
|
67
117
|
low |= DATA if host.anybits?(HOST_DATA_OUT)
|
|
68
118
|
i = 0
|
|
@@ -17,7 +17,7 @@ module Badline
|
|
|
17
17
|
def full_filename
|
|
18
18
|
pointer = uint16(@bus.peek(0xbb), @bus.peek(0xbc))
|
|
19
19
|
bytes = Array.new(@bus.peek(0xb7)) do |i|
|
|
20
|
-
@
|
|
20
|
+
@layout.filename_byte(@bus, (pointer + i) & 0xffff)
|
|
21
21
|
end
|
|
22
22
|
Storage.ascii(bytes)
|
|
23
23
|
end
|
|
@@ -18,37 +18,59 @@ module Badline
|
|
|
18
18
|
# primitives' entry points.
|
|
19
19
|
#
|
|
20
20
|
# +banked+ says whether the 6510 port can bank the KERNAL out, and so
|
|
21
|
-
# whether RAM lies under every ROM and the I/O.
|
|
21
|
+
# whether RAM lies under every ROM and the I/O. +mmu+ says the C128's
|
|
22
|
+
# MMU maps the KERNAL instead, and that the file name and the file's
|
|
23
|
+
# bytes lie in the banks the KERNAL keeps at FNBANK and BA. Each
|
|
22
24
|
# serial byte starts a one-shot timer by writing the high byte of its
|
|
23
25
|
# count to +serial_timer+, and the control value TIMER_ONE_SHOT_START
|
|
24
26
|
# to +serial_timer_start+ where the timer has a control register to
|
|
25
27
|
# start it (a CIA's), or nil where the write to the high byte starts it
|
|
26
|
-
# (a VIA's timer 2).
|
|
28
|
+
# (a VIA's timer 2). A KERNAL that times its bytes with a loop has no
|
|
29
|
+
# +serial_timer+. The ROM lets go of each serial line by reading a
|
|
27
30
|
# port, masking the line's bit off and writing it back: ATN at
|
|
28
31
|
# +atn_port+ with +atn_mask+, then the clock and the data line.
|
|
29
32
|
Layout = Data.define(:load, :save, :searching_message, :loading_message, :load_byte_loop, :load_done,
|
|
30
33
|
:file_not_found_exit, :missing_file_name_exit, :saving_message, :clock_release,
|
|
31
34
|
:data_release, :save_done, :talk, :listen, :second, :tksa, :ciout, :untalk,
|
|
32
35
|
:unlisten, :acptr, :banked, :serial_timer, :serial_timer_start, :atn_port, :atn_mask,
|
|
33
|
-
:clock_port, :clock_mask, :data_port, :data_mask) do
|
|
36
|
+
:clock_port, :clock_mask, :data_port, :data_mask, :mmu) do
|
|
34
37
|
# Whether the CPU reads the KERNAL ROM at the trapped addresses: on a
|
|
35
|
-
# banked machine, the 6510 port's banking bits select it
|
|
38
|
+
# banked machine, the 6510 port's banking bits select it, and on the
|
|
39
|
+
# C128 the MMU.
|
|
36
40
|
def kernal_mapped?(bus)
|
|
41
|
+
return bus.system_rom_mapped? if mmu
|
|
42
|
+
|
|
37
43
|
!banked || bus.io_port.kernal?
|
|
38
44
|
end
|
|
39
45
|
|
|
40
46
|
# Stores a loaded file's bytes from +address+ on: on a banked machine
|
|
41
|
-
# into the RAM under whatever is banked in,
|
|
42
|
-
# ROM's byte loop would
|
|
47
|
+
# into the RAM under whatever is banked in, on the C128 into the RAM
|
|
48
|
+
# of the bank at BA, and otherwise where the ROM's byte loop would
|
|
49
|
+
# store them, through the bus.
|
|
43
50
|
def store_file(bus, address, bytes)
|
|
51
|
+
return bus.write_bank(bus.peek(BANK), address, bytes) if mmu
|
|
44
52
|
return bus.ram.write(address, bytes) if banked
|
|
45
53
|
|
|
46
54
|
bytes.each_with_index { |byte, i| bus.poke(address + i, byte) }
|
|
47
55
|
end
|
|
48
56
|
|
|
57
|
+
# The file name's byte at +address+, in the bank at FNBANK on the
|
|
58
|
+
# C128.
|
|
59
|
+
def filename_byte(bus, address)
|
|
60
|
+
mmu ? bus.peek_bank(bus.peek(FILENAME_BANK), address) : bus.peek(address)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# The byte at +address+ a SAVE writes or a VERIFY compares, in the bank
|
|
64
|
+
# at BA on the C128.
|
|
65
|
+
def file_byte(bus, address)
|
|
66
|
+
mmu ? bus.peek_bank(bus.peek(BANK), address) : bus.peek(address)
|
|
67
|
+
end
|
|
68
|
+
|
|
49
69
|
# Starts the timer the ROM times a serial byte with, its count's high
|
|
50
70
|
# byte +timer_high+.
|
|
51
71
|
def time_serial_byte(bus, timer_high)
|
|
72
|
+
return unless serial_timer
|
|
73
|
+
|
|
52
74
|
bus.poke(serial_timer, timer_high)
|
|
53
75
|
start = serial_timer_start
|
|
54
76
|
bus.poke(start, TIMER_ONE_SHOT_START) if start
|
|
@@ -77,6 +99,11 @@ module Badline
|
|
|
77
99
|
# start.
|
|
78
100
|
TIMER_ONE_SHOT_START = 0x19
|
|
79
101
|
|
|
102
|
+
# Where the C128 KERNAL keeps the bank a LOAD or SAVE goes to (BA) and
|
|
103
|
+
# the bank of the file name (FNBANK).
|
|
104
|
+
BANK = 0xc6
|
|
105
|
+
FILENAME_BANK = 0xc7
|
|
106
|
+
|
|
80
107
|
# The C64 KERNAL, revision 3. The SX-64's, 251104-04, differs from it
|
|
81
108
|
# only in bytes none of these addresses reach. It times serial bytes
|
|
82
109
|
# with CIA 1's timer B, and drives the lines from CIA 2's port A
|
|
@@ -89,7 +116,8 @@ module Badline
|
|
|
89
116
|
talk: 0xed09, listen: 0xed0c, second: 0xedb9, tksa: 0xedc7,
|
|
90
117
|
ciout: 0xeddd, untalk: 0xedef, unlisten: 0xedfe, acptr: 0xee13,
|
|
91
118
|
banked: true, serial_timer: 0xdc07, serial_timer_start: 0xdc0f,
|
|
92
|
-
atn_port: 0xdd00, atn_mask: 0xf7, clock_port: 0xdd00, clock_mask: 0xef, data_port: 0xdd00, data_mask: 0xdf
|
|
119
|
+
atn_port: 0xdd00, atn_mask: 0xf7, clock_port: 0xdd00, clock_mask: 0xef, data_port: 0xdd00, data_mask: 0xdf,
|
|
120
|
+
mmu: false
|
|
93
121
|
)
|
|
94
122
|
|
|
95
123
|
# The VIC-20 KERNAL, 901486-07 (PAL). The NTSC one, 901486-06, differs
|
|
@@ -106,7 +134,29 @@ module Badline
|
|
|
106
134
|
talk: 0xee14, listen: 0xee17, second: 0xeec0, tksa: 0xeece,
|
|
107
135
|
ciout: 0xeee4, untalk: 0xeef6, unlisten: 0xef04, acptr: 0xef19,
|
|
108
136
|
banked: false, serial_timer: 0x9129, serial_timer_start: nil,
|
|
109
|
-
atn_port: 0x911f, atn_mask: 0x7f, clock_port: 0x912c, clock_mask: 0xfd, data_port: 0x912c, data_mask: 0xdf
|
|
137
|
+
atn_port: 0x911f, atn_mask: 0x7f, clock_port: 0x912c, clock_mask: 0xfd, data_port: 0x912c, data_mask: 0xdf,
|
|
138
|
+
mmu: false
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# The C128 KERNAL, 318020-05, in C128 mode: the jump table's targets
|
|
142
|
+
# for the serial primitives, ILOAD's and ISAVE's default targets, and
|
|
143
|
+
# the ROM's own tails. Its LOAD prints SEARCHING FOR at $F50F and
|
|
144
|
+
# LOADING or VERIFYING at $F533, retries a byte at $F2CF and returns at
|
|
145
|
+
# $F39B. FILE NOT FOUND leaves through $F685 and MISSING FILE NAME
|
|
146
|
+
# through $F691. SAVE prints SAVING at $F5BC and returns at $F5B3, after
|
|
147
|
+
# the UNLISTEN whose clock and data releases are $E545 and $E557. It
|
|
148
|
+
# drives the lines from CIA 2's port A, and times serial bytes with
|
|
149
|
+
# loops, not a timer.
|
|
150
|
+
C128_LAYOUT = Layout.new(
|
|
151
|
+
load: 0xf26c, save: 0xf54e,
|
|
152
|
+
searching_message: 0xf50f, loading_message: 0xf533, load_byte_loop: 0xf2cf, load_done: 0xf39b,
|
|
153
|
+
file_not_found_exit: 0xf685, missing_file_name_exit: 0xf691,
|
|
154
|
+
saving_message: 0xf5bc, clock_release: 0xe545, data_release: 0xe557, save_done: 0xf5b3,
|
|
155
|
+
talk: 0xe33b, listen: 0xe33e, second: 0xe4d2, tksa: 0xe4e0,
|
|
156
|
+
ciout: 0xe503, untalk: 0xe515, unlisten: 0xe526, acptr: 0xe43e,
|
|
157
|
+
banked: true, serial_timer: nil, serial_timer_start: nil,
|
|
158
|
+
atn_port: 0xdd00, atn_mask: 0xf7, clock_port: 0xdd00, clock_mask: 0xef, data_port: 0xdd00, data_mask: 0xdf,
|
|
159
|
+
mmu: true
|
|
110
160
|
)
|
|
111
161
|
end
|
|
112
162
|
end
|
|
@@ -112,7 +112,7 @@ module Badline
|
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
def verify(addr, payload)
|
|
115
|
-
match = payload.each_with_index.all? { |byte, i| @
|
|
115
|
+
match = payload.each_with_index.all? { |byte, i| @layout.file_byte(@bus, addr + i) == byte }
|
|
116
116
|
@bus.poke(0x90, match ? EOI : EOI | VERIFY_MISMATCH)
|
|
117
117
|
end
|
|
118
118
|
|
|
@@ -71,7 +71,7 @@ module Badline
|
|
|
71
71
|
start = uint16(@bus.peek(0xc1), @bus.peek(0xc2))
|
|
72
72
|
length = (uint16(@bus.peek(0xae), @bus.peek(0xaf)) - start) & 0xffff
|
|
73
73
|
[low_byte(start), high_byte(start)] +
|
|
74
|
-
Array.new(length) { |i| @
|
|
74
|
+
Array.new(length) { |i| @layout.file_byte(@bus, (start + i) & 0xffff) }
|
|
75
75
|
end
|
|
76
76
|
end
|
|
77
77
|
end
|
data/lib/badline/machine.rb
CHANGED
|
@@ -4,7 +4,8 @@ module Badline
|
|
|
4
4
|
# = Machine
|
|
5
5
|
#
|
|
6
6
|
# The machines badline builds by family, as the command line's
|
|
7
|
-
# subcommand names them: the C64, Computer,
|
|
7
|
+
# subcommand names them: the C64, Computer, the VIC-20, Vic20, and the
|
|
8
|
+
# C128 in C64 mode, C128.
|
|
8
9
|
#
|
|
9
10
|
# A machine answers what the front end, Media, the runners and the
|
|
10
11
|
# snapshots call on it:
|
|
@@ -23,7 +24,8 @@ module Badline
|
|
|
23
24
|
# - Media: +ram+, +load_prg+, +type_text+, +mount+, +unmount+,
|
|
24
25
|
# +mounted?+, +datasette+, +attach_cartridge+,
|
|
25
26
|
# +press_cartridge_button+, +release_cartridge_button+,
|
|
26
|
-
# +attach_drive1541+, +drive1541+, and +
|
|
27
|
+
# +attach_drive1541+, +drive1541+, +true_drive+ and +plug_true_drive+
|
|
28
|
+
# for the drive Media::TrueDrive plugs in, and +address_bus+ for its
|
|
27
29
|
# +cartridge+ and +detach_cartridge+.
|
|
28
30
|
# - Snapshots and tests: +snapshot+, +restore+, +save_snapshot+,
|
|
29
31
|
# +install_debug_register+ and +capture_output+.
|
|
@@ -32,16 +34,25 @@ module Badline
|
|
|
32
34
|
# input parts, with its one joystick as both +joystick1+ and +joystick2+
|
|
33
35
|
# and nil for +control_ports+, and of the rest +ram+, +load_prg+,
|
|
34
36
|
# +type_text+, +mount+, +unmount+, +mounted?+, +datasette+, +attach_cartridge+,
|
|
35
|
-
# +attach_drive1541+, +drive1541+, +
|
|
36
|
-
# +capture_output+.
|
|
37
|
+
# +attach_drive1541+, +drive1541+, +true_drive+, +plug_true_drive+,
|
|
38
|
+
# +install_debug_register+ and +capture_output+.
|
|
39
|
+
#
|
|
40
|
+
# The C128 answers all but the cartridge button, and besides +mode+,
|
|
41
|
+
# :c64, as Machine.build builds it, or :c128, +vdc+, the VDC a front
|
|
42
|
+
# end can show in place of +video+ once +vdc_shown=+ renders it,
|
|
43
|
+
# +press_caps_lock+, +release_caps_lock+, +press_display_key+,
|
|
44
|
+
# +release_display_key+, and +attach_drive1571+ and +drive1571+ for the
|
|
45
|
+
# C128D's 1571, its true drive.
|
|
37
46
|
module Machine
|
|
38
|
-
# A new machine of `family`, :c64 or :
|
|
39
|
-
# names. A C64's model is one of Model::ALL, with its SID
|
|
40
|
-
# when not nil and an REU of `reu` K when not nil. A
|
|
41
|
-
# with the RAM expansion `ram` names
|
|
42
|
-
# unexpanded when nil.
|
|
47
|
+
# A new machine of `family`, :c64, :vic20 or :c128, the model of it
|
|
48
|
+
# `model` names. A C64's model is one of Model::ALL, with its SID
|
|
49
|
+
# `sid_model` when not nil and an REU of `reu` K when not nil. A
|
|
50
|
+
# VIC-20's is "pal", with the RAM expansion `ram` names
|
|
51
|
+
# (Vic20::Bus::RAM_CONFIGURATIONS), unexpanded when nil. A C128's is
|
|
52
|
+
# one of C128::Model::ALL, with its SID `sid_model` when not nil.
|
|
43
53
|
def self.build(family, model:, sid_model: nil, reu: nil, ram: nil)
|
|
44
54
|
return vic20(model, ram) if family == :vic20
|
|
55
|
+
return C128.new(model:, sid_model:) if family == :c128
|
|
45
56
|
raise ArgumentError, "no machine family named #{family}" unless family == :c64
|
|
46
57
|
|
|
47
58
|
profile = Model.named(model)
|