badline 0.15.1 → 0.16.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.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -0
  3. data/CLAUDE.md +8 -7
  4. data/README.md +102 -6
  5. data/doc/pinned-behaviour.md +67 -0
  6. data/doc/snapshot-format.md +213 -18
  7. data/doc/snapshots.md +14 -0
  8. data/lib/badline/c64/common.rb +1 -1
  9. data/lib/badline/chips/crtc/keyed_state.rb +46 -0
  10. data/lib/badline/chips/crtc.rb +163 -0
  11. data/lib/badline/chips/pia/control_lines.rb +138 -0
  12. data/lib/badline/chips/pia/keyed_parts.rb +56 -0
  13. data/lib/badline/chips/pia.rb +138 -0
  14. data/lib/badline/chips/vic/register_log.rb +1 -4
  15. data/lib/badline/core.rb +3 -0
  16. data/lib/badline/drives/ieee_bus.rb +28 -0
  17. data/lib/badline/drives/model1551/cable.rb +5 -2
  18. data/lib/badline/drives/model1551/tia.rb +13 -34
  19. data/lib/badline/drives/model1551.rb +26 -4
  20. data/lib/badline/drives.rb +1 -0
  21. data/lib/badline/frontend/app.rb +1 -1
  22. data/lib/badline/frontend/controls.rb +23 -3
  23. data/lib/badline/frontend/gamepads.rb +10 -3
  24. data/lib/badline/frontend/keys.rb +88 -0
  25. data/lib/badline/frontend/media_slots.rb +1 -0
  26. data/lib/badline/frontend/menu/menu_dialogs.rb +2 -1
  27. data/lib/badline/frontend/menu/menu_media.rb +27 -28
  28. data/lib/badline/frontend/menu/menu_pages.rb +15 -2
  29. data/lib/badline/frontend/menu/menu_ports.rb +22 -1
  30. data/lib/badline/frontend/menu/pause_menu.rb +1 -0
  31. data/lib/badline/frontend/pet_keyboard.rb +124 -0
  32. data/lib/badline/frontend/screens.rb +2 -1
  33. data/lib/badline/frontend/timeline.rb +16 -9
  34. data/lib/badline/frontend.rb +1 -0
  35. data/lib/badline/kernal_trap/dos/writes.rb +3 -1
  36. data/lib/badline/kernal_trap/dos.rb +6 -1
  37. data/lib/badline/kernal_trap/frames.rb +117 -0
  38. data/lib/badline/kernal_trap/ieee.rb +148 -0
  39. data/lib/badline/kernal_trap/ieee_layout.rb +113 -0
  40. data/lib/badline/kernal_trap/keyed_state.rb +4 -4
  41. data/lib/badline/kernal_trap/routine.rb +3 -53
  42. data/lib/badline/kernal_trap/serial.rb +4 -102
  43. data/lib/badline/kernal_trap/steps.rb +64 -0
  44. data/lib/badline/kernal_trap.rb +4 -0
  45. data/lib/badline/machine/keyboard_buffer.rb +8 -0
  46. data/lib/badline/machine/settling.rb +1 -1
  47. data/lib/badline/machine.rb +28 -6
  48. data/lib/badline/media/extensions.rb +5 -2
  49. data/lib/badline/media/pet_media.rb +40 -0
  50. data/lib/badline/media/program.rb +14 -2
  51. data/lib/badline/media/vic20_media.rb +2 -2
  52. data/lib/badline/media.rb +36 -16
  53. data/lib/badline/options/family.rb +17 -6
  54. data/lib/badline/options/help.rb +19 -3
  55. data/lib/badline/options/table.rb +14 -2
  56. data/lib/badline/options/validation.rb +68 -5
  57. data/lib/badline/options.rb +31 -19
  58. data/lib/badline/pet/bus/expansion.rb +106 -0
  59. data/lib/badline/pet/bus.rb +204 -0
  60. data/lib/badline/pet/canvas.rb +97 -0
  61. data/lib/badline/pet/cassettes.rb +63 -0
  62. data/lib/badline/pet/characters.rb +26 -0
  63. data/lib/badline/pet/cpu.rb +10 -0
  64. data/lib/badline/pet/crtc_video.rb +142 -0
  65. data/lib/badline/pet/discrete_video.rb +83 -0
  66. data/lib/badline/pet/ieee_ports.rb +66 -0
  67. data/lib/badline/pet/ieee_traps.rb +75 -0
  68. data/lib/badline/pet/keyboard_ports.rb +74 -0
  69. data/lib/badline/pet/keyed_modules.rb +85 -0
  70. data/lib/badline/pet/keyed_parts.rb +123 -0
  71. data/lib/badline/pet/keyed_state.rb +42 -0
  72. data/lib/badline/pet/model.rb +113 -0
  73. data/lib/badline/pet/rom_sets.rb +65 -0
  74. data/lib/badline/pet/sound.rb +128 -0
  75. data/lib/badline/pet/via.rb +10 -0
  76. data/lib/badline/pet/via_ports.rb +19 -0
  77. data/lib/badline/pet.rb +226 -0
  78. data/lib/badline/plus4/keyed_state.rb +5 -3
  79. data/lib/badline/plus4/tcbm_drive.rb +14 -0
  80. data/lib/badline/plus4.rb +1 -1
  81. data/lib/badline/roms/README +137 -0
  82. data/lib/badline/roms/pet/basic1.rom +0 -0
  83. data/lib/badline/roms/pet/basic2.rom +0 -0
  84. data/lib/badline/roms/pet/basic4.rom +0 -0
  85. data/lib/badline/roms/pet/characters1.rom +0 -0
  86. data/lib/badline/roms/pet/characters2.rom +0 -0
  87. data/lib/badline/roms/pet/editor1.rom +0 -0
  88. data/lib/badline/roms/pet/editor2-b.rom +0 -0
  89. data/lib/badline/roms/pet/editor2-n.rom +0 -0
  90. data/lib/badline/roms/pet/editor4-40-n-50hz.rom +0 -0
  91. data/lib/badline/roms/pet/editor4-40-n-60hz.rom +0 -0
  92. data/lib/badline/roms/pet/editor4-80-b-50hz-0283.rom +0 -0
  93. data/lib/badline/roms/pet/editor4-80-b-50hz.rom +0 -0
  94. data/lib/badline/roms/pet/editor4-80-b-60hz.rom +0 -0
  95. data/lib/badline/roms/pet/editor4-b.rom +0 -0
  96. data/lib/badline/roms/pet/editor4-n.rom +0 -0
  97. data/lib/badline/roms/pet/kernal1.rom +0 -0
  98. data/lib/badline/roms/pet/kernal2.rom +0 -0
  99. data/lib/badline/roms/pet/kernal4.rom +0 -0
  100. data/lib/badline/snapshot/container.rb +2 -0
  101. data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
  102. data/lib/badline/snapshot/field_doc.rb +16 -3
  103. data/lib/badline/snapshot/image.rb +14 -7
  104. data/lib/badline/snapshot/pet_setup.rb +71 -0
  105. data/lib/badline/snapshot/storage_setup.rb +7 -1
  106. data/lib/badline/snapshot.rb +9 -5
  107. data/lib/badline/storage/d80_image.rb +48 -0
  108. data/lib/badline/storage/d82_image.rb +21 -0
  109. data/lib/badline/storage/tap.rb +16 -3
  110. data/lib/badline/storage.rb +2 -0
  111. data/lib/badline/support/pet_checkpoint.rb +38 -0
  112. data/lib/badline/timing.rb +5 -4
  113. data/lib/badline/version.rb +1 -1
  114. data/lib/badline/vic20/attachments.rb +7 -2
  115. data/lib/badline/vic20/bus/keyed_state.rb +5 -4
  116. data/lib/badline/vic20/bus.rb +5 -0
  117. data/lib/badline/vic20.rb +1 -1
  118. metadata +55 -1
