badline 0.17.0 → 0.19.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 +193 -0
- data/CLAUDE.md +19 -7
- data/CONTRIBUTING.md +11 -1
- data/README.md +178 -26
- data/doc/library.md +5 -1
- data/doc/media.md +3 -2
- data/doc/pinned-behaviour.md +169 -1
- data/doc/snapshot-format.md +302 -35
- data/doc/snapshots.md +97 -26
- data/doc/vice.md +344 -0
- data/exe/badline-ruby +5 -0
- data/lib/badline/audio/renderer.rb +4 -8
- data/lib/badline/audio/terminal.rb +34 -2
- data/lib/badline/c128/keyed_state.rb +2 -0
- data/lib/badline/c128.rb +5 -0
- data/lib/badline/c64/bus/fittings.rb +6 -0
- data/lib/badline/c64/keyed_state.rb +2 -0
- data/lib/badline/c64.rb +7 -0
- data/lib/badline/cbm2/keyed_state.rb +4 -0
- data/lib/badline/cbm2/p500.rb +9 -3
- data/lib/badline/cbm2.rb +10 -3
- data/lib/badline/chips/cia/timer.rb +2 -2
- data/lib/badline/chips/cpu/core.rb +29 -4
- data/lib/badline/chips/cpu/instruction_set.rb +1 -1
- data/lib/badline/chips/cpu/interrupts.rb +9 -0
- data/lib/badline/chips/cpu/keyed_state.rb +15 -9
- data/lib/badline/chips/cpu/microcode.rb +10 -0
- data/lib/badline/chips/m6809/arithmetic.rb +140 -0
- data/lib/badline/chips/m6809/bus_steps.rb +223 -0
- data/lib/badline/chips/m6809/core.rb +149 -0
- data/lib/badline/chips/m6809/indexed.rb +82 -0
- data/lib/badline/chips/m6809/interrupts.rb +159 -0
- data/lib/badline/chips/m6809/keyed_state.rb +147 -0
- data/lib/badline/chips/m6809/opcodes.rb +159 -0
- data/lib/badline/chips/m6809/operations.rb +258 -0
- data/lib/badline/chips/m6809/plans.rb +143 -0
- data/lib/badline/chips/m6809/registers.rb +82 -0
- data/lib/badline/chips/m6809/stack.rb +140 -0
- data/lib/badline/chips/m6809.rb +13 -0
- data/lib/badline/chips/riot/timer.rb +14 -3
- data/lib/badline/chips/rriot.rb +8 -3
- data/lib/badline/chips/sid/decimator.rb +1 -1
- data/lib/badline/chips/sid/filter.rb +4 -4
- data/lib/badline/chips/sid/keyed_parts.rb +51 -18
- data/lib/badline/chips/sid/waveform.rb +6 -6
- data/lib/badline/chips/sid.rb +6 -2
- data/lib/badline/chips/vic_i/keyed_parts.rb +12 -2
- data/lib/badline/chips/vic_i/light_pen.rb +89 -0
- data/lib/badline/chips/vic_i.rb +91 -23
- data/lib/badline/data_path.rb +3 -3
- data/lib/badline/drives/ieee_models.rb +54 -0
- data/lib/badline/drives/model1001.rb +37 -0
- data/lib/badline/drives/model1541/sector_reader.rb +20 -3
- data/lib/badline/drives/model1541.rb +1 -0
- data/lib/badline/drives/model1551.rb +1 -0
- data/lib/badline/drives/model1571.rb +1 -0
- data/lib/badline/drives/model1581.rb +1 -0
- data/lib/badline/drives/model2031.rb +7 -0
- data/lib/badline/drives/model4040/disk.rb +62 -0
- data/lib/badline/drives/model4040/ip_bus.rb +46 -0
- data/lib/badline/drives/model4040/keyed_state.rb +57 -0
- data/lib/badline/drives/model4040/mechanism.rb +195 -0
- data/lib/badline/drives/model4040/parts.rb +20 -0
- data/lib/badline/drives/model4040.rb +195 -0
- data/lib/badline/drives/model8050/board.rb +79 -0
- data/lib/badline/drives/model8050/dos_board.rb +134 -0
- data/lib/badline/drives/model8050/fdc_bus.rb +7 -0
- data/lib/badline/drives/model8050/idle.rb +148 -31
- data/lib/badline/drives/model8050/keyed_state.rb +5 -2
- data/lib/badline/drives/model8050.rb +32 -130
- data/lib/badline/drives/model9060/idle.rb +169 -0
- data/lib/badline/drives/model9060/keyed_state.rb +123 -0
- data/lib/badline/drives/model9060/parts.rb +98 -0
- data/lib/badline/drives/model9060/sasi_bus.rb +88 -0
- data/lib/badline/drives/model9060/sasi_commands.rb +168 -0
- data/lib/badline/drives/model9060/sasi_target.rb +161 -0
- data/lib/badline/drives/model9060.rb +197 -0
- data/lib/badline/frontend/app.rb +8 -0
- data/lib/badline/frontend/boot.rb +3 -2
- data/lib/badline/frontend/controls.rb +62 -6
- data/lib/badline/frontend/keys.rb +36 -0
- data/lib/badline/frontend/kim1_panel.rb +208 -0
- data/lib/badline/frontend/media_slots.rb +8 -1
- data/lib/badline/frontend/menu/drive_types.rb +56 -0
- data/lib/badline/frontend/menu/menu_dialogs.rb +61 -14
- data/lib/badline/frontend/menu/menu_media.rb +42 -12
- data/lib/badline/frontend/menu/menu_pages.rb +97 -2
- data/lib/badline/frontend/menu/menu_ports.rb +38 -1
- data/lib/badline/frontend/menu/pause_menu.rb +5 -1
- data/lib/badline/frontend/pet_keyboard.rb +23 -1
- data/lib/badline/frontend/screen.rb +1 -1
- data/lib/badline/frontend/screens.rb +40 -2
- data/lib/badline/frontend/teletype_pane.rb +152 -0
- data/lib/badline/frontend/timeline.rb +31 -2
- data/lib/badline/frontend.rb +3 -0
- data/lib/badline/input/joystick.rb +15 -2
- data/lib/badline/kim1/bus.rb +117 -0
- data/lib/badline/kim1/cpu.rb +25 -0
- data/lib/badline/kim1/host_terminal.rb +74 -0
- data/lib/badline/kim1/key_entry.rb +74 -0
- data/lib/badline/kim1/keyed_modules.rb +53 -0
- data/lib/badline/kim1/keyed_state.rb +133 -0
- data/lib/badline/kim1/panel.rb +176 -0
- data/lib/badline/kim1/rriots.rb +28 -0
- data/lib/badline/kim1/sound.rb +29 -0
- data/lib/badline/kim1/tape.rb +118 -0
- data/lib/badline/kim1/teletype.rb +193 -0
- data/lib/badline/kim1.rb +194 -0
- data/lib/badline/machine/ieee_drives.rb +94 -117
- data/lib/badline/machine/keyboard_buffer.rb +20 -3
- data/lib/badline/machine/line_sound.rb +122 -0
- data/lib/badline/machine.rb +49 -15
- data/lib/badline/media/extensions.rb +8 -4
- data/lib/badline/media/kim1_media.rb +150 -0
- data/lib/badline/media/pet_media.rb +3 -1
- data/lib/badline/media/true_drive.rb +52 -45
- data/lib/badline/media/vic20_media.rb +4 -2
- data/lib/badline/media.rb +16 -6
- data/lib/badline/memory.rb +2 -4
- data/lib/badline/options/family.rb +98 -8
- data/lib/badline/options/help.rb +44 -12
- data/lib/badline/options/table.rb +44 -12
- data/lib/badline/options/validation.rb +44 -8
- data/lib/badline/options.rb +27 -8
- data/lib/badline/pet/bus.rb +16 -2
- data/lib/badline/pet/keyboard_ports.rb +9 -2
- data/lib/badline/pet/keyed_state.rb +4 -0
- data/lib/badline/pet/model.rb +11 -2
- data/lib/badline/pet/rom_sets.rb +1 -0
- data/lib/badline/pet/sound.rb +5 -105
- data/lib/badline/pet/superpet/acia.rb +12 -0
- data/lib/badline/pet/superpet/board.rb +221 -0
- data/lib/badline/pet/superpet/dongle.rb +92 -0
- data/lib/badline/pet/superpet/keyed_modules.rb +63 -0
- data/lib/badline/pet/superpet/keyed_parts.rb +76 -0
- data/lib/badline/pet/superpet/m6809.rb +32 -0
- data/lib/badline/pet/superpet/waterloo_traps.rb +199 -0
- data/lib/badline/pet/superpet.rb +221 -0
- data/lib/badline/pet.rb +23 -3
- data/lib/badline/plus4/keyed_state.rb +2 -0
- data/lib/badline/plus4.rb +5 -0
- data/lib/badline/roms/1001/fdc-251257-02a.rom +0 -0
- data/lib/badline/roms/4040/dos-1.2.rom +0 -0
- data/lib/badline/roms/4040/dos-2.1-rev.rom +0 -0
- data/lib/badline/roms/4040/dos-2.1.rom +0 -0
- data/lib/badline/roms/4040/fdc-901466-02.rom +0 -0
- data/lib/badline/roms/4040/fdc-901466-04.rom +0 -0
- data/lib/badline/roms/9060/dos-3.0.rom +0 -0
- data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
- data/lib/badline/roms/README +176 -0
- data/lib/badline/roms/kim1/6530-002.rom +0 -0
- data/lib/badline/roms/kim1/6530-003.rom +0 -0
- data/lib/badline/roms/pet/characters-superpet.rom +0 -0
- data/lib/badline/roms/pet/superpet-a000.rom +0 -0
- data/lib/badline/roms/pet/superpet-c000.rom +0 -0
- data/lib/badline/roms/pet/superpet-e000-50hz.rom +0 -0
- data/lib/badline/roms/pet/superpet-e000.rom +0 -0
- data/lib/badline/snapshot/container.rb +8 -4
- data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
- data/lib/badline/snapshot/field_doc.rb +35 -10
- data/lib/badline/snapshot/image.rb +19 -7
- data/lib/badline/snapshot/kim1_setup.rb +28 -0
- data/lib/badline/snapshot/pet_setup.rb +27 -11
- data/lib/badline/snapshot/storage_setup.rb +4 -1
- data/lib/badline/snapshot/vic20_setup.rb +11 -7
- data/lib/badline/snapshot/vice/pet.rb +14 -7
- data/lib/badline/snapshot/vice/pet_cpu.rb +39 -5
- data/lib/badline/snapshot/vice/pet_crtc.rb +15 -2
- data/lib/badline/snapshot/vice/pet_dongle.rb +72 -0
- data/lib/badline/snapshot/vice/pet_drive8050.rb +188 -68
- data/lib/badline/snapshot/vice/pet_mem.rb +55 -5
- data/lib/badline/snapshot/vice/pet_restore.rb +40 -11
- data/lib/badline/snapshot/vice/vic20.rb +70 -0
- data/lib/badline/snapshot/vice/vic20_cpu.rb +61 -0
- data/lib/badline/snapshot/vice/vic20_mem.rb +85 -0
- data/lib/badline/snapshot/vice/vic20_restore.rb +124 -0
- data/lib/badline/snapshot/vice/vic20_vici.rb +200 -0
- data/lib/badline/snapshot/vice.rb +1 -0
- data/lib/badline/snapshot.rb +8 -4
- data/lib/badline/status.rb +30 -30
- data/lib/badline/storage/d67_image.rb +55 -0
- data/lib/badline/storage/d90_image.rb +180 -0
- data/lib/badline/storage/kim_tape.rb +254 -0
- data/lib/badline/storage/papertape.rb +90 -0
- data/lib/badline/storage.rb +4 -0
- data/lib/badline/support/kim1_checkpoint.rb +41 -0
- data/lib/badline/support/superpet_checkpoint.rb +35 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic20/keyed_modules.rb +6 -3
- data/lib/badline/vic20/keyed_state.rb +6 -4
- data/lib/badline/vic20/port_wiring.rb +11 -0
- data/lib/badline/vic20.rb +30 -12
- data/lib/badline.rb +1 -0
- metadata +85 -1
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Storage
|
|
5
|
+
# A D9060 or D9090 hard disk: 153 cylinders of 32 blocks a head, four
|
|
6
|
+
# heads on the D9060 and six on the D9090. DOS 3.0 numbers a block by
|
|
7
|
+
# its cylinder as the track, from 0, and by head × 32 + sector as the
|
|
8
|
+
# sector. The controller numbers the same block by its place in the
|
|
9
|
+
# image (LBA), and reads and writes it there.
|
|
10
|
+
#
|
|
11
|
+
# The image's size tells the two apart. One of another size, such as an
|
|
12
|
+
# empty file to format, takes the heads it's given, or four, and reads as
|
|
13
|
+
# zeros past its end.
|
|
14
|
+
class D90Image < DiskImage
|
|
15
|
+
CYLINDERS = 153
|
|
16
|
+
SECTORS_PER_HEAD = 32
|
|
17
|
+
|
|
18
|
+
# The heads of each drive, by the size of its image.
|
|
19
|
+
HEADS = { 5_013_504 => 4, 7_520_256 => 6 }.freeze
|
|
20
|
+
|
|
21
|
+
# The BAM entry of a head: a free count and a four-byte bitmap.
|
|
22
|
+
ENTRY = 5
|
|
23
|
+
|
|
24
|
+
# The heads of the drive an image of the size belongs to, or nil for
|
|
25
|
+
# a size neither has.
|
|
26
|
+
def self.heads_for(size) = HEADS[size]
|
|
27
|
+
|
|
28
|
+
attr_reader :heads
|
|
29
|
+
|
|
30
|
+
def initialize(path, read_only: false, bytes: nil, heads: nil)
|
|
31
|
+
super(path, read_only:, bytes:)
|
|
32
|
+
@heads = heads || HEADS.fetch(@bytes.length, 4)
|
|
33
|
+
size = block_count * SECTOR_SIZE
|
|
34
|
+
@bytes.concat(Array.new(size - @bytes.length, 0)) if @bytes.length < size
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def sectors_in(_track) = @heads * SECTORS_PER_HEAD
|
|
38
|
+
|
|
39
|
+
# The blocks the disk holds.
|
|
40
|
+
def block_count = CYLINDERS * sectors_in(0)
|
|
41
|
+
|
|
42
|
+
# The controller's number for the block.
|
|
43
|
+
def lba(track, sector) = (track * sectors_in(track)) + sector
|
|
44
|
+
|
|
45
|
+
def header_block = [76, 20]
|
|
46
|
+
|
|
47
|
+
# Whether the disk holds the blocks from the LBA on.
|
|
48
|
+
def blocks?(lba, count) = lba >= 0 && lba + count <= block_count
|
|
49
|
+
|
|
50
|
+
# The bytes of the blocks from the LBA on, or nil when the disk
|
|
51
|
+
# doesn't hold them all.
|
|
52
|
+
def read_blocks(lba, count)
|
|
53
|
+
@bytes[lba * SECTOR_SIZE, count * SECTOR_SIZE] if blocks?(lba, count)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Writes whole blocks from the LBA on, straight to the host file. It
|
|
57
|
+
# writes nothing when the disk doesn't hold them all. A disk that
|
|
58
|
+
# takes no writes raises WRITE PROTECT ON and keeps its blocks.
|
|
59
|
+
def write_blocks(lba, bytes)
|
|
60
|
+
return unless (bytes.length % SECTOR_SIZE).zero? && blocks?(lba, bytes.length / SECTOR_SIZE)
|
|
61
|
+
raise WriteError, WriteError::WRITE_PROTECT_ON unless writable?
|
|
62
|
+
|
|
63
|
+
store(lba * SECTOR_SIZE, bytes)
|
|
64
|
+
@bytes[lba * SECTOR_SIZE, bytes.length] = bytes
|
|
65
|
+
@entries = nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def bam_block?(track, sector) = block?(track, sector)
|
|
69
|
+
|
|
70
|
+
def block_free?(track, sector)
|
|
71
|
+
block?(track, sector) && @bytes[bitmap_byte(track, sector)][sector % 8] == 1
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def error_tables = {}
|
|
77
|
+
|
|
78
|
+
def store(offset, bytes)
|
|
79
|
+
File.open(@path, "r+b") do |file|
|
|
80
|
+
file.seek(offset)
|
|
81
|
+
file.write(bytes.pack("C*"))
|
|
82
|
+
end
|
|
83
|
+
rescue SystemCallError
|
|
84
|
+
raise WriteError, WriteError::WRITE_PROTECT_ON
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def block?(track, sector)
|
|
88
|
+
track.between?(0, CYLINDERS - 1) && sector.between?(0, sectors_in(track) - 1)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def track_offset(track) = track * sectors_in(track)
|
|
92
|
+
|
|
93
|
+
def clear_blocks
|
|
94
|
+
header = sector_offset(*header_block)
|
|
95
|
+
@bytes.fill(0, 0, header)
|
|
96
|
+
@bytes.fill(0, header + SECTOR_SIZE, (block_count * SECTOR_SIZE) - header - SECTOR_SIZE)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# The header at 76/20 holds the disk name at $06 and the ID at $18,
|
|
100
|
+
# after the link to the directory's first block, 76/10.
|
|
101
|
+
def header_name = 0x06
|
|
102
|
+
def directory_track = 76
|
|
103
|
+
def directory_sector = 10
|
|
104
|
+
def directory_interleave = 3
|
|
105
|
+
def interleave = 10
|
|
106
|
+
def reserved_tracks = [76]
|
|
107
|
+
|
|
108
|
+
# A link to track 0 ends a chain, so files never go there.
|
|
109
|
+
def allocation_order = super - [0]
|
|
110
|
+
|
|
111
|
+
def bam_tracks = 0..(CYLINDERS - 1)
|
|
112
|
+
def header_link = [76, 10]
|
|
113
|
+
def dos_type = "3A"
|
|
114
|
+
def header_tail = 4
|
|
115
|
+
|
|
116
|
+
# The header holds no format letter after its link.
|
|
117
|
+
def write_header(name, id)
|
|
118
|
+
super
|
|
119
|
+
write_at(*header_block, 2, [0])
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# The BAM takes sector 0 of a cylinder for each group of cylinders
|
|
123
|
+
# its entries fit in, twelve on a D9060 and eight on a D9090: the
|
|
124
|
+
# cylinder after the group's first, or the last one for a group of
|
|
125
|
+
# one at the end.
|
|
126
|
+
def tracks_per_bam_block = (SECTOR_SIZE - 16) / (@heads * ENTRY)
|
|
127
|
+
|
|
128
|
+
def bam_block_track(track)
|
|
129
|
+
first = track - (track % tracks_per_bam_block)
|
|
130
|
+
[first + 1, CYLINDERS - 1].min
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def entry_offset(track, sector)
|
|
134
|
+
group = track % tracks_per_bam_block
|
|
135
|
+
sector_offset(bam_block_track(track), 0) + 16 +
|
|
136
|
+
(((group * @heads) + (sector / SECTORS_PER_HEAD)) * ENTRY)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def bitmap_byte(track, sector) = entry_offset(track, sector) + 1 + ((sector % SECTORS_PER_HEAD) / 8)
|
|
140
|
+
|
|
141
|
+
def mark(track, sector, free)
|
|
142
|
+
return if !block?(track, sector) || block_free?(track, sector) == free
|
|
143
|
+
|
|
144
|
+
entry = entry_offset(track, sector)
|
|
145
|
+
byte = bitmap_byte(track, sector)
|
|
146
|
+
bit = 1 << (sector % 8)
|
|
147
|
+
@bytes[byte] = free ? @bytes[byte] | bit : @bytes[byte] & ~bit
|
|
148
|
+
@bytes[entry] = (@bytes[entry] + (free ? 1 : -1)) & 0xff
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def free_track(track)
|
|
152
|
+
@heads.times do |head|
|
|
153
|
+
@bytes[entry_offset(track, head * SECTORS_PER_HEAD), ENTRY] = [SECTORS_PER_HEAD, 0xff, 0xff, 0xff, 0xff]
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def blocks_free
|
|
158
|
+
(bam_tracks.to_a - reserved_tracks).sum do |track|
|
|
159
|
+
(0...@heads).sum { |head| @bytes[entry_offset(track, head * SECTORS_PER_HEAD)] }
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Block 0/0 points at the bad-block list in 0/1, the directory, the
|
|
164
|
+
# header and the BAM's first block, and holds the ID. Each BAM block
|
|
165
|
+
# links the next and the one before it, $FF $FF at either end, and
|
|
166
|
+
# names the first cylinder it covers and the one past its last.
|
|
167
|
+
def bam_headers(id)
|
|
168
|
+
groups = bam_tracks.each_slice(tracks_per_bam_block).to_a
|
|
169
|
+
blocks = groups.map { |tracks| bam_block_track(tracks.first) }
|
|
170
|
+
bam = groups.each_with_index.map do |tracks, i|
|
|
171
|
+
following = i == groups.length - 1 ? [0xff, 0xff] : [blocks[i + 1], 0]
|
|
172
|
+
previous = i.zero? ? [0xff, 0xff] : [blocks[i - 1], 0]
|
|
173
|
+
[blocks[i], 0, [*following, *previous, tracks.first, tracks.last + 1]]
|
|
174
|
+
end
|
|
175
|
+
[[0, 0, [0, 1, 0, 0xff, *header_link, *header_block, blocks.first, 0, *id]],
|
|
176
|
+
[0, 1, Array.new(SECTOR_SIZE, 0xff)], *bam]
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Storage
|
|
5
|
+
# A KIM-1 cassette, held as the times its signal crosses zero, in
|
|
6
|
+
# microseconds from the start of the tape, which are the KIM-1's
|
|
7
|
+
# cycles. The KIM-1 writes the tape as a square wave on U2's PB7, each
|
|
8
|
+
# bit 3,700 Hz then 2,400 Hz, and reads it back through a 565 PLL and a
|
|
9
|
+
# 311 comparator, which hold PB7 high through the 2,400 Hz tone and low
|
|
10
|
+
# through the 3,700 Hz one (User Manual, appendix E). edges gives the
|
|
11
|
+
# times PB7 changes as the tape plays.
|
|
12
|
+
#
|
|
13
|
+
# A tape reads from a .wav file, 8-bit or 16-bit PCM, its first
|
|
14
|
+
# channel only, and writes one back as a square wave.
|
|
15
|
+
class KIMTape
|
|
16
|
+
class FormatError < StandardError; end
|
|
17
|
+
|
|
18
|
+
# A period longer than this many microseconds is the 2,400 Hz tone:
|
|
19
|
+
# the split between the tones, at the geometric mean of the two.
|
|
20
|
+
SPLIT = 336
|
|
21
|
+
|
|
22
|
+
# The time constants, in seconds, of the detector's DC level and of
|
|
23
|
+
# its envelope, which sets the hysteresis.
|
|
24
|
+
DC_TIME = 0.002
|
|
25
|
+
ENVELOPE_TIME = 0.005
|
|
26
|
+
|
|
27
|
+
# The signal crosses the DC level once it swings this fraction of the
|
|
28
|
+
# envelope past it, and not for an envelope smaller than SILENCE, of
|
|
29
|
+
# full scale.
|
|
30
|
+
HYSTERESIS = 0.3
|
|
31
|
+
SILENCE = 0.03
|
|
32
|
+
|
|
33
|
+
# A gap this many microseconds long between crossings plays as
|
|
34
|
+
# silence in the .wav a tape writes.
|
|
35
|
+
GAP = 2_000
|
|
36
|
+
|
|
37
|
+
# The file the tape reads from and writes back to, or "" for none.
|
|
38
|
+
attr_reader :path, :crossings
|
|
39
|
+
|
|
40
|
+
# The tape in the .wav file at `path`.
|
|
41
|
+
def self.read(path) = new(path, crossings: from_wav(File.binread(path).bytes))
|
|
42
|
+
|
|
43
|
+
# A blank tape at `path`, written to the host file.
|
|
44
|
+
def self.create(path)
|
|
45
|
+
tape = new(path)
|
|
46
|
+
tape.save
|
|
47
|
+
tape
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The zero crossings of the .wav file in `bytes`.
|
|
51
|
+
def self.from_wav(bytes)
|
|
52
|
+
raise FormatError, "Not a RIFF WAVE file" unless bytes[0, 4] == "RIFF".bytes && bytes[8, 4] == "WAVE".bytes
|
|
53
|
+
|
|
54
|
+
format = nil
|
|
55
|
+
pos = 12
|
|
56
|
+
while pos + 8 <= bytes.length
|
|
57
|
+
id = bytes[pos, 4].pack("C*")
|
|
58
|
+
size = little_endian(bytes, pos + 4, 4)
|
|
59
|
+
format = bytes[pos + 8, 16] if id == "fmt "
|
|
60
|
+
return Detector.new(Format.new(format)).crossings(bytes, pos + 8, size) if id == "data" && format
|
|
61
|
+
|
|
62
|
+
pos += 8 + size + (size & 1)
|
|
63
|
+
end
|
|
64
|
+
raise FormatError, "No PCM data in the WAVE file"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.little_endian(bytes, pos, count)
|
|
68
|
+
value = 0
|
|
69
|
+
count.times { |i| value |= bytes[pos + i].to_i << (8 * i) }
|
|
70
|
+
value
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def initialize(path = "", crossings: [])
|
|
74
|
+
@path = path
|
|
75
|
+
@crossings = crossings
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# The time of the last crossing.
|
|
79
|
+
def duration = @crossings.empty? ? 0 : @crossings.last
|
|
80
|
+
|
|
81
|
+
# Erases the tape from `time` on, as a deck recording from there
|
|
82
|
+
# does.
|
|
83
|
+
def erase_from(time)
|
|
84
|
+
@crossings.pop while !@crossings.empty? && @crossings.last >= time
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Records a crossing at `time`, after the last one.
|
|
88
|
+
def record(time)
|
|
89
|
+
@crossings << time
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# The times PB7 changes as the tape plays, from high at its start: to
|
|
93
|
+
# low where a period is the 3,700 Hz tone and back to high where one
|
|
94
|
+
# is the 2,400 Hz tone. Each period runs over two crossings, and two
|
|
95
|
+
# in a row must agree before PB7 follows, so that one crossing out of
|
|
96
|
+
# place doesn't split a bit. The period that straddles the change
|
|
97
|
+
# lies between the tones, so PB7 changes half way through it.
|
|
98
|
+
def edges
|
|
99
|
+
crossings = @crossings
|
|
100
|
+
edges = []
|
|
101
|
+
high = true
|
|
102
|
+
disagree = 0
|
|
103
|
+
i = 2
|
|
104
|
+
while i < crossings.length
|
|
105
|
+
low_tone = crossings[i] - crossings[i - 2] > SPLIT
|
|
106
|
+
if low_tone == high
|
|
107
|
+
disagree = 0
|
|
108
|
+
else
|
|
109
|
+
disagree += 1
|
|
110
|
+
if disagree == 2
|
|
111
|
+
high = low_tone
|
|
112
|
+
edges << ((crossings[i - 3] + crossings[i - 2]) / 2)
|
|
113
|
+
disagree = 0
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
i += 1
|
|
117
|
+
end
|
|
118
|
+
edges
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Writes the tape back to its host file.
|
|
122
|
+
def save
|
|
123
|
+
write_wav(@path)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Writes the tape to `path` as a .wav file, a square wave of `bits`
|
|
127
|
+
# (8 or 16) at `rate` samples a second.
|
|
128
|
+
def write_wav(path, rate: 44_100, bits: 8)
|
|
129
|
+
samples = square_wave(rate, bits == 8 ? 96 : 24_000)
|
|
130
|
+
bytes = bits == 8 ? samples.map { |sample| sample + 128 } : samples.flat_map { |s| [s & 0xff, (s >> 8) & 0xff] }
|
|
131
|
+
File.binwrite(path, (wav_header(rate, bits, bytes.length) + bytes).pack("C*"))
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
# The tape as samples of `amplitude`: silent up to the first
|
|
137
|
+
# crossing, after a GAP and at the end, and otherwise each crossing
|
|
138
|
+
# turning the wave over.
|
|
139
|
+
def square_wave(rate, amplitude)
|
|
140
|
+
samples = []
|
|
141
|
+
crossings = @crossings
|
|
142
|
+
count = crossings.empty? ? 0 : (((crossings.last + GAP) * rate) / 1_000_000) + 1
|
|
143
|
+
index = 0
|
|
144
|
+
level = -amplitude
|
|
145
|
+
count.times do |n|
|
|
146
|
+
time = (n * 1_000_000) / rate
|
|
147
|
+
while index < crossings.length && crossings[index] <= time
|
|
148
|
+
level = -level
|
|
149
|
+
index += 1
|
|
150
|
+
end
|
|
151
|
+
samples << (index.zero? || time - crossings[index - 1] > GAP ? 0 : level)
|
|
152
|
+
end
|
|
153
|
+
samples
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def wav_header(rate, bits, size)
|
|
157
|
+
bytes_per_sample = bits / 8
|
|
158
|
+
"RIFF".bytes + le(36 + size, 4) + "WAVEfmt ".bytes + le(16, 4) + le(1, 2) + le(1, 2) +
|
|
159
|
+
le(rate, 4) + le(rate * bytes_per_sample, 4) + le(bytes_per_sample, 2) + le(bits, 2) +
|
|
160
|
+
"data".bytes + le(size, 4)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def le(value, count) = Array.new(count) { |i| (value >> (8 * i)) & 0xff }
|
|
164
|
+
|
|
165
|
+
# A WAVE file's fmt chunk: PCM, 8-bit unsigned or 16-bit signed.
|
|
166
|
+
class Format
|
|
167
|
+
attr_reader :channels, :rate, :bits
|
|
168
|
+
|
|
169
|
+
def initialize(chunk)
|
|
170
|
+
tag = KIMTape.little_endian(chunk, 0, 2)
|
|
171
|
+
@channels = KIMTape.little_endian(chunk, 2, 2)
|
|
172
|
+
@rate = KIMTape.little_endian(chunk, 4, 4)
|
|
173
|
+
@bits = KIMTape.little_endian(chunk, 14, 2)
|
|
174
|
+
return if [1, 0xfffe].include?(tag) && [8, 16].include?(@bits) && @channels.positive?
|
|
175
|
+
|
|
176
|
+
raise FormatError, "Only 8-bit and 16-bit PCM WAVE files are supported"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def frame_size = @channels * @bits / 8
|
|
180
|
+
|
|
181
|
+
# The first channel's sample at `pos`, from -1 to 1.
|
|
182
|
+
def level_at(bytes, pos)
|
|
183
|
+
return (bytes[pos] - 128) / 128.0 if @bits == 8
|
|
184
|
+
|
|
185
|
+
value = bytes[pos] | (bytes[pos + 1] << 8)
|
|
186
|
+
(value >= 0x8000 ? value - 0x10000 : value) / 32_768.0
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Finds where a recording crosses zero. A cassette's signal wanders
|
|
191
|
+
# off its centre and fades, so the detector follows its DC level and
|
|
192
|
+
# its envelope, and counts a crossing only once the signal swings past
|
|
193
|
+
# the DC level by HYSTERESIS of the envelope, timing it where it
|
|
194
|
+
# crossed the DC level, between two samples.
|
|
195
|
+
class Detector
|
|
196
|
+
def initialize(format)
|
|
197
|
+
@format = format
|
|
198
|
+
rate = format.rate
|
|
199
|
+
@dc_step = 1.0 / (rate * DC_TIME)
|
|
200
|
+
@decay = Math.exp(-1.0 / (rate * ENVELOPE_TIME))
|
|
201
|
+
@scale = 1_000_000.0 / rate
|
|
202
|
+
@dc = @envelope = @previous = @rose = @fell = 0.0
|
|
203
|
+
@high = false
|
|
204
|
+
@found = []
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# The crossings, in microseconds, of the samples in `bytes` from
|
|
208
|
+
# `pos`, `size` bytes of them.
|
|
209
|
+
def crossings(bytes, pos, size)
|
|
210
|
+
format = @format
|
|
211
|
+
frame = format.frame_size
|
|
212
|
+
last = [pos + size, bytes.length].min - frame
|
|
213
|
+
n = 0
|
|
214
|
+
while pos <= last
|
|
215
|
+
follow(format.level_at(bytes, pos), n)
|
|
216
|
+
pos += frame
|
|
217
|
+
n += 1
|
|
218
|
+
end
|
|
219
|
+
@found
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
private
|
|
223
|
+
|
|
224
|
+
# Takes the sample `sample`, the `index`th.
|
|
225
|
+
def follow(sample, index)
|
|
226
|
+
@dc += (sample - @dc) * @dc_step
|
|
227
|
+
level = sample - @dc
|
|
228
|
+
@envelope *= @decay
|
|
229
|
+
@envelope = level.abs if level.abs > @envelope
|
|
230
|
+
previous = @previous
|
|
231
|
+
if previous <= 0 && level.positive?
|
|
232
|
+
@rose = index - 1 + (previous / (previous - level))
|
|
233
|
+
elsif previous >= 0 && level.negative?
|
|
234
|
+
@fell = index - 1 + (previous / (previous - level))
|
|
235
|
+
end
|
|
236
|
+
@previous = level
|
|
237
|
+
swing(level) if @envelope > SILENCE
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Turns over at a `level` far enough past the DC level.
|
|
241
|
+
def swing(level)
|
|
242
|
+
swing = @envelope * HYSTERESIS
|
|
243
|
+
if !@high && level > swing
|
|
244
|
+
@high = true
|
|
245
|
+
@found << (@rose * @scale).round
|
|
246
|
+
elsif @high && level < -swing
|
|
247
|
+
@high = false
|
|
248
|
+
@found << (@fell * @scale).round
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Storage
|
|
5
|
+
# A MOS papertape, the .ptp or .pap the KIM-1 monitor's Q command
|
|
6
|
+
# punches and its L command reads (User Manual, appendix F). Each
|
|
7
|
+
# record is a semicolon, then in hex its count of data bytes, its
|
|
8
|
+
# address, high byte first, the bytes and a 16-bit checksum, the sum
|
|
9
|
+
# of every byte before it, high byte first. The last record has a
|
|
10
|
+
# count of 0, the number of records before it where the address goes,
|
|
11
|
+
# and its checksum, which the monitor checks without counting the
|
|
12
|
+
# records. What comes between records, a line's end, NULs
|
|
13
|
+
# and the XOFF that ends a dump, the monitor skips, as this does.
|
|
14
|
+
class Papertape
|
|
15
|
+
class FormatError < StandardError; end
|
|
16
|
+
|
|
17
|
+
# One record's data: its bytes and where they go.
|
|
18
|
+
class Record
|
|
19
|
+
attr_reader :address, :bytes
|
|
20
|
+
|
|
21
|
+
def initialize(address, bytes)
|
|
22
|
+
@address = address
|
|
23
|
+
@bytes = bytes
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
HEX = "0123456789ABCDEFabcdef"
|
|
28
|
+
|
|
29
|
+
# The Records, in the tape's order.
|
|
30
|
+
attr_reader :records
|
|
31
|
+
|
|
32
|
+
# The tape's text from its start to the last record's end, which a
|
|
33
|
+
# teletype sends for the L command.
|
|
34
|
+
attr_reader :text
|
|
35
|
+
|
|
36
|
+
def self.read(path) = new(File.binread(path))
|
|
37
|
+
|
|
38
|
+
def initialize(text)
|
|
39
|
+
@records = []
|
|
40
|
+
@text = text.b
|
|
41
|
+
@position = 0
|
|
42
|
+
read_records
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Where the first record goes.
|
|
46
|
+
def start_address = @records.first.address
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def read_records
|
|
51
|
+
loop do
|
|
52
|
+
start = @text.index(";", @position)
|
|
53
|
+
raise FormatError, "no last record" if start.nil?
|
|
54
|
+
|
|
55
|
+
@position = start + 1
|
|
56
|
+
count = byte
|
|
57
|
+
address = word
|
|
58
|
+
data = Array.new(count) { byte }
|
|
59
|
+
check(count, address, data)
|
|
60
|
+
return finish if count.zero?
|
|
61
|
+
|
|
62
|
+
@records << Record.new(address, data)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def check(count, address, data)
|
|
67
|
+
sum = (count + (address >> 8) + (address & 0xff) + data.sum) & 0xffff
|
|
68
|
+
raise FormatError, "bad checksum in the record at $#{address.to_s(16).rjust(4, '0')}" unless word == sum
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def finish
|
|
72
|
+
raise FormatError, "no records" if @records.empty?
|
|
73
|
+
|
|
74
|
+
@text = @text[0, @position]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def byte
|
|
78
|
+
digits = @text[@position, 2].to_s
|
|
79
|
+
unless digits.size == 2 && HEX.include?(digits[0]) && HEX.include?(digits[1])
|
|
80
|
+
raise FormatError, "not a hex byte at offset #{@position}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
@position += 2
|
|
84
|
+
digits.to_i(16)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def word = (byte << 8) | byte
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
data/lib/badline/storage.rb
CHANGED
|
@@ -5,13 +5,17 @@ require "badline/storage/p00"
|
|
|
5
5
|
require "badline/storage/host_directory"
|
|
6
6
|
require "badline/storage/disk_image"
|
|
7
7
|
require "badline/storage/d64_image"
|
|
8
|
+
require "badline/storage/d67_image"
|
|
8
9
|
require "badline/storage/d71_image"
|
|
9
10
|
require "badline/storage/d81_image"
|
|
10
11
|
require "badline/storage/d80_image"
|
|
11
12
|
require "badline/storage/d82_image"
|
|
13
|
+
require "badline/storage/d90_image"
|
|
12
14
|
require "badline/storage/g64_image"
|
|
13
15
|
require "badline/storage/t64"
|
|
14
16
|
require "badline/storage/tap"
|
|
17
|
+
require "badline/storage/papertape"
|
|
18
|
+
require "badline/storage/kim_tape"
|
|
15
19
|
require "badline/storage/crt_file"
|
|
16
20
|
require "badline/storage/unavailable"
|
|
17
21
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Support
|
|
5
|
+
# A Checkpoint of a KIM-1: its CPU, the 1K of RAM, each 6530's RAM,
|
|
6
|
+
# ports and timer, the panel's held key and what its digits show, and
|
|
7
|
+
# what the teletype has printed and its line's level.
|
|
8
|
+
module KIM1Checkpoint
|
|
9
|
+
COMPONENTS = %w[cpu ram u2 u3 panel tty].freeze
|
|
10
|
+
|
|
11
|
+
def self.take(machine)
|
|
12
|
+
ram = machine.ram
|
|
13
|
+
Checkpoint.new(machine.cycles, [
|
|
14
|
+
Checkpoint.fnv1a(Checkpoint.cpu_state(machine.cpu)),
|
|
15
|
+
Checkpoint.fnv1a(Checkpoint.ram(ram, ram.length)),
|
|
16
|
+
Checkpoint.fnv1a(rriot_state(machine.u2)),
|
|
17
|
+
Checkpoint.fnv1a(rriot_state(machine.u3)),
|
|
18
|
+
Checkpoint.fnv1a(panel_state(machine.panel)),
|
|
19
|
+
Checkpoint.fnv1a(teletype_state(machine.teletype))
|
|
20
|
+
], COMPONENTS)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.parse(line) = Checkpoint.parse(line, COMPONENTS)
|
|
24
|
+
|
|
25
|
+
def self.rriot_state(rriot) = rriot.idle_state.map { |value| bit(value) } + [rriot.timer.counter]
|
|
26
|
+
|
|
27
|
+
def self.panel_state(panel)
|
|
28
|
+
held = panel.held
|
|
29
|
+
[held.nil? ? -1 : KIM1::Panel::KEYS.index(held), *panel.display]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.teletype_state(teletype) = teletype.output.bytes + [teletype.line_high? ? 1 : 0]
|
|
33
|
+
|
|
34
|
+
def self.bit(value)
|
|
35
|
+
return value if value.is_a?(Integer)
|
|
36
|
+
|
|
37
|
+
value ? 1 : 0
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "pet_checkpoint"
|
|
4
|
+
|
|
5
|
+
module Badline
|
|
6
|
+
module Support
|
|
7
|
+
# A Checkpoint of a SuperPET: the PET's components (PETCheckpoint), the
|
|
8
|
+
# board's latches and bank RAM, the 6702, the ACIA, the 6809 with
|
|
9
|
+
# whether it has the bus, and the traps on the Waterloo ROM's IEEE-488
|
|
10
|
+
# routines, empty without a disk on device 8.
|
|
11
|
+
module SuperPETCheckpoint
|
|
12
|
+
COMPONENTS = (PETCheckpoint::COMPONENTS + %w[board dongle acia m6809 waterloo]).freeze
|
|
13
|
+
|
|
14
|
+
def self.take(machine)
|
|
15
|
+
pet = PETCheckpoint.take(machine)
|
|
16
|
+
board = machine.board
|
|
17
|
+
dongle = machine.dongle
|
|
18
|
+
acia = machine.acia
|
|
19
|
+
Checkpoint.new(machine.cycles, pet.digests + [
|
|
20
|
+
Checkpoint.fnv1a([board.bank_latch, board.system_latch, board.socket, *board.cells]),
|
|
21
|
+
Checkpoint.fnv1a(dongle.registers + [dongle.latch, dongle.shift_at]),
|
|
22
|
+
Checkpoint.fnv1a([acia.command, acia.control, acia.status, acia.due]),
|
|
23
|
+
Checkpoint.fnv1a(m6809_state(machine.m6809) + [machine.m6809_running? ? 1 : 0]),
|
|
24
|
+
Checkpoint.fnv1a(machine.waterloo_traps&.state || [])
|
|
25
|
+
], COMPONENTS)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.m6809_state(cpu)
|
|
29
|
+
[cpu.a, cpu.b, cpu.x, cpu.y, cpu.u, cpu.s, cpu.pc, cpu.dp, cpu.cc, cpu.cycles]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.parse(line) = Checkpoint.parse(line, COMPONENTS)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/badline/version.rb
CHANGED
|
@@ -28,7 +28,7 @@ module Badline
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
# Raises Snapshot::FormatError for the modules of a VIC-20 with
|
|
31
|
-
# another RAM expansion.
|
|
31
|
+
# another RAM expansion or of another model.
|
|
32
32
|
def load_modules(modules)
|
|
33
33
|
machine = modules.machine
|
|
34
34
|
check_snapshot_setup(Snapshot::Vic20Setup.read_fields(machine))
|
|
@@ -62,9 +62,12 @@ module Badline
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def check_snapshot_setup(setup)
|
|
65
|
-
|
|
65
|
+
unless setup.ram == @ram_configuration
|
|
66
|
+
raise Snapshot::FormatError, "the snapshot is of a VIC-20 with #{setup.ram} RAM, not #{@ram_configuration}"
|
|
67
|
+
end
|
|
68
|
+
return if setup.model == @model
|
|
66
69
|
|
|
67
|
-
raise Snapshot::FormatError, "the snapshot is of a
|
|
70
|
+
raise Snapshot::FormatError, "the snapshot is of a #{setup.model.upcase} VIC-20, not a #{@model.upcase} one"
|
|
68
71
|
end
|
|
69
72
|
end
|
|
70
73
|
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
module Badline
|
|
4
4
|
class Vic20
|
|
5
5
|
# The machine's own fields in a keyed snapshot module (Snapshot::Keyed,
|
|
6
|
-
# BL-MACHINE): the family, its RAM expansion by name, the
|
|
7
|
-
# the RESTORE key's NMI, and the host's typing still to
|
|
8
|
-
# handlers still to run under host.*.
|
|
6
|
+
# BL-MACHINE): the family, its RAM expansion and model by name, the
|
|
7
|
+
# cycle count, the RESTORE key's NMI, and the host's typing still to
|
|
8
|
+
# feed and on_init handlers still to run under host.*.
|
|
9
9
|
module KeyedState
|
|
10
10
|
# How many more on_init handlers the machine a snapshot was taken of had
|
|
11
11
|
# yet to run than this one has. Nil until the machine is restored.
|
|
@@ -20,10 +20,11 @@ module Badline
|
|
|
20
20
|
|
|
21
21
|
def save_machine_fields(out)
|
|
22
22
|
out.text("family", "vic20").text("written_by", VERSION)
|
|
23
|
-
Snapshot::Vic20Setup.new(ram: @ram_configuration).write_fields(out)
|
|
23
|
+
Snapshot::Vic20Setup.new(ram: @ram_configuration, model: @model).write_fields(out)
|
|
24
24
|
out.int("cycles", @cycles).bool("nmi_asserted", @nmi_asserted)
|
|
25
25
|
pending = @pending_keys
|
|
26
26
|
out.ints("host.pending_keys", pending) if pending
|
|
27
|
+
save_init_keys(out)
|
|
27
28
|
out.int("host.init_pending", @cycles <= @init_threshold ? @init_handlers.length : 0)
|
|
28
29
|
end
|
|
29
30
|
|
|
@@ -32,6 +33,7 @@ module Badline
|
|
|
32
33
|
@nmi_asserted = input.bool("nmi_asserted", @nmi_asserted)
|
|
33
34
|
pending = input.ints("host.pending_keys", [])
|
|
34
35
|
@pending_keys = pending.empty? ? nil : pending.dup
|
|
36
|
+
load_init_keys(input)
|
|
35
37
|
@init_handlers_lost = [input.int("host.init_pending", 0) - @init_handlers.length, 0].max
|
|
36
38
|
end
|
|
37
39
|
|