badline 0.13.0 → 0.15.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 +72 -1
- data/CLAUDE.md +42 -12
- data/CONTRIBUTING.md +4 -3
- data/README.md +76 -13
- data/doc/media.md +2 -1
- data/doc/pinned-behaviour.md +174 -0
- data/doc/snapshot-format.md +1197 -0
- data/doc/snapshots.md +90 -40
- data/lib/badline/audio/bare_player.rb +46 -17
- data/lib/badline/audio/machine_player.rb +6 -10
- data/lib/badline/audio/renderer.rb +32 -21
- data/lib/badline/c128/banks.rb +7 -6
- data/lib/badline/c128/bus/keyed_state.rb +45 -0
- data/lib/badline/c128/bus.rb +36 -4
- data/lib/badline/c128/drives.rb +6 -25
- data/lib/badline/c128/keyed_modules.rb +117 -0
- data/lib/badline/c128/keyed_state.rb +91 -0
- data/lib/badline/c128/keys.rb +9 -0
- data/lib/badline/c128/mmu.rb +9 -8
- data/lib/badline/c128/mmu_pages.rb +27 -0
- data/lib/badline/c128/modes.rb +19 -9
- data/lib/badline/c128/z80_turns.rb +1 -1
- data/lib/badline/c128.rb +8 -8
- data/lib/badline/c64/attachments.rb +0 -44
- data/lib/badline/c64/banked_ram/plus256k.rb +18 -10
- data/lib/badline/c64/banked_ram/plus60k.rb +8 -6
- data/lib/badline/c64/banked_ram/unexpanded.rb +2 -2
- data/lib/badline/c64/bus/debug_register.rb +32 -0
- data/lib/badline/c64/bus/extra_sids.rb +80 -0
- data/lib/badline/c64/bus/keyed_state.rb +41 -0
- data/lib/badline/c64/bus.rb +3 -1
- data/lib/badline/c64/cartridge/action_replay.rb +6 -15
- data/lib/badline/c64/cartridge/comal80.rb +1 -0
- data/lib/badline/c64/cartridge/dinamic.rb +3 -1
- data/lib/badline/c64/cartridge/easy_flash.rb +0 -15
- data/lib/badline/c64/cartridge/epyx_fastload.rb +0 -8
- data/lib/badline/c64/cartridge/final_cartridge3.rb +1 -12
- data/lib/badline/c64/cartridge/flash.rb +3 -3
- data/lib/badline/c64/cartridge/fun_play.rb +2 -1
- data/lib/badline/c64/cartridge/g_mod2.rb +0 -12
- data/lib/badline/c64/cartridge/game_system.rb +2 -1
- data/lib/badline/c64/cartridge/geo_ram.rb +0 -16
- data/lib/badline/c64/cartridge/kcs_power.rb +0 -8
- data/lib/badline/c64/cartridge/keyed_state.rb +514 -0
- data/lib/badline/c64/cartridge/magic_desk.rb +2 -1
- data/lib/badline/c64/cartridge/ocean.rb +1 -0
- data/lib/badline/c64/cartridge/pagefox.rb +1 -10
- data/lib/badline/c64/cartridge/retro_replay.rb +0 -8
- data/lib/badline/c64/cartridge/rgcd.rb +2 -11
- data/lib/badline/c64/cartridge/super_games.rb +3 -12
- data/lib/badline/c64/cartridge/zaxxon.rb +1 -16
- data/lib/badline/c64/cartridge.rb +4 -30
- data/lib/badline/c64/kernal_traps.rb +28 -25
- data/lib/badline/c64/keyed_modules.rb +115 -0
- data/lib/badline/c64/keyed_state.rb +68 -0
- data/lib/badline/c64/reu/dma.rb +0 -4
- data/lib/badline/c64/reu/keyed_state.rb +140 -0
- data/lib/badline/c64/reu/ram.rb +0 -15
- data/lib/badline/c64/reu.rb +1 -3
- data/lib/badline/c64/true_drives.rb +56 -5
- data/lib/badline/c64.rb +7 -3
- data/lib/badline/chips/cia/interrupt_register.rb +0 -17
- data/lib/badline/chips/cia/keyed_parts.rb +213 -0
- data/lib/badline/chips/cia/serial.rb +0 -4
- data/lib/badline/chips/cia/time_of_day.rb +0 -22
- data/lib/badline/chips/cia/timer.rb +0 -20
- data/lib/badline/chips/cia.rb +2 -2
- data/lib/badline/chips/cpu/core.rb +18 -2
- data/lib/badline/chips/cpu/keyed_state.rb +177 -0
- data/lib/badline/chips/cpu/microcode.rb +16 -0
- data/lib/badline/chips/cpu/port.rb +20 -3
- data/lib/badline/chips/cpu.rb +5 -0
- data/lib/badline/chips/sid/decimator.rb +10 -8
- data/lib/badline/chips/sid/envelope.rb +1 -6
- data/lib/badline/chips/sid/filter.rb +0 -14
- data/lib/badline/chips/sid/keyed_parts.rb +286 -0
- data/lib/badline/chips/sid/voice.rb +0 -10
- data/lib/badline/chips/sid/waveform.rb +0 -2
- data/lib/badline/chips/sid.rb +1 -2
- data/lib/badline/chips/ted/beam.rb +118 -0
- data/lib/badline/chips/ted/fetches.rb +199 -0
- data/lib/badline/chips/ted/keyed_state.rb +156 -0
- data/lib/badline/chips/ted/modes.rb +134 -0
- data/lib/badline/chips/ted/palette.rb +43 -0
- data/lib/badline/chips/ted/picture.rb +182 -0
- data/lib/badline/chips/ted/sound/output.rb +86 -0
- data/lib/badline/chips/ted/sound.rb +284 -0
- data/lib/badline/chips/ted/timers.rb +66 -0
- data/lib/badline/chips/ted.rb +190 -0
- data/lib/badline/chips/tia.rb +61 -0
- data/lib/badline/chips/vdc/keyed_parts.rb +105 -0
- data/lib/badline/chips/vdc/painter.rb +0 -12
- data/lib/badline/chips/vdc/raster.rb +0 -16
- data/lib/badline/chips/vdc/window.rb +0 -9
- data/lib/badline/chips/vdc.rb +1 -2
- data/lib/badline/chips/via/control_lines.rb +0 -15
- data/lib/badline/chips/via/interrupt_register.rb +0 -9
- data/lib/badline/chips/via/keyed_parts.rb +163 -0
- data/lib/badline/chips/via/shift_register.rb +0 -14
- data/lib/badline/chips/via/timer1.rb +0 -13
- data/lib/badline/chips/via/timer2.rb +0 -18
- data/lib/badline/chips/via.rb +1 -2
- data/lib/badline/chips/vic/border_mask.rb +0 -10
- data/lib/badline/chips/vic/collisions.rb +0 -11
- data/lib/badline/chips/vic/color_patches.rb +0 -11
- data/lib/badline/chips/vic/display_state.rb +0 -19
- data/lib/badline/chips/vic/graphics_shifter.rb +0 -4
- data/lib/badline/chips/vic/keyed_parts.rb +228 -0
- data/lib/badline/chips/vic/keyed_sprites.rb +225 -0
- data/lib/badline/chips/vic/keyed_state.rb +102 -0
- data/lib/badline/chips/vic/register_log.rb +0 -18
- data/lib/badline/chips/vic/registers.rb +0 -9
- data/lib/badline/chips/vic/sequencer.rb +0 -2
- data/lib/badline/chips/vic/sprite/internal_bus.rb +0 -11
- data/lib/badline/chips/vic/sprite.rb +0 -2
- data/lib/badline/chips/vic/sprites.rb +0 -3
- data/lib/badline/chips/vic.rb +6 -2
- data/lib/badline/chips/vic_i/keyed_parts.rb +142 -0
- data/lib/badline/chips/vic_i/painter.rb +0 -4
- data/lib/badline/chips/vic_i/sound.rb +38 -46
- data/lib/badline/chips/vic_i.rb +2 -2
- data/lib/badline/chips/z80/core.rb +2 -2
- data/lib/badline/chips/z80/keyed_state.rb +73 -0
- data/lib/badline/chips.rb +10 -0
- data/lib/badline/core.rb +4 -21
- data/lib/badline/drives/datasette/keyed_state.rb +43 -0
- data/lib/badline/drives/datasette.rb +62 -12
- data/lib/badline/drives/engine/core.rb +3 -0
- data/lib/badline/drives/engine/serial_lines.rb +4 -7
- data/lib/badline/drives/model1541/bus.rb +0 -13
- data/lib/badline/drives/model1541/disk.rb +9 -8
- data/lib/badline/drives/model1541/keyed_state.rb +251 -0
- data/lib/badline/drives/model1541/mechanism.rb +0 -2
- data/lib/badline/drives/model1541.rb +1 -2
- data/lib/badline/drives/model1571/bus.rb +0 -13
- data/lib/badline/drives/model1571/keyed_state.rb +108 -0
- data/lib/badline/drives/model1571/wd1770.rb +0 -14
- data/lib/badline/drives/model1571.rb +1 -2
- data/lib/badline/drives/model1581/bus.rb +0 -13
- data/lib/badline/drives/model1581/disk.rb +0 -4
- data/lib/badline/drives/model1581/keyed_state.rb +280 -0
- data/lib/badline/drives/model1581/mechanism.rb +0 -21
- data/lib/badline/drives/model1581/slot.rb +0 -18
- data/lib/badline/drives/model1581/wd1772.rb +0 -2
- data/lib/badline/drives/model1581.rb +2 -2
- data/lib/badline/drives.rb +8 -0
- data/lib/badline/frontend/app.rb +3 -3
- data/lib/badline/frontend/boot.rb +4 -2
- data/lib/badline/frontend/controls.rb +25 -5
- data/lib/badline/frontend/drive_led.rb +44 -21
- data/lib/badline/frontend/keys.rb +14 -2
- data/lib/badline/frontend/media_slots.rb +11 -1
- data/lib/badline/frontend/menu/menu_dialogs.rb +4 -3
- data/lib/badline/frontend/menu/menu_media.rb +13 -3
- data/lib/badline/frontend/menu/menu_pages.rb +8 -8
- data/lib/badline/frontend/menu/menu_ports.rb +13 -1
- data/lib/badline/frontend/screen.rb +33 -12
- data/lib/badline/frontend/screens.rb +2 -1
- data/lib/badline/frontend/snapshots.rb +12 -1
- data/lib/badline/frontend/sound.rb +4 -4
- data/lib/badline/frontend/timeline.rb +43 -12
- data/lib/badline/input/matrix_typist.rb +98 -0
- data/lib/badline/input.rb +1 -0
- data/lib/badline/kernal_trap/chrout.rb +45 -0
- data/lib/badline/kernal_trap/dos/channel.rb +0 -27
- data/lib/badline/kernal_trap/dos/channels.rb +0 -25
- data/lib/badline/kernal_trap/dos/memory.rb +0 -9
- data/lib/badline/kernal_trap/dos/status.rb +0 -8
- data/lib/badline/kernal_trap/dos/write_file.rb +0 -18
- data/lib/badline/kernal_trap/dos.rb +0 -19
- data/lib/badline/kernal_trap/file_routine.rb +4 -4
- data/lib/badline/kernal_trap/keyed_state.rb +150 -0
- data/lib/badline/kernal_trap/layout.rb +75 -11
- data/lib/badline/kernal_trap/load.rb +14 -16
- data/lib/badline/kernal_trap/routine.rb +12 -0
- data/lib/badline/kernal_trap/save.rb +8 -18
- data/lib/badline/kernal_trap/serial.rb +1 -17
- data/lib/badline/kernal_trap.rb +2 -0
- data/lib/badline/machine/keyboard_buffer.rb +49 -0
- data/lib/badline/machine/settling.rb +29 -0
- data/lib/badline/machine.rb +29 -6
- data/lib/badline/media/boot_disk.rb +14 -2
- data/lib/badline/media/extensions.rb +10 -0
- data/lib/badline/media/plus4_media.rb +68 -0
- data/lib/badline/media/program.rb +20 -6
- data/lib/badline/media.rb +33 -12
- data/lib/badline/memory.rb +3 -6
- data/lib/badline/options/family.rb +6 -2
- data/lib/badline/options/help.rb +66 -16
- data/lib/badline/options/table.rb +17 -2
- data/lib/badline/options/validation.rb +26 -2
- data/lib/badline/options.rb +42 -9
- data/lib/badline/plus4/acia.rb +45 -0
- data/lib/badline/plus4/bus.rb +375 -0
- data/lib/badline/plus4/cassette_sense.rb +21 -0
- data/lib/badline/plus4/cpu.rb +10 -0
- data/lib/badline/plus4/cpu_port.rb +93 -0
- data/lib/badline/plus4/keyboard_port.rb +76 -0
- data/lib/badline/plus4/keyed_modules.rb +99 -0
- data/lib/badline/plus4/keyed_parts.rb +143 -0
- data/lib/badline/plus4/keyed_state.rb +41 -0
- data/lib/badline/plus4/model.rb +38 -0
- data/lib/badline/plus4/user_port.rb +31 -0
- data/lib/badline/plus4.rb +240 -0
- data/lib/badline/roms/README +38 -0
- data/lib/badline/roms/plus4/basic.rom +0 -0
- data/lib/badline/roms/plus4/function-high.rom +0 -0
- data/lib/badline/roms/plus4/function-low.rom +0 -0
- data/lib/badline/roms/plus4/kernal-ntsc.rom +0 -0
- data/lib/badline/roms/plus4/kernal-pal.rom +0 -0
- data/lib/badline/snapshot/c128_setup.rb +9 -20
- data/lib/badline/snapshot/c64_setup.rb +28 -24
- data/lib/badline/snapshot/container.rb +6 -0
- data/lib/badline/snapshot/field_doc/markdown.rb +113 -0
- data/lib/badline/snapshot/field_doc.rb +148 -0
- data/lib/badline/snapshot/image.rb +143 -28
- data/lib/badline/snapshot/keyed/cursor.rb +40 -0
- data/lib/badline/snapshot/keyed/modules.rb +66 -0
- data/lib/badline/snapshot/keyed/reader.rb +188 -0
- data/lib/badline/snapshot/keyed/recorder.rb +88 -0
- data/lib/badline/snapshot/keyed/report.rb +26 -0
- data/lib/badline/snapshot/keyed/writer.rb +55 -0
- data/lib/badline/snapshot/keyed.rb +64 -0
- data/lib/badline/snapshot/machine_state.rb +17 -64
- data/lib/badline/snapshot/plus4_setup.rb +39 -0
- data/lib/badline/snapshot/storage_setup.rb +29 -34
- data/lib/badline/snapshot/vic20_setup.rb +6 -15
- data/lib/badline/snapshot/vice/reu1764.rb +2 -2
- data/lib/badline/snapshot/vice.rb +7 -31
- data/lib/badline/snapshot.rb +48 -20
- data/lib/badline/storage/crt_file.rb +3 -1
- data/lib/badline/storage/g64_image.rb +9 -4
- data/lib/badline/storage/tap.rb +57 -20
- data/lib/badline/support/checkpoint.rb +3 -2
- data/lib/badline/support/plus4_checkpoint.rb +42 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic20/attachments.rb +3 -0
- data/lib/badline/vic20/bus/keyed_state.rb +52 -0
- data/lib/badline/vic20/bus.rb +1 -2
- data/lib/badline/vic20/keyed_modules.rb +73 -0
- data/lib/badline/vic20/keyed_state.rb +45 -0
- data/lib/badline/vic20.rb +5 -5
- data/lib/badline.rb +1 -0
- metadata +75 -42
- data/lib/badline/c128/bus/saved_state.rb +0 -53
- data/lib/badline/c128/saved_state.rb +0 -117
- data/lib/badline/c64/bus/saved_state.rb +0 -68
- data/lib/badline/c64/cartridge/flash/saved_state.rb +0 -40
- data/lib/badline/c64/cartridge/retro_replay/saved_state.rb +0 -47
- data/lib/badline/c64/cartridge/saved_state.rb +0 -63
- data/lib/badline/c64/reu/dma/saved_state.rb +0 -52
- data/lib/badline/c64/reu/saved_state.rb +0 -55
- data/lib/badline/c64/saved_state.rb +0 -112
- data/lib/badline/chips/cia/saved_state.rb +0 -49
- data/lib/badline/chips/cia/serial/saved_state.rb +0 -45
- data/lib/badline/chips/cpu/saved_state.rb +0 -117
- data/lib/badline/chips/sid/envelope/saved_state.rb +0 -70
- data/lib/badline/chips/sid/filter/saved_state.rb +0 -42
- data/lib/badline/chips/sid/saved_state.rb +0 -52
- data/lib/badline/chips/sid/waveform/saved_state.rb +0 -68
- data/lib/badline/chips/vdc/saved_state.rb +0 -36
- data/lib/badline/chips/via/saved_state.rb +0 -44
- data/lib/badline/chips/vic/graphics_shifter/saved_state.rb +0 -40
- data/lib/badline/chips/vic/saved_state.rb +0 -82
- data/lib/badline/chips/vic/sequencer/saved_state.rb +0 -91
- data/lib/badline/chips/vic/sprite/saved_state.rb +0 -66
- data/lib/badline/chips/vic/sprites/saved_state.rb +0 -59
- data/lib/badline/chips/vic_i/painter/saved_state.rb +0 -40
- data/lib/badline/chips/vic_i/saved_state.rb +0 -55
- data/lib/badline/chips/z80/saved_state.rb +0 -71
- data/lib/badline/chrout_trap.rb +0 -43
- data/lib/badline/debug_register.rb +0 -28
- data/lib/badline/drives/datasette/saved_state.rb +0 -37
- data/lib/badline/drives/model1541/disk/saved_state.rb +0 -88
- data/lib/badline/drives/model1541/mechanism/saved_state.rb +0 -72
- data/lib/badline/drives/model1541/saved_state.rb +0 -44
- data/lib/badline/drives/model1571/saved_state.rb +0 -47
- data/lib/badline/drives/model1581/disk/saved_state.rb +0 -89
- data/lib/badline/drives/model1581/saved_state.rb +0 -42
- data/lib/badline/drives/model1581/wd1772/saved_state.rb +0 -68
- data/lib/badline/keyboard_buffer.rb +0 -33
- data/lib/badline/snapshot/setup.rb +0 -31
- data/lib/badline/snapshot/state.rb +0 -179
- data/lib/badline/vic20/bus/saved_state.rb +0 -35
- data/lib/badline/vic20/saved_state.rb +0 -110
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Keyed
|
|
6
|
+
# Reads BER numbers and byte runs from a module's payload, or from one
|
|
7
|
+
# record's value, failing on anything cut short.
|
|
8
|
+
class Cursor
|
|
9
|
+
def initialize(bytes, name)
|
|
10
|
+
@bytes = bytes
|
|
11
|
+
@name = name
|
|
12
|
+
@pos = 0
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def done? = @pos >= @bytes.bytesize
|
|
16
|
+
|
|
17
|
+
def remaining = @bytes.bytesize - @pos
|
|
18
|
+
|
|
19
|
+
def ber
|
|
20
|
+
value = 0
|
|
21
|
+
loop do
|
|
22
|
+
raise FormatError, "#{@name} holds a number too large for a snapshot" if value >= (1 << 56)
|
|
23
|
+
|
|
24
|
+
byte = take(1).getbyte(0).to_i
|
|
25
|
+
value = (value << 7) | (byte & 0x7f)
|
|
26
|
+
return value if byte < 0x80
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def int = Keyed.unzigzag(ber)
|
|
31
|
+
|
|
32
|
+
def take(count)
|
|
33
|
+
raise FormatError, "#{@name} ends early, at byte #{@bytes.bytesize}" if count > remaining
|
|
34
|
+
|
|
35
|
+
@bytes.byteslice(@pos, count).to_s.tap { @pos += count }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Keyed
|
|
6
|
+
# A snapshot's BL-* modules by name, which a family reads in its own
|
|
7
|
+
# fixed order, whatever order the file has them in. Each module is
|
|
8
|
+
# parsed once, and its Reader kept for the Report.
|
|
9
|
+
#
|
|
10
|
+
# A `detached` load restores a machine that opens no host file and
|
|
11
|
+
# writes none: a disk image comes from the bytes the snapshot holds,
|
|
12
|
+
# write-protected, and a directory device 8 serves is left out.
|
|
13
|
+
class Modules
|
|
14
|
+
attr_reader :readers
|
|
15
|
+
|
|
16
|
+
def initialize(sections, detached: false)
|
|
17
|
+
@sections = {}
|
|
18
|
+
sections.each { |section| @sections[section.name] = section }
|
|
19
|
+
@parsed = {}
|
|
20
|
+
@readers = []
|
|
21
|
+
@detached = detached
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def detached? = @detached
|
|
25
|
+
|
|
26
|
+
# BL-MACHINE, which every family writes first.
|
|
27
|
+
def machine = required("BL-MACHINE")
|
|
28
|
+
|
|
29
|
+
# The module a family always writes, or a Reader standing in for it,
|
|
30
|
+
# reported missing.
|
|
31
|
+
def required(name)
|
|
32
|
+
reader = @parsed[name]
|
|
33
|
+
return reader if reader
|
|
34
|
+
|
|
35
|
+
section = @sections[name]
|
|
36
|
+
keep(name, section.nil? ? missing(name) : parse(section))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# The module, or nil when the snapshot has none, as it has none for a
|
|
40
|
+
# cartridge or a drive that isn't there.
|
|
41
|
+
def optional(name)
|
|
42
|
+
reader = @parsed[name]
|
|
43
|
+
return reader if reader
|
|
44
|
+
|
|
45
|
+
section = @sections[name]
|
|
46
|
+
section.nil? ? nil : keep(name, parse(section))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The BL-* modules no family read, from a later badline.
|
|
50
|
+
def unread = @sections.keys.select { |name| name.start_with?("BL-") && !@parsed.key?(name) }
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def parse(section) = Reader.parse(section)
|
|
55
|
+
|
|
56
|
+
def missing(name) = Reader.missing(name)
|
|
57
|
+
|
|
58
|
+
def keep(name, reader)
|
|
59
|
+
@parsed[name] = reader
|
|
60
|
+
@readers << reader
|
|
61
|
+
reader
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Keyed
|
|
6
|
+
# Parses a module's records into one Hash per type, so every value
|
|
7
|
+
# read has a single type in Spinel. Each read takes the current value
|
|
8
|
+
# as its default, which in a machine built for the load is power-on,
|
|
9
|
+
# and returns it when the record is missing or of another type.
|
|
10
|
+
#
|
|
11
|
+
# A Reader with no records stands in for a module the snapshot
|
|
12
|
+
# doesn't have, so every load takes the same path.
|
|
13
|
+
class Reader
|
|
14
|
+
# The tag of each key the module's records hold.
|
|
15
|
+
attr_reader :name, :tags
|
|
16
|
+
|
|
17
|
+
# Whether it stands in for a module the snapshot doesn't have.
|
|
18
|
+
def missing? = @missing
|
|
19
|
+
|
|
20
|
+
# The records in `section`, which must be in this encoding.
|
|
21
|
+
def self.parse(section)
|
|
22
|
+
unless section.major == MAJOR
|
|
23
|
+
raise FormatError, "#{section.name} #{section.version} was written by a newer badline's encoding, " \
|
|
24
|
+
"and this one reads #{MAJOR}.#{MINOR}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
new(section.name, Zlib.inflate(section.data))
|
|
28
|
+
rescue Zlib::Error => e
|
|
29
|
+
raise FormatError, "#{section.name} is damaged: #{e.message}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Stands in for a module the snapshot doesn't have.
|
|
33
|
+
def self.missing(name) = new(name, "".b, missing: true)
|
|
34
|
+
|
|
35
|
+
def initialize(name, payload = "".b, missing: false)
|
|
36
|
+
@name = name
|
|
37
|
+
@missing = missing
|
|
38
|
+
@ints = {}
|
|
39
|
+
@bools = {}
|
|
40
|
+
@bytes = {}
|
|
41
|
+
@lists = {}
|
|
42
|
+
@texts = {}
|
|
43
|
+
@tags = {}
|
|
44
|
+
@read = {}
|
|
45
|
+
@adjusted = []
|
|
46
|
+
@unknown_tags = 0
|
|
47
|
+
parse(Cursor.new(payload, name))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def int(key, default) = fetch_int(key, default)
|
|
51
|
+
|
|
52
|
+
def word(key, default) = fetch_masked(key, default, 0xffff)
|
|
53
|
+
|
|
54
|
+
def byte(key, default) = fetch_masked(key, default, 0xff)
|
|
55
|
+
|
|
56
|
+
# An int cut to the bits a latch holds, reported if it was wider.
|
|
57
|
+
def masked(key, default, mask) = fetch_masked(key, default, mask)
|
|
58
|
+
|
|
59
|
+
def bool(key, default) = present?(key, "a bool", @bools.key?(key)) ? @bools.fetch(key) : default
|
|
60
|
+
|
|
61
|
+
# Nil when the record is missing.
|
|
62
|
+
def optional_int(key) = present?(key, "an int", @ints.key?(key)) ? @ints.fetch(key) : nil
|
|
63
|
+
|
|
64
|
+
def ints(key, default) = present?(key, "a list of ints", @lists.key?(key)) ? @lists.fetch(key) : default
|
|
65
|
+
|
|
66
|
+
# A binary String.
|
|
67
|
+
def bytes(key, default) = fetch_bytes(key, default)
|
|
68
|
+
|
|
69
|
+
# Reads a hardware array into `array` in place. A shorter list
|
|
70
|
+
# leaves the rest as it was and a longer one is cut, and both are
|
|
71
|
+
# reported.
|
|
72
|
+
def ints_into(key, array)
|
|
73
|
+
return array unless present?(key, "a list of ints", @lists.key?(key))
|
|
74
|
+
|
|
75
|
+
fill(key, array, @lists.fetch(key))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def bytes_into(key, array)
|
|
79
|
+
return array unless present?(key, "bytes", @bytes.key?(key))
|
|
80
|
+
|
|
81
|
+
fill(key, array, @bytes.fetch(key).bytes)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def text(key, default) = fetch_text(key, default)
|
|
85
|
+
|
|
86
|
+
# The text, if it is one of `names`, and otherwise `default`,
|
|
87
|
+
# reported.
|
|
88
|
+
def choice(key, names, default)
|
|
89
|
+
value = fetch_text(key, default)
|
|
90
|
+
return value if names.include?(value)
|
|
91
|
+
|
|
92
|
+
@adjusted << "#{key} #{value.inspect} is not one of #{names.join(', ')}, left at #{default}"
|
|
93
|
+
default
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Reports something a unit's load left out or changed beyond one
|
|
97
|
+
# field, such as a line batch from another renderer.
|
|
98
|
+
def adjust(line)
|
|
99
|
+
@adjusted << line
|
|
100
|
+
self
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# The keys of records nothing read.
|
|
104
|
+
def unknown = @tags.keys.reject { |key| @read.key?(key) }
|
|
105
|
+
|
|
106
|
+
def report
|
|
107
|
+
Report.new(name:, missing: @missing, unknown:, adjusted: @adjusted.dup, unknown_tags: @unknown_tags)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def fetch_int(key, default) = present?(key, "an int", @ints.key?(key)) ? @ints.fetch(key) : default
|
|
113
|
+
|
|
114
|
+
def fetch_masked(key, default, mask)
|
|
115
|
+
value = fetch_int(key, default)
|
|
116
|
+
return value if value & mask == value
|
|
117
|
+
|
|
118
|
+
@adjusted << "#{key} #{value} is wider than its latch, masked to #{value & mask}"
|
|
119
|
+
value & mask
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def fetch_bytes(key, default) = present?(key, "bytes", @bytes.key?(key)) ? @bytes.fetch(key) : default
|
|
123
|
+
|
|
124
|
+
def fetch_text(key, default) = present?(key, "text", @texts.key?(key)) ? @texts.fetch(key) : default
|
|
125
|
+
|
|
126
|
+
# Whether the key has a record of the type read, `there`. A record
|
|
127
|
+
# of another type counts as missing, and is reported.
|
|
128
|
+
def present?(key, type, there)
|
|
129
|
+
@read[key] = true if there || @tags.key?(key)
|
|
130
|
+
@adjusted << "#{key} is not #{type}, left at its default" if !there && @tags.key?(key)
|
|
131
|
+
there
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def fill(key, array, values)
|
|
135
|
+
unless values.length == array.length
|
|
136
|
+
what = values.length < array.length ? "the rest left as they were" : "the rest cut"
|
|
137
|
+
@adjusted << "#{key} holds #{values.length} values where #{array.length} fit, #{what}"
|
|
138
|
+
end
|
|
139
|
+
[values.length, array.length].min.times { |index| array[index] = values[index] }
|
|
140
|
+
array
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def parse(cursor)
|
|
144
|
+
until cursor.done?
|
|
145
|
+
key = cursor.take(cursor.ber)
|
|
146
|
+
tag = cursor.take(1).getbyte(0).to_i
|
|
147
|
+
store(key, tag, cursor.take(cursor.ber))
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def store(key, tag, value)
|
|
152
|
+
unless tag.between?(INT, TEXT)
|
|
153
|
+
@unknown_tags += 1
|
|
154
|
+
return
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
forget(key)
|
|
158
|
+
@tags[key] = tag
|
|
159
|
+
case tag
|
|
160
|
+
when INT then @ints[key] = Cursor.new(value, "#{@name} #{key}").int
|
|
161
|
+
when BOOL then @bools[key] = value.getbyte(0).to_i != 0
|
|
162
|
+
when BYTES then @bytes[key] = value
|
|
163
|
+
when INTS then @lists[key] = list(Cursor.new(value, "#{@name} #{key}"))
|
|
164
|
+
else @texts[key] = value.force_encoding(Encoding::UTF_8)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def forget(key)
|
|
169
|
+
return unless @tags.key?(key)
|
|
170
|
+
|
|
171
|
+
@adjusted << "#{key} appears more than once, the last one counts"
|
|
172
|
+
@ints.delete(key)
|
|
173
|
+
@bools.delete(key)
|
|
174
|
+
@bytes.delete(key)
|
|
175
|
+
@lists.delete(key)
|
|
176
|
+
@texts.delete(key)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def list(cursor)
|
|
180
|
+
count = cursor.ber
|
|
181
|
+
raise FormatError, "#{@name} ends early, in a list of #{count}" if count > cursor.remaining
|
|
182
|
+
|
|
183
|
+
Array.new(count) { cursor.int }
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Keyed
|
|
6
|
+
# A Reader that notes every read: the module, the key, the type read
|
|
7
|
+
# and the default passed. Loading a machine at power-on through
|
|
8
|
+
# Recorders lists each module's fields with their power-on values
|
|
9
|
+
# (FieldDoc).
|
|
10
|
+
class Recorder < Reader
|
|
11
|
+
Field = Data.define(:module_name, :key, :type, :default)
|
|
12
|
+
|
|
13
|
+
attr_reader :fields
|
|
14
|
+
|
|
15
|
+
def initialize(name, payload = "".b, missing: false)
|
|
16
|
+
super
|
|
17
|
+
@fields = []
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def int(key, default)
|
|
21
|
+
note(key, "int", default)
|
|
22
|
+
super
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def word(key, default)
|
|
26
|
+
note(key, "word", default)
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def byte(key, default)
|
|
31
|
+
note(key, "byte", default)
|
|
32
|
+
super
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def masked(key, default, mask)
|
|
36
|
+
note(key, "int & 0x#{mask.to_s(16)}", default)
|
|
37
|
+
super
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def bool(key, default)
|
|
41
|
+
note(key, "bool", default)
|
|
42
|
+
super
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def optional_int(key)
|
|
46
|
+
note(key, "int, optional", nil)
|
|
47
|
+
super
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def ints(key, default)
|
|
51
|
+
note(key, "ints", default.dup)
|
|
52
|
+
super
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def bytes(key, default)
|
|
56
|
+
note(key, "bytes", default.dup)
|
|
57
|
+
super
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def ints_into(key, array)
|
|
61
|
+
note(key, "ints", array.dup)
|
|
62
|
+
super
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def bytes_into(key, array)
|
|
66
|
+
note(key, "bytes", array.dup)
|
|
67
|
+
super
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def text(key, default)
|
|
71
|
+
note(key, "text", default)
|
|
72
|
+
super
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def choice(key, names, default)
|
|
76
|
+
note(key, "text: #{names.join(' ')}", default)
|
|
77
|
+
super
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def note(key, type, default)
|
|
83
|
+
@fields << Field.new(module_name: name, key:, type:, default:)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Keyed
|
|
6
|
+
# What reading a module left out or changed: whether the snapshot
|
|
7
|
+
# had the module at all, the keys nothing read, a line for each value
|
|
8
|
+
# read other than as written, and how many records had a tag this
|
|
9
|
+
# badline doesn't know.
|
|
10
|
+
Report = Data.define(:name, :missing, :unknown, :adjusted, :unknown_tags) do
|
|
11
|
+
# Lines for the user. A missing module and adjusted values always
|
|
12
|
+
# get one, and unknown keys and tags only when `verbose`, since a
|
|
13
|
+
# snapshot from a later badline has them on every load.
|
|
14
|
+
def lines(verbose: false)
|
|
15
|
+
lines = missing ? ["#{name} is missing, left at power-on"] : []
|
|
16
|
+
lines.concat(adjusted.map { |line| "#{name}: #{line}" })
|
|
17
|
+
return lines unless verbose
|
|
18
|
+
|
|
19
|
+
lines << "#{name}: #{unknown.length} unknown field(s): #{unknown.join(', ')}" unless unknown.empty?
|
|
20
|
+
lines << "#{name}: #{unknown_tags} field(s) of an unknown type, skipped" if unknown_tags.positive?
|
|
21
|
+
lines
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
module Keyed
|
|
6
|
+
# Appends records to a module's payload. Each method returns self.
|
|
7
|
+
class Writer
|
|
8
|
+
def initialize
|
|
9
|
+
@out = String.new(encoding: Encoding::BINARY)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Raises TypeError unless `value` is an Integer, and RangeError
|
|
13
|
+
# outside VALUES.
|
|
14
|
+
def int(key, value) = record(key, INT, Keyed.ber(fold(key, value)))
|
|
15
|
+
|
|
16
|
+
def bool(key, value) = record(key, BOOL, value ? "\x01".b : "\x00".b)
|
|
17
|
+
|
|
18
|
+
# An Array of 0..255, or a binary String.
|
|
19
|
+
def bytes(key, values) = record(key, BYTES, values.is_a?(String) ? values.b : values.pack("C*"))
|
|
20
|
+
|
|
21
|
+
def ints(key, values)
|
|
22
|
+
value = Keyed.ber(values.length)
|
|
23
|
+
values.each { |number| value << Keyed.ber(fold(key, number)) }
|
|
24
|
+
record(key, INTS, value)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def text(key, value) = record(key, TEXT, value.b)
|
|
28
|
+
|
|
29
|
+
# Writes nothing for nil, which a reader reads back as nil.
|
|
30
|
+
def optional_int(key, value) = value.nil? ? self : int(key, value)
|
|
31
|
+
|
|
32
|
+
# The records written so far.
|
|
33
|
+
def to_s = @out.dup
|
|
34
|
+
|
|
35
|
+
def section(name) = Section.new(name:, major: MAJOR, minor: MINOR, data: Zlib.deflate(@out))
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def fold(key, value)
|
|
40
|
+
raise TypeError, "#{key} is not an Integer: #{value.inspect}" unless value.is_a?(Integer)
|
|
41
|
+
raise RangeError, "#{key} #{value} is too large for a snapshot" unless VALUES.cover?(value)
|
|
42
|
+
|
|
43
|
+
Keyed.zigzag(value)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def record(key, tag, value)
|
|
47
|
+
raise ArgumentError, "#{key.inspect} is not a snapshot key" unless key.match?(KEY)
|
|
48
|
+
|
|
49
|
+
@out << Keyed.ber(key.bytesize) << key.b << [tag].pack("C") << Keyed.ber(value.bytesize) << value
|
|
50
|
+
self
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "zlib"
|
|
4
|
+
require "badline/snapshot/container"
|
|
5
|
+
|
|
6
|
+
module Badline
|
|
7
|
+
module Snapshot
|
|
8
|
+
# A snapshot module as keyed, typed records, which a later badline
|
|
9
|
+
# reads by name: one it doesn't know is skipped, and one that isn't
|
|
10
|
+
# there leaves the value a freshly built machine has.
|
|
11
|
+
#
|
|
12
|
+
# The payload is deflated. Inside, each record is the key's length and
|
|
13
|
+
# the key (ASCII, lower snake case with dots between sub-units), a
|
|
14
|
+
# one-byte type tag, then the value's length and the value. Lengths are
|
|
15
|
+
# BER-compressed (Ruby's pack "w"), and so are integers, each folded to
|
|
16
|
+
# a natural number (0, -1, 1, -2 ... as 0, 1, 2, 3 ...). The value's
|
|
17
|
+
# length lets a reader skip a tag it doesn't know.
|
|
18
|
+
#
|
|
19
|
+
# A module's header version is MAJOR.MINOR. The major number is this
|
|
20
|
+
# record encoding, and a reader refuses another one. Fields carry no
|
|
21
|
+
# version: a changed meaning gets a new key.
|
|
22
|
+
module Keyed
|
|
23
|
+
MAJOR = 1
|
|
24
|
+
MINOR = 0
|
|
25
|
+
|
|
26
|
+
INT = 0x01
|
|
27
|
+
BOOL = 0x02
|
|
28
|
+
BYTES = 0x03
|
|
29
|
+
INTS = 0x04
|
|
30
|
+
TEXT = 0x05
|
|
31
|
+
|
|
32
|
+
KEY = /\A[a-z0-9_]+(\.[a-z0-9_]+)*\z/
|
|
33
|
+
|
|
34
|
+
# The values an int record holds: what a 64-bit integer holds once
|
|
35
|
+
# zigzag doubles it, which a native build's integers can't exceed.
|
|
36
|
+
VALUES = (-(1 << 62))..((1 << 62) - 1)
|
|
37
|
+
|
|
38
|
+
module_function
|
|
39
|
+
|
|
40
|
+
def ber(number) = [number].pack("w")
|
|
41
|
+
|
|
42
|
+
# Folds an Integer in VALUES to a natural number, as a
|
|
43
|
+
# native build's 64-bit integers hold it.
|
|
44
|
+
def zigzag(value) = value.negative? ? ((-value - 1) << 1) | 1 : value << 1
|
|
45
|
+
|
|
46
|
+
def unzigzag(folded) = folded.odd? ? -(folded >> 1) - 1 : folded >> 1
|
|
47
|
+
|
|
48
|
+
# The module `name`, with the records the block writes to the Writer
|
|
49
|
+
# it yields.
|
|
50
|
+
def section(name)
|
|
51
|
+
out = Writer.new
|
|
52
|
+
yield out
|
|
53
|
+
out.section(name)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
require "badline/snapshot/keyed/cursor"
|
|
60
|
+
require "badline/snapshot/keyed/writer"
|
|
61
|
+
require "badline/snapshot/keyed/report"
|
|
62
|
+
require "badline/snapshot/keyed/reader"
|
|
63
|
+
require "badline/snapshot/keyed/recorder"
|
|
64
|
+
require "badline/snapshot/keyed/modules"
|
|
@@ -2,83 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
module Snapshot
|
|
5
|
-
# The BADLINE module
|
|
6
|
-
#
|
|
7
|
-
#
|
|
5
|
+
# The BADLINE module badline 0.13 and earlier saved the machine in, a
|
|
6
|
+
# positional layout only the version that wrote it reads. All that is
|
|
7
|
+
# left of it is reading which version that was, for the refusal
|
|
8
|
+
# (Image#load).
|
|
8
9
|
#
|
|
9
10
|
# The payload is deflated. Inside, a little-endian 32-bit length leads
|
|
10
11
|
# a run of BER-compressed integers (Ruby's pack "w"): the number of
|
|
11
|
-
# values and of strings, each string's length, then the values
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# A State only restores in the badline version that wrote it, since
|
|
16
|
-
# each chip writes its fields as it holds them.
|
|
12
|
+
# values and of strings, each string's length, then the values. The
|
|
13
|
+
# strings follow, one after another: the family's marker first, then
|
|
14
|
+
# the badline version.
|
|
17
15
|
module MachineState
|
|
18
16
|
NAME = "BADLINE"
|
|
19
|
-
MAJOR = 2
|
|
20
|
-
MINOR = 0
|
|
21
|
-
|
|
22
|
-
# The values a 64-bit integer holds once encode doubles them, which a
|
|
23
|
-
# native build's integers can't exceed.
|
|
24
|
-
VALUES = (-(1 << 62))..((1 << 62) - 1)
|
|
25
17
|
|
|
26
18
|
module_function
|
|
27
19
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
unless section.major == MAJOR
|
|
34
|
-
raise FormatError, "#{NAME} #{section.version} was written by another badline version, " \
|
|
35
|
-
"and this one reads #{MAJOR}.#{MINOR}"
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
decode(Zlib.inflate(section.data))
|
|
39
|
-
rescue Zlib::Error => e
|
|
40
|
-
raise FormatError, "#{NAME} is damaged: #{e.message}"
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def encode(state)
|
|
44
|
-
values = state.values
|
|
45
|
-
strings = state.strings
|
|
46
|
-
numbers = [values.length, strings.length]
|
|
47
|
-
strings.each { |string| numbers << string.bytesize }
|
|
48
|
-
values.each do |value|
|
|
49
|
-
raise RangeError, "#{value} is too large for a snapshot" unless VALUES.cover?(value)
|
|
50
|
-
|
|
51
|
-
numbers << (value.negative? ? (-2 * value) - 1 : 2 * value)
|
|
52
|
-
end
|
|
53
|
-
index = numbers.pack("w*")
|
|
54
|
-
[index.bytesize].pack("V") + index + strings.join
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def decode(bytes)
|
|
58
|
-
raise FormatError, "#{NAME} ends early" if bytes.bytesize < 4
|
|
20
|
+
# The badline version that saved the module, or an empty string when
|
|
21
|
+
# it is too damaged to say.
|
|
22
|
+
def written_by(section)
|
|
23
|
+
bytes = Zlib.inflate(section.data)
|
|
24
|
+
return "" if bytes.bytesize < 4
|
|
59
25
|
|
|
60
26
|
length = bytes.byteslice(0, 4).unpack1("V").to_i
|
|
61
27
|
numbers = bytes.byteslice(4, length).to_s.unpack("w*").map(&:to_i)
|
|
62
|
-
value_count = numbers.fetch(0, 0)
|
|
63
28
|
string_count = numbers.fetch(1, 0)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
State.new(values(numbers, 2 + string_count), strings(bytes, 4 + length, numbers.slice(2, string_count)))
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def values(numbers, from)
|
|
70
|
-
Array.new(numbers.length - from) do |i|
|
|
71
|
-
folded = numbers[from + i]
|
|
72
|
-
folded.odd? ? -((folded + 1) / 2) : folded / 2
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def strings(bytes, pos, lengths)
|
|
77
|
-
lengths.map do |length|
|
|
78
|
-
raise FormatError, "#{NAME} ends early" if pos + length > bytes.bytesize
|
|
29
|
+
return "" if string_count < 2 || numbers.length < 4
|
|
79
30
|
|
|
80
|
-
|
|
81
|
-
|
|
31
|
+
pos = 4 + length + numbers[2]
|
|
32
|
+
bytes.byteslice(pos, numbers[3]).to_s
|
|
33
|
+
rescue Zlib::Error
|
|
34
|
+
""
|
|
82
35
|
end
|
|
83
36
|
end
|
|
84
37
|
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Snapshot
|
|
5
|
+
# How a 264 series machine was built: its model's name, one of
|
|
6
|
+
# Plus4::Model::ALL, which also says whether the Plus/4's function ROMs
|
|
7
|
+
# are fitted, its RAM in K, and whether a 1551's TCBM interface is
|
|
8
|
+
# plugged in, the arguments Plus4.new takes.
|
|
9
|
+
Plus4Setup = Data.define(:model, :ram_kb, :tcbm)
|
|
10
|
+
|
|
11
|
+
class Plus4Setup
|
|
12
|
+
MODELS = Plus4::Model::ALL.map(&:name).freeze
|
|
13
|
+
|
|
14
|
+
# The setup of `machine`.
|
|
15
|
+
def self.of(machine) = new(model: machine.model.name, ram_kb: machine.ram_kb, tcbm: !machine.tias.empty?)
|
|
16
|
+
|
|
17
|
+
# The model by name, the RAM and the TCBM interface, in a keyed
|
|
18
|
+
# snapshot module.
|
|
19
|
+
def write_fields(out)
|
|
20
|
+
out.text("model", model).int("ram_kb", ram_kb).bool("tcbm", tcbm)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.read_fields(input)
|
|
24
|
+
model = input.choice("model", MODELS, "plus4")
|
|
25
|
+
ram_kb = input.int("ram_kb", Plus4::Model.named(model).ram_kb)
|
|
26
|
+
unless Plus4::Model::RAM_SIZES.include?(ram_kb)
|
|
27
|
+
raise FormatError, "the snapshot is of a Plus/4 with #{ram_kb}K of RAM, which badline doesn't build"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
new(model:, ram_kb:, tcbm: input.bool("tcbm", false))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# A machine built this way, at power-on.
|
|
34
|
+
def build = Plus4.new(model:, ram: ram_kb, tcbm:)
|
|
35
|
+
|
|
36
|
+
def to_s = "#{model} with #{ram_kb}K#{' and a TCBM interface' if tcbm}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|