@@ -0,0 +1,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/pet/rom_sets"
4
+ require "badline/pet/model"
5
+ require "badline/pet/cpu"
6
+ require "badline/pet/via"
7
+ require "badline/pet/via_ports"
8
+ require "badline/pet/keyboard_ports"
9
+ require "badline/pet/ieee_ports"
10
+ require "badline/pet/ieee_traps"
11
+ require "badline/pet/cassettes"
12
+ require "badline/pet/characters"
13
+ require "badline/pet/canvas"
14
+ require "badline/pet/discrete_video"
15
+ require "badline/pet/crtc_video"
16
+ require "badline/pet/bus"
17
+ require "badline/pet/sound"
18
+ require "badline/snapshot/pet_setup"
19
+ require "badline/pet/keyed_parts"
20
+ require "badline/pet/keyed_state"
21
+ require "badline/pet/keyed_modules"
22
+
23
+ module Badline
24
+ # The Commodore PET and CBM: a 6502 at 1 MHz, two 6520 PIAs at $E810 and
25
+ # $E820, a 6522 VIA at $E840 and, from the 4032 on, a 6545 CRTC at
26
+ # $E880. Every chip's IRQ is wired to the CPU's, and nothing pulls NMI.
27
+ # Nothing halts the CPU either: the video reads the screen RAM between
28
+ # its accesses.
29
+ #
30
+ # The video's vertical drive line, low in the retrace, reaches PIA 1's
31
+ # CB1, whose falling edge is the editor's 50 or 60 Hz interrupt, and the
32
+ # VIA's PB5. The discrete boards count it out themselves
33
+ # (DiscreteVideo), and the CRTC boards take it from the CRTC's vertical
34
+ # sync, inverted.
35
+ #
36
+ # The key matrix hangs off PIA 1 (KeyboardPorts), the graphics one or
37
+ # the business one as the model has, and type_text types through the
38
+ # editor's keyboard buffer.
39
+ #
40
+ # The discrete boards paint their picture (DiscreteVideo) on a Canvas.
41
+ # The CRTC boards paint theirs from the CRTC's outputs (CRTCVideo).
42
+ # The VIA's CB2 is the sound (Sound).
43
+ #
44
+ # Device 8 answers through traps on the KERNAL's IEEE-488 primitives
45
+ # (IEEETraps), on a bus nothing else is plugged into (IEEEPorts), and
46
+ # each cassette port has a datasette (Cassettes). It runs headless.
47
+ class PET
48
+ include Machine::KeyboardBuffer
49
+ include Machine::Clocking
50
+ include Machine::Settling
51
+ include IEEETraps
52
+
53
+ attr_reader :model, :bus, :cpu, :pia1, :pia2, :via, :crtc, :keyboard, :ieee, :cassettes, :cycles, :init_threshold
54
+
55
+ # The editor's frame rate: 50 or 60 on a CRTC board, 60 on the others.
56
+ attr_reader :refresh_hz
57
+
58
+ # The RAM below $8000, in K.
59
+ attr_reader :ram_kb
60
+
61
+ def ram = @bus.ram
62
+
63
+ # Reads and writes memory as the CPU sees it, through the bus.
64
+ def peek(addr) = @bus.peek(addr)
65
+
66
+ def poke(addr, value) = @bus.poke(addr, value)
67
+
68
+ def install_debug_register(&) = @bus.install_debug_register(&)
69
+
70
+ def family = :pet
71
+
72
+ # The datasette on cassette port #1, where a tape goes in (Cassettes).
73
+ def datasette = @cassettes.datasette1
74
+
75
+ # +model+ names one of Model::ALL, with +ram_kb+ of RAM below $8000
76
+ # (Model::RAM_SIZES) in place of the model's own when not nil. A CRTC
77
+ # model runs its 50 Hz editor, or its 60 Hz one for +refresh_hz+ 60.
78
+ # +basic+ and +editor+ name ROMs the board takes in place of its own
79
+ # (Model.upgraded).
80
+ def initialize(model: Model::DEFAULT, ram_kb: nil, refresh_hz: 50, debug: false, basic: nil, editor: nil)
81
+ @model = Model.upgraded(Model.named(model), basic, editor)
82
+ ram_kb ||= @model.ram_kb
83
+ raise ArgumentError, "a PET has no #{ram_kb}K RAM board" unless Model::RAM_SIZES.include?(ram_kb)
84
+
85
+ @ram_kb = ram_kb
86
+
87
+ @refresh_hz = @model.crtc? ? refresh_hz : 60
88
+ @crtc_video = @model.crtc?
89
+ @vertical_drive = true
90
+ matrix = @model.keyboard == :business ? KeyboardPorts::BUSINESS_MATRIX : KeyboardPorts::GRAPHICS_MATRIX
91
+ @keyboard = Input::Keyboard.new(matrix:)
92
+ keyboard_ports = KeyboardPorts.new(@keyboard)
93
+ registers = Array.new(Chips::CRTC::MASKS.length, 0)
94
+ @pia1 = Chips::PIA.new(peripheral: keyboard_ports)
95
+ @ieee = IEEEPorts.new(Drives::IEEEBus.new)
96
+ @pia2 = Chips::PIA.new(peripheral: @ieee)
97
+ @via = VIA.new(start: 0xe800, peripheral: VIAPorts.new(self))
98
+ @sound = Sound.new(@via)
99
+ @cassettes = Cassettes.new(@pia1, @via)
100
+ keyboard_ports.connect(@pia1, @ieee, @cassettes)
101
+ characters = Characters.new(@model.characters)
102
+ @crtc_output = CRTCVideo.new(characters, @via, registers, columns80: @model.video == :crtc80)
103
+ @crtc = Chips::CRTC.new(@crtc_output, registers:)
104
+ @ieee.connect(@pia1, @pia2, @via)
105
+ @bus = Bus.new(model: @model, ram_kb:, editor: @model.editor(@refresh_hz), pia1: @pia1, pia2: @pia2,
106
+ via: @via, crtc: @crtc)
107
+ @crtc_output.cells = @bus.screen.cells
108
+ @discrete = DiscreteVideo.new(@bus.screen.cells, characters, @via)
109
+ @canvas = @crtc_video ? @crtc_output.canvas : @discrete.canvas
110
+ cassettes = @cassettes
111
+ @bus.on_io_write { cassettes.lines_written }
112
+ @cpu = CPU.new(@bus, debug:)
113
+ @cycles = 0
114
+ @init_handlers = []
115
+ @pending_keys = nil
116
+ @dos = nil
117
+ @ieee_trap = nil
118
+ @capture_output = nil
119
+ @init_threshold = boot_cycles(ram_kb)
120
+ end
121
+
122
+ # The vertical drive line, high but in the retrace.
123
+ def vertical_drive? = @vertical_drive
124
+
125
+ # The model, its RAM and its editor's frame rate as the pause menu
126
+ # names them, such as "8032, 32K RAM, 50 HZ".
127
+ def model_label = "#{@model.name.upcase}, #{@ram_kb}K RAM, #{@refresh_hz} HZ"
128
+
129
+ # The Canvas a front end shows.
130
+ def video = @canvas
131
+
132
+ # The 1 MHz clock and the frame the editor sets up, and all of the
133
+ # Canvas. The CRTC editors' lines and frames are their ROM's
134
+ # (ROMSets.crtc_frame), which a program can change. 80 columns show
135
+ # two dots to a window pixel.
136
+ def timing
137
+ crop = [0, 0, @canvas.width, @canvas.height]
138
+ if @crtc_video
139
+ cycles_per_line, lines_per_frame = ROMSets.crtc_frame(@model.editor(@refresh_hz), @refresh_hz)
140
+ Timing.new(clock_hz: 1_000_000, cycles_per_line:, lines_per_frame:,
141
+ crop:, pixel_width: 1, dots: @model.video == :crtc80 ? 2 : 1)
142
+ else
143
+ Timing.new(clock_hz: 1_000_000, cycles_per_line: DiscreteVideo::CYCLES_PER_LINE,
144
+ lines_per_frame: DiscreteVideo::LINES_PER_FRAME, crop:, pixel_width: 1, dots: 1)
145
+ end
146
+ end
147
+
148
+ # The video counts and moves the vertical drive line, then the PIAs
149
+ # and the VIA clock, ahead of the CPU.
150
+ def cycle!
151
+ handle_init if @cycles == @init_threshold
152
+ feed_keyboard if @pending_keys
153
+
154
+ if @crtc_video
155
+ @crtc.cycle!
156
+ drive_retrace(!@crtc.vsync?)
157
+ else
158
+ @discrete.cycle!
159
+ drive_retrace(@discrete.vertical_drive?)
160
+ end
161
+ @pia1.cycle!
162
+ @pia2.cycle!
163
+ @via.cycle!
164
+ @sound.cycle! if @sound.recording?
165
+ @cassettes.cycle!
166
+ @cpu.irq = @pia1.irq? || @pia2.irq? || @via.irq?
167
+ @cpu.cycle!
168
+
169
+ @cycles += 1
170
+ end
171
+
172
+ # What a front end plays: the VIA's CB2 (Sound), which answers #record
173
+ # and #drain_samples.
174
+ def sound_source = @sound
175
+
176
+ # The RES line reaches the CPU, the PIAs, the VIA, the CRTC and the
177
+ # 8096's control register.
178
+ def reset!
179
+ @pia1.reset!
180
+ @pia2.reset!
181
+ @via.reset!
182
+ @crtc.reset!
183
+ @bus.reset!
184
+ @cpu.reset!
185
+ end
186
+
187
+ # Switches the machine off and on: RAM starts cleared, and the RES
188
+ # line resets everything else.
189
+ def power_cycle!
190
+ @bus.power_on!
191
+ reset!
192
+ end
193
+
194
+ # Puts a program file's bytes where its first two say, through the bus,
195
+ # and returns that address.
196
+ def load_prg(data)
197
+ load_addr = data[0] | (data[1] << 8)
198
+ bytes = data[2..] || []
199
+ bytes = bytes[0, 0x10000 - load_addr] if load_addr + bytes.length > 0x10000
200
+ bytes.each_with_index { |byte, i| @bus.poke(load_addr + i, byte) }
201
+ load_addr
202
+ end
203
+
204
+ # Where the editor keeps its keyboard buffer: in page 2 under BASIC 1,
205
+ # in the zero page and page 2 under BASIC 2 and 4.
206
+ def keyboard_buffer
207
+ @model.basic1? ? Machine::KeyboardBuffer::PET_BASIC1_LAYOUT : Machine::KeyboardBuffer::PET_LAYOUT
208
+ end
209
+
210
+ private
211
+
212
+ # The cycle by which the KERNAL has booted and BASIC printed READY,
213
+ # with a margin. The RAM test takes most of it, about 41,000 to
214
+ # 44,000 cycles a K, and the CRTC editors take another 600,000 or so
215
+ # before it: READY is up by about 440,000 on a 2001 with 8K, 1,450,000
216
+ # on a 3032 and 1,980,000 on an 8032.
217
+ def boot_cycles(ram_kb) = (@model.basic == "pet/basic4.rom" ? 700_000 : 150_000) + (46_000 * ram_kb)
218
+
219
+ def drive_retrace(high)
220
+ return if high == @vertical_drive
221
+
222
+ @vertical_drive = high
223
+ @pia1.cb1 = high
224
+ end
225
+ end
226
+ end
@@ -4,9 +4,9 @@ module Badline
4
4
  class Plus4
5
5
  # The machine's own fields in a keyed snapshot module (Snapshot::Keyed,
6
6
  # BL-MACHINE): the family, its setup by name (Snapshot::Plus4Setup), the
7
- # cycle count, and the host's typing still to feed and on_init handlers
8
- # still to run under host.*. The badline version that wrote it is
9
- # written for reports and never read.
7
+ # cycle count, and the host's typing still to feed, on_init handlers
8
+ # still to run and the model plug_true_drive plugs in under host.*. The
9
+ # badline version that wrote it is written for reports and never read.
10
10
  module KeyedState
11
11
  # How many more on_init handlers the machine a snapshot was taken of had
12
12
  # yet to run than this one has. Nil until the machine is restored.
@@ -29,6 +29,7 @@ module Badline
29
29
  pending = @pending_keys
30
30
  out.ints("host.pending_keys", pending) if pending
31
31
  out.int("host.init_pending", booting? ? @init_handlers.length : 0)
32
+ out.text("host.true_drive_model", @true_drive_model)
32
33
  end
33
34
 
34
35
  def load_machine_fields(input)
@@ -36,6 +37,7 @@ module Badline
36
37
  pending = input.ints("host.pending_keys", [])
37
38
  @pending_keys = pending.empty? ? nil : pending.dup
38
39
  @init_handlers_lost = [input.int("host.init_pending", 0) - @init_handlers.length, 0].max
40
+ @true_drive_model = input.choice("host.true_drive_model", TCBMDrive::TRUE_DRIVE_MODELS, @true_drive_model)
39
41
  end
40
42
  end
41
43
 
@@ -32,6 +32,7 @@ module Badline
32
32
  drive.host_clock_hz = timing.clock_hz
33
33
  @drive1551 = drive
34
34
  @bus.plug_tia(drive.device, drive.host_tia)
35
+ drive.on_renumber { |old| drive1551_renumbered(old) }
35
36
  @serial_trap&.device = serial_trap_device
36
37
  @init_threshold = [@init_threshold, Badline::Drives::Model1551::BOOT_CYCLES].max if @cycles < @init_threshold
37
38
  end
@@ -43,6 +44,7 @@ module Badline
43
44
  return unless drive
44
45
 
45
46
  @bus.plug_tia(drive.device, tcbm_tia(drive.device))
47
+ drive.on_renumber
46
48
  @drive1551 = nil
47
49
  @serial_trap&.device = serial_trap_device
48
50
  end
@@ -93,6 +95,18 @@ module Badline
93
95
  @drive1551.load_modules(modules, input, "BL-D#{device}")
94
96
  end
95
97
 
98
+ # The 1551's DEV line moved it from device +old+ to the other: its
99
+ # interface's TIA moves with it, and the TCBM interface's own takes
100
+ # its old place.
101
+ def drive1551_renumbered(old)
102
+ drive = @drive1551
103
+ return unless drive
104
+
105
+ @bus.plug_tia(old, tcbm_tia(old))
106
+ @bus.plug_tia(drive.device, drive.host_tia)
107
+ @serial_trap&.device = serial_trap_device
108
+ end
109
+
96
110
  # The TCBM interface's own TIA for +device+, or nil without one.
97
111
  def tcbm_tia(device)
98
112
  tias = @tias
data/lib/badline/plus4.rb CHANGED
@@ -89,7 +89,7 @@ module Badline
89
89
  ntsc = profile == Chips::TED::NTSC
90
90
  Timing.new(clock_hz: profile.clock_hz, cycles_per_line: Chips::TED::Beam::CYCLES_PER_LINE,
91
91
  lines_per_frame: ntsc ? 262 : 312, crop: ntsc ? [0, 248, 384, 242] : [0, 276, 384, 288],
92
- pixel_width: 1)
92
+ pixel_width: 1, dots: 1)
93
93
  end
94
94
 
95
95
  # Reads and writes memory as the CPU sees it, through the bus.
@@ -179,3 +179,140 @@ plus4/function-high.rom
179
179
  The Plus/4's 3-plus-1 function ROM, high half, 317054-01
180
180
  (3-plus-1.317054-01.bin), for $C000-$FFFF in ROM bank 1.
181
181
  SHA-1 0ad7ac2db7da692d972e586ca0dfd747d82c7693.
182
+
183
+ Commodore PET/CBM ROMs
184
+ ======================
185
+
186
+ Downloaded from
187
+ https://www.zimmers.net/anonftp/pub/cbm/firmware/computers/pet/
188
+ Each BASIC and KERNAL pair is one ROM set, and the editors and the
189
+ character sets are files of their own, so a model's stock set and the
190
+ upgrade ROMs Commodore sold can be picked apart. PET::ROMSets
191
+ (pet/rom_sets.rb) names them for --basic and --editor and says which
192
+ fit each model, and PET::Bus maps the ones fitted.
193
+
194
+ pet/basic1.rom
195
+ --------------
196
+ BASIC 1 for $C000-$DFFF, four 2K chips concatenated:
197
+ 901447-09 (rom-2-c000.901447-09.bin, the revision that replaced
198
+ 901447-01), 901447-02, 901447-03 and 901447-04 (rom-1-c800, rom-1-d000,
199
+ rom-1-d800).
200
+ SHA-1 3be9cd41b646fefa00d80f1c3433a06146e97c59.
201
+
202
+ pet/editor1.rom
203
+ ---------------
204
+ The BASIC 1 screen editor, 901447-05 (rom-1-e000.901447-05.bin), for
205
+ $E000-$E7FF.
206
+ SHA-1 f02f5fb492ba93dbbd390f24c10f7a832dec432a.
207
+
208
+ pet/kernal1.rom
209
+ ---------------
210
+ The BASIC 1 KERNAL for $F000-$FFFF, 901447-06 and 901447-07
211
+ (rom-1-f000.901447-06.bin, rom-1-f800.901447-07.bin) concatenated.
212
+ SHA-1 32dc08828e88c74d6433156e64d402b564556308.
213
+
214
+ pet/characters1.rom
215
+ -------------------
216
+ The 2001's character generator, 901447-08 (characters-1.901447-08.bin),
217
+ with upper and lower case swapped against 901447-10.
218
+ SHA-1 3e067cc621e4beafca2b90cb8f6dba975df2855b.
219
+
220
+ pet/basic2.rom
221
+ --------------
222
+ BASIC 2 for $C000-$DFFF, 901465-01 and 901465-02
223
+ (basic-2-c000.901465-01.bin, basic-2-d000.901465-02.bin) concatenated.
224
+ The 2K chips of the 6540 boards (rom-3-*) hold the same bytes.
225
+ SHA-1 9322a9f0354b9049847d98d3a045fbec860a6264.
226
+
227
+ pet/kernal2.rom
228
+ ---------------
229
+ The BASIC 2 KERNAL, 901465-03 (kernal-2.901465-03.bin), for $F000-$FFFF.
230
+ SHA-1 38742bdf449f629bcba6276ef24d3daeb7da6e84.
231
+
232
+ pet/editor2-n.rom
233
+ -----------------
234
+ The BASIC 2 editor for the graphics keyboard, 901447-24
235
+ (edit-2-n.901447-24.bin), for $E000-$E7FF.
236
+ SHA-1 5e5502ce32f5a7e387d65efe058916282041e54b.
237
+
238
+ pet/editor2-b.rom
239
+ -----------------
240
+ The BASIC 2 editor for the business keyboard, 901474-01
241
+ (edit-2-b.901474-01.bin), for $E000-$E7FF.
242
+ SHA-1 174ace3a8c0348cd21d39cc864e2adc58b0101a9.
243
+
244
+ pet/characters2.rom
245
+ -------------------
246
+ The character generator of BASIC 2 and 4 machines, 901447-10
247
+ (characters-2.901447-10.bin).
248
+ SHA-1 0157a2d55b7ac4eaeb38475889ebeea52e2593db.
249
+
250
+ pet/basic4.rom
251
+ --------------
252
+ BASIC 4 for $B000-$DFFF, 901465-23, 901465-20 and 901465-21
253
+ (basic-4-b000.901465-23.bin, basic-4-c000.901465-20.bin,
254
+ basic-4-d000.901465-21.bin) concatenated. 901465-23 is the patched
255
+ $B000 chip that replaced 901465-19. The 8296's 324746-01 holds the same
256
+ four chips.
257
+ SHA-1 d70bec479e1e3a0d0908a4897066e9959bbc11c8.
258
+
259
+ pet/kernal4.rom
260
+ ---------------
261
+ The BASIC 4 KERNAL, 901465-22 (kernal-4.901465-22.bin), for
262
+ $F000-$FFFF.
263
+ SHA-1 96a0fa56e0c937da92971d9c99d504e44e898806.
264
+
265
+ pet/editor4-n.rom
266
+ -----------------
267
+ The BASIC 4 editor for the graphics keyboard without a CRTC, 901447-29
268
+ (edit-4-n.901447-29.bin), for $E000-$E7FF.
269
+ SHA-1 e88f56e5c54b0e8d8d4e8cb39a4647c803c1f51c.
270
+
271
+ pet/editor4-b.rom
272
+ -----------------
273
+ The BASIC 4 editor for the business keyboard without a CRTC, 901474-02
274
+ (edit-4-b.901474-02.bin), for $E000-$E7FF.
275
+ SHA-1 0ca5c4e8f532f914cb0bf86ea9900f20f0a655ce.
276
+
277
+ pet/editor4-40-n-50hz.rom
278
+ -------------------------
279
+ The 4032's 50 Hz editor, 40 columns on a CRTC, graphics keyboard,
280
+ 901498-01 (edit-4-40-n-50Hz.901498-01.bin), for $E000-$E7FF.
281
+ SHA-1 05af284c914d53a52987b5f602466de75765f650.
282
+
283
+ pet/editor4-40-n-60hz.rom
284
+ -------------------------
285
+ The 4032's 60 Hz editor, 901499-01 (edit-4-40-n-60Hz.901499-01.bin),
286
+ for $E000-$E7FF.
287
+ SHA-1 8cbf086c1ce4dfb2a2fe24c47476dfb878493dee.
288
+
289
+ pet/editor4-80-b-50hz.rom
290
+ -------------------------
291
+ The 8032's 50 Hz editor, 80 columns on a CRTC, business keyboard,
292
+ 901474-04, the chip dated 3681
293
+ (edit-4-80-b-50Hz.901474-04-3681.bin), for $E000-$E7FF.
294
+ SHA-1 7040b283ba39e9630e3d147f7d076b7abc39bc70.
295
+
296
+ zimmers.net has three dumps of 901474-04 that differ. This one is the
297
+ 60 Hz 901474-03 below with the 50 Hz jiffy clock patch at $E431-$E441
298
+ and $E455, the CRTC's vertical registers (R4, R5 and R7) changed in both
299
+ of its register tables at $E72A, and the byte at $E787 changed to keep
300
+ the ROM's byte sum at $8A, the same as 901474-03's. The line length (R0)
301
+ stays the 60 Hz editor's. A second chip, dated 0384, reads the same
302
+ (8296/edit-50hz-324243-02b.txt). The -0283 dump changes R0 and sums to
303
+ $AE, and the _alt dump changes R0 and $E787.
304
+
305
+ pet/editor4-80-b-50hz-0283.rom
306
+ ------------------------------
307
+ The 8032's other 50 Hz editor, 901474-04, the chip dated 0283
308
+ (edit-4-80-b-50Hz.901474-04-0283.bin), for $E000-$E7FF, which
309
+ --editor 4-80-b-50hz-0283 fits. It differs from the 3681 dump only in
310
+ the CRTC's register tables at $E72A and $E73C: lines of 64 cycles (R0
311
+ 63) and frames of 313 lines, with nine scanlines a character row.
312
+ SHA-1 81975eab31a8f4f51ae2a20d099a567c7b3f2dd1.
313
+
314
+ pet/editor4-80-b-60hz.rom
315
+ -------------------------
316
+ The 8032's 60 Hz editor, 901474-03 (edit-4-80-b-60Hz.901474-03.bin),
317
+ for $E000-$E7FF.
318
+ SHA-1 c605fa343fd77c73cbe1e0e9567e2f014f6e7e30.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -36,6 +36,8 @@ module Badline
36
36
  C128 = "C128"
37
37
  # xplus4 names its machine PLUS4, for the C16 too.
38
38
  PLUS4 = "PLUS4"
39
+ # xpet names its machine PET, for every model.
40
+ PET = "PET"
39
41
  GZIP_MAGIC = "\x1f\x8b".b
40
42
 
41
43
  # The VICE version block, when VICE wrote the file: four version
@@ -7,7 +7,7 @@ module Badline
7
7
  # its default, with keys that differ only in a unit's number, such as
8
8
  # voice1 to voice3, on one row.
9
9
  class Markdown
10
- NAMES = { "c64" => "C64", "vic20" => "VIC-20", "c128" => "C128", "plus4" => "Plus/4" }.freeze
10
+ NAMES = { "c64" => "C64", "vic20" => "VIC-20", "c128" => "C128", "plus4" => "Plus/4", "pet" => "PET" }.freeze
11
11
  TAG_TYPES = { Keyed::INT => "int", Keyed::BOOL => "bool", Keyed::BYTES => "bytes", Keyed::INTS => "ints",
12
12
  Keyed::TEXT => "text" }.freeze
13
13
 
@@ -36,8 +36,9 @@ module Badline
36
36
 
37
37
  # The machines the doc is made from: each family at power-on, the C64
38
38
  # with each kind of expansion, cartridge and media, and a second SID,
39
- # and the 264 series' models, its TCBM interface, cartridge, tape and
40
- # true drives, the 1541 and the 1551.
39
+ # the 264 series' models, its TCBM interface, cartridge, tape and
40
+ # true drives, the 1541 and the 1551, and a CRTC PET, a 2001, an 8096
41
+ # and a PET with a disk and tapes.
41
42
  def self.machines
42
43
  {
43
44
  "c64" => [C64.new, C64.new(ram_expansion: :plus256k), C64.new(reu: 512),
@@ -48,10 +49,22 @@ module Badline
48
49
  "plus4" => [Plus4.new, Plus4.new(model: "c16"), Plus4.new(tcbm: true), Plus4.new(sid_card: :mos6581),
49
50
  Plus4.new(sid_card: :mos8580, sid_card_address: 0xfe80),
50
51
  Plus4.new(ram_expansion: :csory256),
51
- plus4_with_cartridge, plus4_with_tape, plus4_with_drive, plus4_with_drive("1551")]
52
+ plus4_with_cartridge, plus4_with_tape, plus4_with_drive, plus4_with_drive("1551")],
53
+ "pet" => [PET.new, PET.new(model: "2001"), PET.new(model: "8096"), pet_with_media]
52
54
  }
53
55
  end
54
56
 
57
+ # A PET with a disk in device 8 through the traps and a tape in each
58
+ # datasette.
59
+ def self.pet_with_media
60
+ bytes = "C64-TAPE-RAW".bytes + [1, 3, 0, 0, 4, 0, 0, 0, 0x30, 0x30, 0x30, 0x30]
61
+ PET.new.tap do |machine|
62
+ machine.mount(Storage::D64Image.new("", read_only: true, bytes: blank(174_848)))
63
+ machine.cassettes.datasette1.insert(Storage::TAP.new("", bytes:))
64
+ machine.cassettes.datasette2.insert(Storage::TAP.new("", bytes:))
65
+ end
66
+ end
67
+
55
68
  def self.blank(length) = Array.new(length, 0)
56
69
 
57
70
  def self.with_cartridge(type)
@@ -34,7 +34,7 @@ module Badline
34
34
  # of a machine in memory (.of).
35
35
  class Image
36
36
  MACHINE = "BL-MACHINE"
37
- FAMILIES = { "c64" => "C64", "vic20" => "VIC-20", "c128" => "C128", "plus4" => "Plus/4" }.freeze
37
+ FAMILIES = { "c64" => "C64", "vic20" => "VIC-20", "c128" => "C128", "plus4" => "Plus/4", "pet" => "PET" }.freeze
38
38
 
39
39
  attr_reader :container
40
40
 
@@ -54,10 +54,11 @@ module Badline
54
54
  end
55
55
 
56
56
  # The snapshot with VICE's modules ahead of the BL-* ones for a C64,
57
- # so x64sc opens it too. Raises FormatError for a Plus/4, whose
58
- # snapshots badline doesn't write for xplus4.
57
+ # so x64sc opens it too. Raises FormatError for a Plus/4 or a PET,
58
+ # whose snapshots badline doesn't write for xplus4 or xpet.
59
59
  def self.exported(computer)
60
60
  raise FormatError, "badline doesn't export a Plus/4's snapshot for VICE" if computer.family == :plus4
61
+ raise FormatError, "badline doesn't export a PET's snapshot for VICE" if computer.family == :pet
61
62
 
62
63
  computer.settle_for_snapshot!
63
64
  sections = computer.save_modules
@@ -71,6 +72,7 @@ module Badline
71
72
  when :vic20 then Container::VIC20
72
73
  when :c128 then Container::C128
73
74
  when :plus4 then Container::PLUS4
75
+ when :pet then Container::PET
74
76
  else Container::MACHINE
75
77
  end
76
78
  end
@@ -82,6 +84,7 @@ module Badline
82
84
  when "vic20" then Vic20Setup.read_fields(machine)
83
85
  when "c128" then C128Setup.read_fields(machine)
84
86
  when "plus4" then Plus4Setup.read_fields(machine)
87
+ when "pet" then PETSetup.read_fields(machine)
85
88
  else C64Setup.read_fields(machine)
86
89
  end
87
90
  end
@@ -97,15 +100,16 @@ module Badline
97
100
 
98
101
  # A new machine, built as the snapshot's was and restored from it. A
99
102
  # badline snapshot restores from its BL-* modules alone, and a VICE
100
- # one through the modules badline reads. xvic's, x128's and xplus4's own
101
- # modules aren't read. Yields a line for each thing left out, or
102
- # warns it without a block. `detached` restores it without the host's
103
- # files (Keyed::Modules).
103
+ # one through the modules badline reads. xvic's, x128's, xplus4's and
104
+ # xpet's own modules aren't read. Yields a line for each thing left
105
+ # out, or warns it without a block. `detached` restores it without
106
+ # the host's files (Keyed::Modules).
104
107
  def load(detached: false, &)
105
108
  refuse_old_layout
106
109
  raise FormatError, "badline reads only its own VIC-20 snapshots, not xvic's" if vic20? && !badline?
107
110
  raise FormatError, "badline reads only its own C128 snapshots, not x128's" if c128? && !badline?
108
111
  raise FormatError, "badline reads only its own Plus/4 snapshots, not xplus4's" if plus4? && !badline?
112
+ raise FormatError, "badline reads only its own PET snapshots, not xpet's" if pet? && !badline?
109
113
  return load_vice(&) unless badline?
110
114
 
111
115
  modules = Keyed::Modules.new(sections, detached:)
@@ -123,6 +127,9 @@ module Badline
123
127
  # Whether the machine is of the 264 series, as the container names it.
124
128
  def plus4? = @container.machine == Container::PLUS4
125
129
 
130
+ # Whether the machine is a PET, as the container names it.
131
+ def pet? = @container.machine == Container::PET
132
+
126
133
  # Restores `computer` from the snapshot, and returns the Report. The
127
134
  # snapshot goes into a new machine first, so one that fails, or is of
128
135
  # a machine built another way, leaves `computer` as it was.