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.
Files changed (195) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +193 -0
  3. data/CLAUDE.md +19 -7
  4. data/CONTRIBUTING.md +11 -1
  5. data/README.md +178 -26
  6. data/doc/library.md +5 -1
  7. data/doc/media.md +3 -2
  8. data/doc/pinned-behaviour.md +169 -1
  9. data/doc/snapshot-format.md +302 -35
  10. data/doc/snapshots.md +97 -26
  11. data/doc/vice.md +344 -0
  12. data/exe/badline-ruby +5 -0
  13. data/lib/badline/audio/renderer.rb +4 -8
  14. data/lib/badline/audio/terminal.rb +34 -2
  15. data/lib/badline/c128/keyed_state.rb +2 -0
  16. data/lib/badline/c128.rb +5 -0
  17. data/lib/badline/c64/bus/fittings.rb +6 -0
  18. data/lib/badline/c64/keyed_state.rb +2 -0
  19. data/lib/badline/c64.rb +7 -0
  20. data/lib/badline/cbm2/keyed_state.rb +4 -0
  21. data/lib/badline/cbm2/p500.rb +9 -3
  22. data/lib/badline/cbm2.rb +10 -3
  23. data/lib/badline/chips/cia/timer.rb +2 -2
  24. data/lib/badline/chips/cpu/core.rb +29 -4
  25. data/lib/badline/chips/cpu/instruction_set.rb +1 -1
  26. data/lib/badline/chips/cpu/interrupts.rb +9 -0
  27. data/lib/badline/chips/cpu/keyed_state.rb +15 -9
  28. data/lib/badline/chips/cpu/microcode.rb +10 -0
  29. data/lib/badline/chips/m6809/arithmetic.rb +140 -0
  30. data/lib/badline/chips/m6809/bus_steps.rb +223 -0
  31. data/lib/badline/chips/m6809/core.rb +149 -0
  32. data/lib/badline/chips/m6809/indexed.rb +82 -0
  33. data/lib/badline/chips/m6809/interrupts.rb +159 -0
  34. data/lib/badline/chips/m6809/keyed_state.rb +147 -0
  35. data/lib/badline/chips/m6809/opcodes.rb +159 -0
  36. data/lib/badline/chips/m6809/operations.rb +258 -0
  37. data/lib/badline/chips/m6809/plans.rb +143 -0
  38. data/lib/badline/chips/m6809/registers.rb +82 -0
  39. data/lib/badline/chips/m6809/stack.rb +140 -0
  40. data/lib/badline/chips/m6809.rb +13 -0
  41. data/lib/badline/chips/riot/timer.rb +14 -3
  42. data/lib/badline/chips/rriot.rb +8 -3
  43. data/lib/badline/chips/sid/decimator.rb +1 -1
  44. data/lib/badline/chips/sid/filter.rb +4 -4
  45. data/lib/badline/chips/sid/keyed_parts.rb +51 -18
  46. data/lib/badline/chips/sid/waveform.rb +6 -6
  47. data/lib/badline/chips/sid.rb +6 -2
  48. data/lib/badline/chips/vic_i/keyed_parts.rb +12 -2
  49. data/lib/badline/chips/vic_i/light_pen.rb +89 -0
  50. data/lib/badline/chips/vic_i.rb +91 -23
  51. data/lib/badline/data_path.rb +3 -3
  52. data/lib/badline/drives/ieee_models.rb +54 -0
  53. data/lib/badline/drives/model1001.rb +37 -0
  54. data/lib/badline/drives/model1541/sector_reader.rb +20 -3
  55. data/lib/badline/drives/model1541.rb +1 -0
  56. data/lib/badline/drives/model1551.rb +1 -0
  57. data/lib/badline/drives/model1571.rb +1 -0
  58. data/lib/badline/drives/model1581.rb +1 -0
  59. data/lib/badline/drives/model2031.rb +7 -0
  60. data/lib/badline/drives/model4040/disk.rb +62 -0
  61. data/lib/badline/drives/model4040/ip_bus.rb +46 -0
  62. data/lib/badline/drives/model4040/keyed_state.rb +57 -0
  63. data/lib/badline/drives/model4040/mechanism.rb +195 -0
  64. data/lib/badline/drives/model4040/parts.rb +20 -0
  65. data/lib/badline/drives/model4040.rb +195 -0
  66. data/lib/badline/drives/model8050/board.rb +79 -0
  67. data/lib/badline/drives/model8050/dos_board.rb +134 -0
  68. data/lib/badline/drives/model8050/fdc_bus.rb +7 -0
  69. data/lib/badline/drives/model8050/idle.rb +148 -31
  70. data/lib/badline/drives/model8050/keyed_state.rb +5 -2
  71. data/lib/badline/drives/model8050.rb +32 -130
  72. data/lib/badline/drives/model9060/idle.rb +169 -0
  73. data/lib/badline/drives/model9060/keyed_state.rb +123 -0
  74. data/lib/badline/drives/model9060/parts.rb +98 -0
  75. data/lib/badline/drives/model9060/sasi_bus.rb +88 -0
  76. data/lib/badline/drives/model9060/sasi_commands.rb +168 -0
  77. data/lib/badline/drives/model9060/sasi_target.rb +161 -0
  78. data/lib/badline/drives/model9060.rb +197 -0
  79. data/lib/badline/frontend/app.rb +8 -0
  80. data/lib/badline/frontend/boot.rb +3 -2
  81. data/lib/badline/frontend/controls.rb +62 -6
  82. data/lib/badline/frontend/keys.rb +36 -0
  83. data/lib/badline/frontend/kim1_panel.rb +208 -0
  84. data/lib/badline/frontend/media_slots.rb +8 -1
  85. data/lib/badline/frontend/menu/drive_types.rb +56 -0
  86. data/lib/badline/frontend/menu/menu_dialogs.rb +61 -14
  87. data/lib/badline/frontend/menu/menu_media.rb +42 -12
  88. data/lib/badline/frontend/menu/menu_pages.rb +97 -2
  89. data/lib/badline/frontend/menu/menu_ports.rb +38 -1
  90. data/lib/badline/frontend/menu/pause_menu.rb +5 -1
  91. data/lib/badline/frontend/pet_keyboard.rb +23 -1
  92. data/lib/badline/frontend/screen.rb +1 -1
  93. data/lib/badline/frontend/screens.rb +40 -2
  94. data/lib/badline/frontend/teletype_pane.rb +152 -0
  95. data/lib/badline/frontend/timeline.rb +31 -2
  96. data/lib/badline/frontend.rb +3 -0
  97. data/lib/badline/input/joystick.rb +15 -2
  98. data/lib/badline/kim1/bus.rb +117 -0
  99. data/lib/badline/kim1/cpu.rb +25 -0
  100. data/lib/badline/kim1/host_terminal.rb +74 -0
  101. data/lib/badline/kim1/key_entry.rb +74 -0
  102. data/lib/badline/kim1/keyed_modules.rb +53 -0
  103. data/lib/badline/kim1/keyed_state.rb +133 -0
  104. data/lib/badline/kim1/panel.rb +176 -0
  105. data/lib/badline/kim1/rriots.rb +28 -0
  106. data/lib/badline/kim1/sound.rb +29 -0
  107. data/lib/badline/kim1/tape.rb +118 -0
  108. data/lib/badline/kim1/teletype.rb +193 -0
  109. data/lib/badline/kim1.rb +194 -0
  110. data/lib/badline/machine/ieee_drives.rb +94 -117
  111. data/lib/badline/machine/keyboard_buffer.rb +20 -3
  112. data/lib/badline/machine/line_sound.rb +122 -0
  113. data/lib/badline/machine.rb +49 -15
  114. data/lib/badline/media/extensions.rb +8 -4
  115. data/lib/badline/media/kim1_media.rb +150 -0
  116. data/lib/badline/media/pet_media.rb +3 -1
  117. data/lib/badline/media/true_drive.rb +52 -45
  118. data/lib/badline/media/vic20_media.rb +4 -2
  119. data/lib/badline/media.rb +16 -6
  120. data/lib/badline/memory.rb +2 -4
  121. data/lib/badline/options/family.rb +98 -8
  122. data/lib/badline/options/help.rb +44 -12
  123. data/lib/badline/options/table.rb +44 -12
  124. data/lib/badline/options/validation.rb +44 -8
  125. data/lib/badline/options.rb +27 -8
  126. data/lib/badline/pet/bus.rb +16 -2
  127. data/lib/badline/pet/keyboard_ports.rb +9 -2
  128. data/lib/badline/pet/keyed_state.rb +4 -0
  129. data/lib/badline/pet/model.rb +11 -2
  130. data/lib/badline/pet/rom_sets.rb +1 -0
  131. data/lib/badline/pet/sound.rb +5 -105
  132. data/lib/badline/pet/superpet/acia.rb +12 -0
  133. data/lib/badline/pet/superpet/board.rb +221 -0
  134. data/lib/badline/pet/superpet/dongle.rb +92 -0
  135. data/lib/badline/pet/superpet/keyed_modules.rb +63 -0
  136. data/lib/badline/pet/superpet/keyed_parts.rb +76 -0
  137. data/lib/badline/pet/superpet/m6809.rb +32 -0
  138. data/lib/badline/pet/superpet/waterloo_traps.rb +199 -0
  139. data/lib/badline/pet/superpet.rb +221 -0
  140. data/lib/badline/pet.rb +23 -3
  141. data/lib/badline/plus4/keyed_state.rb +2 -0
  142. data/lib/badline/plus4.rb +5 -0
  143. data/lib/badline/roms/1001/fdc-251257-02a.rom +0 -0
  144. data/lib/badline/roms/4040/dos-1.2.rom +0 -0
  145. data/lib/badline/roms/4040/dos-2.1-rev.rom +0 -0
  146. data/lib/badline/roms/4040/dos-2.1.rom +0 -0
  147. data/lib/badline/roms/4040/fdc-901466-02.rom +0 -0
  148. data/lib/badline/roms/4040/fdc-901466-04.rom +0 -0
  149. data/lib/badline/roms/9060/dos-3.0.rom +0 -0
  150. data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
  151. data/lib/badline/roms/README +176 -0
  152. data/lib/badline/roms/kim1/6530-002.rom +0 -0
  153. data/lib/badline/roms/kim1/6530-003.rom +0 -0
  154. data/lib/badline/roms/pet/characters-superpet.rom +0 -0
  155. data/lib/badline/roms/pet/superpet-a000.rom +0 -0
  156. data/lib/badline/roms/pet/superpet-c000.rom +0 -0
  157. data/lib/badline/roms/pet/superpet-e000-50hz.rom +0 -0
  158. data/lib/badline/roms/pet/superpet-e000.rom +0 -0
  159. data/lib/badline/snapshot/container.rb +8 -4
  160. data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
  161. data/lib/badline/snapshot/field_doc.rb +35 -10
  162. data/lib/badline/snapshot/image.rb +19 -7
  163. data/lib/badline/snapshot/kim1_setup.rb +28 -0
  164. data/lib/badline/snapshot/pet_setup.rb +27 -11
  165. data/lib/badline/snapshot/storage_setup.rb +4 -1
  166. data/lib/badline/snapshot/vic20_setup.rb +11 -7
  167. data/lib/badline/snapshot/vice/pet.rb +14 -7
  168. data/lib/badline/snapshot/vice/pet_cpu.rb +39 -5
  169. data/lib/badline/snapshot/vice/pet_crtc.rb +15 -2
  170. data/lib/badline/snapshot/vice/pet_dongle.rb +72 -0
  171. data/lib/badline/snapshot/vice/pet_drive8050.rb +188 -68
  172. data/lib/badline/snapshot/vice/pet_mem.rb +55 -5
  173. data/lib/badline/snapshot/vice/pet_restore.rb +40 -11
  174. data/lib/badline/snapshot/vice/vic20.rb +70 -0
  175. data/lib/badline/snapshot/vice/vic20_cpu.rb +61 -0
  176. data/lib/badline/snapshot/vice/vic20_mem.rb +85 -0
  177. data/lib/badline/snapshot/vice/vic20_restore.rb +124 -0
  178. data/lib/badline/snapshot/vice/vic20_vici.rb +200 -0
  179. data/lib/badline/snapshot/vice.rb +1 -0
  180. data/lib/badline/snapshot.rb +8 -4
  181. data/lib/badline/status.rb +30 -30
  182. data/lib/badline/storage/d67_image.rb +55 -0
  183. data/lib/badline/storage/d90_image.rb +180 -0
  184. data/lib/badline/storage/kim_tape.rb +254 -0
  185. data/lib/badline/storage/papertape.rb +90 -0
  186. data/lib/badline/storage.rb +4 -0
  187. data/lib/badline/support/kim1_checkpoint.rb +41 -0
  188. data/lib/badline/support/superpet_checkpoint.rb +35 -0
  189. data/lib/badline/version.rb +1 -1
  190. data/lib/badline/vic20/keyed_modules.rb +6 -3
  191. data/lib/badline/vic20/keyed_state.rb +6 -4
  192. data/lib/badline/vic20/port_wiring.rb +11 -0
  193. data/lib/badline/vic20.rb +30 -12
  194. data/lib/badline.rb +1 -0
  195. metadata +85 -1
@@ -128,6 +128,91 @@ https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/old/8050/
128
128
  and named by the 8050's parts list
129
129
  (https://www.zimmers.net/anonftp/pub/cbm/schematics/drives/old/8050/parts.txt).
130
130
 
131
+ 1001/fdc-251257-02a.rom
132
+ -----------------------
133
+ The SFD-1001's floppy controller program, 251257-02A: the first 1 KB
134
+ of the 2716 on its 6530 adaptor (251252-01), whose A10 is grounded, so
135
+ the controller runs it from $FC00-$FFFF in place of the 6530's mask.
136
+ Its interface processor runs 8050/dos-2.7.rom, the same 16 KB as
137
+ 901887-01 and 901888-01. Drives::Model1001 loads it. The unit's
138
+ mechanism, a Matsushita JU-570-2, has 100 tracks an inch, as its spec
139
+ sheet in the 8250LP and SFD-1001 technical manual gives it, not the 96
140
+ that zimmers.net's description says.
141
+ SHA-1 473664d405942865d46a6e723eb7d3f243584f7a (the whole 2 KB EPROM,
142
+ SHA-1 3b954eb34f7ea088eed1d33ebc6d6e83a3e9be15).
143
+
144
+ Downloaded from
145
+ https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/old/1001/251257-02A.bin
146
+
147
+ 4040/dos-1.2.rom
148
+ ----------------
149
+ The 2040's DOS 1.2, the 3040's with DOS 1, 8 KB for the interface
150
+ processor's $E000-$FFFF: 901468-06 ($E000-$EFFF, SHA-1
151
+ 4d9658f2e6ff3276e5c6e224611a66ce44b16fc7) followed by 901468-07
152
+ ($F000-$FFFF, SHA-1 6a51c7954938439ca8342fc295bda050c06e1791). No DOS
153
+ 1.0 image is public, so both DOS 1 units run it, as xpet's 2040 does.
154
+ The 2040 is often listed with DOS 1.0, but this set is 1.2: 901468-07's
155
+ IRQ vector is $E2D6, and VICE-testprogs' vdrive/diskaddrchange.bas
156
+ names the drive whose $FFFF reads $E2 "2040 v1.2". The Commodore ROM
157
+ Genealogy (1980) lists it as the 2040's production set.
158
+ Drives::Model2040 loads it.
159
+ SHA-1 e37b3bfd801eb19dea50cf8b4a6af8613c3520d3.
160
+
161
+ 4040/fdc-901466-02.rom
162
+ ----------------------
163
+ The 6530 of the DOS 1 floppy controller, 901466-02, the same 1 KB as
164
+ 901466-01, run from $FC00-$FFFF.
165
+ SHA-1 d539858f839f96393f218307df7394362a84a26a.
166
+
167
+ 4040/dos-2.1.rom
168
+ ----------------
169
+ DOS 2.1, 12 KB for the interface processor's $D000-$FFFF: 901468-11
170
+ ($D000, SHA-1 8415f3159dea73161e0cef7960afa6c76953b6f8), 901468-12
171
+ ($E000, SHA-1 e8a94f239082d45f64f01b2d8e488d18fe659cbb) and 901468-13
172
+ ($F000, SHA-1 6ada7904ac9d13c3f1c0a8715f9c4be1aa6eb0bb). xpet's 3040
173
+ runs it, and so does Drives::Model3040.
174
+ SHA-1 02bd4b0c8508b3874b95799a9a63caf369c837de.
175
+
176
+ 4040/dos-2.1-rev.rom
177
+ --------------------
178
+ The revised DOS 2.1, 12 KB for $D000-$FFFF: 901468-14 ($D000, SHA-1
179
+ ffb992b82ec913ddff7be964d7527aca3e21580c), 901468-15 ($E000, SHA-1
180
+ f702d6917fe8a798740ba4d467b500944ae7b70a) and 901468-16 ($F000, SHA-1
181
+ 04b918cf4adeee8015b43383d3cea7288a7d0aa8). xpet's 4040 runs it, and
182
+ so does Drives::Model4040.
183
+ SHA-1 245abbe76581558d270cdbb6fe55f56748403d8e.
184
+
185
+ 4040/fdc-901466-04.rom
186
+ ----------------------
187
+ The 6530 of the DOS 2.1 floppy controller, 901466-04, run from
188
+ $FC00-$FFFF under both DOS 2.1 sets.
189
+ SHA-1 6645fa40b81be1ff7d1384e9b52df06a26ab0bfb.
190
+
191
+ All downloaded from
192
+ https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/old/4040/
193
+ and concatenated in address order.
194
+
195
+ 9060/dos-3.0.rom
196
+ ----------------
197
+ The D9060's and D9090's DOS 3.0, 16 KB for the interface processor's
198
+ $C000-$FFFF: 300516 rev C ($C000-$DFFF, SHA-1
199
+ bb1ddb5da94a86266012eca54818aa21dc4cef6a) followed by 300517 rev C
200
+ ($E000-$FFFF, SHA-1 4c17d014de48c906871b9b6c7d037d8736b1fd52), the set
201
+ xpet's 9000 runs.
202
+ SHA-1 c48df2cbb0eea656c3243df7ea8bfefcd171b8e8.
203
+
204
+ 9060/sasi-300515-revb.rom
205
+ -------------------------
206
+ The program of the D9060's and D9090's SASI host processor, 300515
207
+ rev B, 2 KB for $F800-$FFFF.
208
+ SHA-1 59dafbd4855083074ba8dc96a04d4daa5b76e0d6.
209
+
210
+ Both downloaded from
211
+ https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/old/9090/
212
+ The harddrives.zip elsewhere on zimmers.net is not the D9060's DOS: it
213
+ is a 1981 prototype DOS for one processor, driving an 8" floppy and an
214
+ 8" hard disk.
215
+
131
216
  Commodore VIC-20 ROMs
132
217
  =====================
133
218
 
@@ -377,6 +462,66 @@ the German letters and the section sign in place of @ [ \ ] and in
377
462
  some of the graphics. The CRTC's MA13, bit 5 of R12, picks the half.
378
463
  SHA-1 9616f7f18757cccefb702a945f954b644d5b17d1.
379
464
 
465
+ Commodore SuperPET ROMs
466
+ =======================
467
+
468
+ Downloaded from
469
+ https://www.zimmers.net/anonftp/pub/cbm/firmware/computers/pet/SuperPET/
470
+ The Waterloo 6809 firmware, as the two-board SuperPET's three 2764s hold
471
+ it. The three-board SuperPET's 2532s and 2516 hold the same bytes, and
472
+ each file below names them. In 6502 mode the SuperPET runs the 8032's
473
+ ROMs above. Nothing maps these yet.
474
+
475
+ pet/superpet-a000.rom
476
+ ---------------------
477
+ The Waterloo ROM for $A000-$BFFF, 970018-12
478
+ (waterloo-a000-bfff.970018-12.bin): 901898-01
479
+ (waterloo-a000.901898-01.bin, SHA-1
480
+ 0414b3ab847c8977eb05c2fcc72efcf2f9d92871) followed by 901898-02
481
+ (waterloo-b000.901898-02.bin, SHA-1
482
+ df154939b934d0aeeb376813ec1ba0d43c2a3378).
483
+ SHA-1 dcd18397dd2a691118aaacf2b087d08e3782f81a.
484
+
485
+ pet/superpet-c000.rom
486
+ ---------------------
487
+ The Waterloo ROM for $C000-$DFFF, 970019-12
488
+ (waterloo-c000-dfff.970019-12.bin): 901898-03
489
+ (waterloo-c000.901898-03.bin, SHA-1
490
+ 6c5b0cce97068f8841112ba6d5cd8e568b562fa3) followed by 901898-04
491
+ (waterloo-d000.901898-04.bin, SHA-1
492
+ b42a2050a319a1ffca7868a8d8d635fadd37ec37).
493
+ SHA-1 3ac4b9ae895c45630d10cfc9079f5dcbce5eb48e.
494
+
495
+ pet/superpet-e000.rom
496
+ ---------------------
497
+ The Waterloo ROM for $E000-$FFFF, 970020-12
498
+ (waterloo-e000-ffff.970020-12.bin), with the 60 Hz CRTC table: 901897-01
499
+ (waterloo-e000.901897-01.bin, SHA-1
500
+ e8ce8347451a001214a5e71a13081b38b4be23bc) for $E000-$E7FF, 2K of $FF
501
+ where the I/O page sits, and 901898-05 (waterloo-f000.901898-05.bin,
502
+ SHA-1 9b4a5134d20345171e7303445f87c4e0b9addc96) for $F000-$FFFF, which
503
+ ends in the 6809's vectors.
504
+ SHA-1 618992fe0046b670695f5b1d1c168ace9e6b8470.
505
+
506
+ pet/superpet-e000-50hz.rom
507
+ --------------------------
508
+ The 50 Hz Waterloo ROM for $E000-$FFFF, 970034-12
509
+ (waterloo-e000-ffff-970034-12.bin, from Christian Forstreuter's MMF
510
+ 9000, U49). Its $E000-$E7FF is the 50 Hz 901897-01
511
+ (waterloo-e000.901897-x.50Hz.bin, SHA-1
512
+ 57f8c3ef305768d9cba9a96d3f7b90d4a3c2daa4), which differs from the 60 Hz
513
+ one in three bytes of the CRTC table, $E0C2, $E0C3 and $E0C5, and the
514
+ rest is superpet-e000.rom's.
515
+ SHA-1 ef6c5e5e9be17b87faed43cf0007f98731d78384.
516
+
517
+ pet/characters-superpet.rom
518
+ ---------------------------
519
+ The SuperPET's 4K character generator, 901640-01
520
+ (characters.901640-01.bin). Its first half is 901447-10 byte for byte,
521
+ and its second holds an ASCII and an APL set. The CRTC's MA13, bit 5 of
522
+ R12, picks the half.
523
+ SHA-1 bf346f11595a3e65e55d6aeeaa2c0cec807b66c7.
524
+
380
525
  Commodore CBM-II ROMs
381
526
  =====================
382
527
 
@@ -459,3 +604,34 @@ p500-basic-hi.901235-02.bin, SHA-1
459
604
  1b9a55f12f8cf025754d8029cc5324b474c35841) concatenated, for
460
605
  $8000-$BFFF in segment 15.
461
606
  SHA-1 e69531d45f5993f8e53c6bd9ab2a7bc10fe2d02f.
607
+
608
+ MOS KIM-1 ROMs
609
+ ==============
610
+
611
+ Downloaded from
612
+ https://www.zimmers.net/anonftp/pub/cbm/firmware/computers/kim1/
613
+ The files there are the monitor listing assembled with $FF in the
614
+ unused bytes. Real 6530-002 and 6530-003 chips have $00 there, as Hans
615
+ Otten found dumping his own
616
+ (http://retro.hansotten.nl/6502-sbc/kim-1-manuals-and-software/kim_1-roms/),
617
+ so badline zeroes those bytes and keeps the code as it is. Only a
618
+ program that reads unused ROM sees the difference.
619
+
620
+ kim1/6530-002.rom
621
+ -----------------
622
+ The mask ROM of the 6530-002, U2: the monitor, for $1C00-$1FFF, its
623
+ keypad and display routines, the teletype's and the 6502's vectors.
624
+ 6530-002.bin from zimmers.net, SHA-1
625
+ 054f7f6989af3a59462ffb0372b6f56f307b5362, with the three bytes at
626
+ $1FF7-$1FF9 zeroed. The result is Otten's dump of a real chip,
627
+ 6530-002 fillerbyte00.bin.
628
+ SHA-1 d0b5ceb279303cad219d6609d7db03a120f59ab8.
629
+
630
+ kim1/6530-003.rom
631
+ -----------------
632
+ The mask ROM of the 6530-003, U3: the cassette routines, for
633
+ $1800-$1BFF. 6530-003.bin from zimmers.net, SHA-1
634
+ 60b6e48f35fe4899e29166641bac3e81e3b9d220, with the 356 bytes at
635
+ $1A96-$1BF9 zeroed. The result is Otten's dump of a real chip,
636
+ 6530-003 fillerbyte00.bin.
637
+ SHA-1 16caaf9d4c8327e0d3198887499fe30056df3084.
Binary file
Binary file
@@ -42,10 +42,13 @@ module Badline
42
42
  CBM2 = "CBM-II"
43
43
  # xcbm5x0 names its machine CBM-II-5x0, the P500.
44
44
  CBM2_5X0 = "CBM-II-5x0"
45
+ # VICE has no KIM-1, so badline names it.
46
+ KIM1 = "KIM1"
45
47
  # The file format version each machine's VICE emulator reads, where
46
- # it isn't FORMAT_MAJOR.0: xpet and xcbm2 refuse any other than 1.0, and
47
- # xcbm5x0 writes 0.0.
48
- FORMAT_MAJORS = { PET => 1, CBM2 => 1, CBM2_5X0 => 0 }.freeze
48
+ # it isn't FORMAT_MAJOR.0: xpet and xcbm2 refuse any other than 1.0,
49
+ # xcbm5x0 writes 0.0, and xvic refuses any other than 3.1.
50
+ FORMAT_MAJORS = { PET => 1, CBM2 => 1, CBM2_5X0 => 0, VIC20 => 3 }.freeze
51
+ FORMAT_MINORS = { VIC20 => 1 }.freeze
49
52
  GZIP_MAGIC = "\x1f\x8b".b
50
53
 
51
54
  # The VICE version block, when VICE wrote the file: four version
@@ -75,7 +78,8 @@ module Badline
75
78
  def [](name) = @sections.find { |section| section.name == name }
76
79
 
77
80
  def to_s
78
- header = MAGIC + [FORMAT_MAJORS.fetch(@machine, FORMAT_MAJOR), FORMAT_MINOR].pack("CC") + padded(@machine)
81
+ version = [FORMAT_MAJORS.fetch(@machine, FORMAT_MAJOR), FORMAT_MINORS.fetch(@machine, FORMAT_MINOR)]
82
+ header = MAGIC + version.pack("CC") + padded(@machine)
79
83
  header += VERSION_MAGIC + @vice_version if @vice_version
80
84
  header + @sections.map { |section| encode(section) }.join
81
85
  end
@@ -8,7 +8,7 @@ module Badline
8
8
  # voice1 to voice3, on one row.
9
9
  class Markdown
10
10
  NAMES = { "c64" => "C64", "vic20" => "VIC-20", "c128" => "C128", "plus4" => "Plus/4", "pet" => "PET",
11
- "cbm2" => "CBM-II" }.freeze
11
+ "cbm2" => "CBM-II", "kim1" => "KIM-1" }.freeze
12
12
  TAG_TYPES = { Keyed::INT => "int", Keyed::BOOL => "bool", Keyed::BYTES => "bytes", Keyed::INTS => "ints",
13
13
  Keyed::TEXT => "text" }.freeze
14
14
 
@@ -35,18 +35,21 @@ module Badline
35
35
  CARTRIDGES = [32, 5, 21, 18, 1, 53].freeze
36
36
 
37
37
  # The machines the doc is made from: each family at power-on, the C64
38
- # with each kind of expansion, cartridge and media, and a second SID,
39
- # the 264 series' models, its TCBM interface, cartridge, tape and
40
- # true drives, the 1541 and the 1551, a CRTC PET, a 2001, an 8096,
38
+ # with each kind of expansion, cartridge and media, a second SID and
39
+ # its sound recording, the 264 series' models, its TCBM interface,
40
+ # cartridge, tape and true drives, the 1541 and the 1551, a CRTC PET,
41
+ # a 2001, an 8096,
41
42
  # an 8296 with its user port jumpered, a PET with a disk and tapes and
42
- # one with a true 2031, one with an 8050 and one with an 8250, and a
43
- # 610, a 720, a 610 with a cartridge and one with a disk, and a PAL
44
- # and an NTSC 510.
43
+ # one with a true 2031, one with an 8050, one with an 8250, one with
44
+ # a 4040 and one with a D9060, a SuperPET in 6809 mode with a disk in
45
+ # device 8, and a 610, a 720, a 610 with a cartridge and one with a
46
+ # disk, a PAL and an NTSC 510, and a KIM-1 on its keypad and one
47
+ # expanded, on a teletype.
45
48
  def self.machines
46
49
  {
47
50
  "c64" => [C64.new, C64.new(ram_expansion: :plus256k), C64.new(reu: 512),
48
51
  *CARTRIDGES.map { |type| with_cartridge(type) }, with_trap_dos, with_tape, with_drive(:d64),
49
- with_drive(:d81), with_extra_sids],
52
+ with_drive(:d81), with_extra_sids, with_recording],
50
53
  "vic20" => [Vic20.new, with_rom],
51
54
  "c128" => [C128.new(mode: :c128), with_drive(:d71)],
52
55
  "plus4" => [Plus4.new, Plus4.new(model: "c16"), Plus4.new(tcbm: true), Plus4.new(sid_card: :mos6581),
@@ -55,9 +58,11 @@ module Badline
55
58
  plus4_with_cartridge, plus4_with_tape, plus4_with_drive, plus4_with_drive("1551")],
56
59
  "pet" => [PET.new, PET.new(model: "2001"), PET.new(model: "8096"),
57
60
  PET.new(model: "8296", characters: "din", jumpers: %w[userport]), pet_with_media,
58
- pet_with_drive, pet_with_unit("8050"), pet_with_unit("8250")],
61
+ pet_with_drive, pet_with_unit("8050"), pet_with_unit("8250"), pet_with_dos2_unit,
62
+ pet_with_hard_disk, superpet_with_disk],
59
63
  "cbm2" => [CBM2.new, CBM2.new(model: "720", kernal: "03b", mains_hz: 60), cbm2_with_cartridge,
60
- cbm2_with_disk, CBM2::P500.new, CBM2::P500.new(model: "510ntsc")]
64
+ cbm2_with_disk, CBM2::P500.new, CBM2::P500.new(model: "510ntsc")],
65
+ "kim1" => [KIM1.new, KIM1.new(tty: true, ram: :expanded)]
61
66
  }
62
67
  end
63
68
 
@@ -72,6 +77,14 @@ module Badline
72
77
  end
73
78
  end
74
79
 
80
+ # A SuperPET with its CPU switch at 6809 and a disk in device 8
81
+ # through the traps.
82
+ def self.superpet_with_disk
83
+ PET::SuperPET.new(cpu: "6809").tap do |machine|
84
+ machine.mount(Storage::D64Image.new("", read_only: true, bytes: blank(174_848)))
85
+ end
86
+ end
87
+
75
88
  # A PET with a 2031 holding a blank disk.
76
89
  def self.pet_with_drive
77
90
  PET.new.tap do |machine|
@@ -87,9 +100,18 @@ module Badline
87
100
  else
88
101
  Storage::D80Image.new("", bytes: blank(533_248))
89
102
  end
90
- PET.new.tap { |machine| machine.plug_drive8050(model).insert(Drives::Model8050::Disk.new(image)) }
103
+ PET.new.tap { |machine| machine.plug_ieee_drive(model).insert(Drives::Model8050::Disk.new(image)) }
91
104
  end
92
105
 
106
+ # A PET with a 4040 holding a blank disk in drive 0.
107
+ def self.pet_with_dos2_unit
108
+ image = Storage::D64Image.new("", bytes: blank(174_848))
109
+ PET.new.tap { |machine| machine.plug_ieee_drive("4040").insert(Drives::Model4040::Disk.new(image)) }
110
+ end
111
+
112
+ # A PET with a D9060 and no hard disk in it.
113
+ def self.pet_with_hard_disk = PET.new.tap { |machine| machine.plug_ieee_drive("9060") }
114
+
93
115
  def self.blank(length) = Array.new(length, 0)
94
116
 
95
117
  # A 610 with a disk in device 8 through the traps.
@@ -118,6 +140,9 @@ module Badline
118
140
  C64.new.tap { |machine| machine.address_bus.add_sid(Chips::SID.new(at: 0xd420)) }
119
141
  end
120
142
 
143
+ # A C64 recording its SID's output, as the window does with sound on.
144
+ def self.with_recording = C64.new.tap { |machine| machine.sound_source.record(rate: 44_100) }
145
+
121
146
  def self.with_trap_dos
122
147
  C64.new.tap { |machine| machine.mount(Storage::D64Image.new("", read_only: true, bytes: blank(174_848))) }
123
148
  end
@@ -35,13 +35,12 @@ module Badline
35
35
  class Image
36
36
  MACHINE = "BL-MACHINE"
37
37
  FAMILIES = { "c64" => "C64", "vic20" => "VIC-20", "c128" => "C128", "plus4" => "Plus/4", "pet" => "PET",
38
- "cbm2" => "CBM-II" }.freeze
38
+ "cbm2" => "CBM-II", "kim1" => "KIM-1" }.freeze
39
39
 
40
40
  # The snapshots VICE's emulators write that badline doesn't read, by
41
41
  # the machine their container names.
42
42
  UNREAD = {
43
- Container::VIC20 => "VIC-20 snapshots, not xvic's", Container::C128 => "C128 snapshots, not x128's",
44
- Container::PLUS4 => "Plus/4 snapshots, not xplus4's"
43
+ Container::C128 => "C128 snapshots, not x128's", Container::PLUS4 => "Plus/4 snapshots, not xplus4's"
45
44
  }.freeze
46
45
 
47
46
  attr_reader :container
@@ -62,16 +61,18 @@ module Badline
62
61
  end
63
62
 
64
63
  # The snapshot with VICE's modules ahead of the BL-* ones for a C64,
65
- # a PET, a CBM-II's B series or a P500, so x64sc, xpet, xcbm2 or
66
- # xcbm5x0 opens it too. Raises FormatError for a Plus/4, whose
64
+ # a VIC-20, a PET, a CBM-II's B series or a P500, so x64sc, xvic,
65
+ # xpet, xcbm2 or xcbm5x0 opens it too. Raises FormatError for a Plus/4, whose
67
66
  # snapshots badline doesn't write for xplus4.
68
67
  def self.exported(computer)
69
68
  raise FormatError, "badline doesn't export a Plus/4's snapshot for VICE" if computer.family == :plus4
69
+ raise FormatError, "VICE has no KIM-1 to open a snapshot of one" if computer.family == :kim1
70
70
 
71
71
  computer.settle_for_vice!
72
72
  sections = computer.save_modules
73
73
  case computer.family
74
74
  when :c64 then sections = Vice.export(computer) + sections
75
+ when :vic20 then sections = Vice::Vic20.export(computer, sections) + sections
75
76
  when :pet then sections = Vice::PET.export(computer, sections) + sections
76
77
  when :cbm2 then sections = cbm2_export(computer, sections) + sections
77
78
  end
@@ -86,6 +87,7 @@ module Badline
86
87
  # The VICE emulator a machine's VICE modules are for.
87
88
  def self.vice_emulator(computer)
88
89
  case machine_name(computer)
90
+ when Container::VIC20 then "xvic"
89
91
  when Container::PET then "xpet"
90
92
  when Container::CBM2 then "xcbm2"
91
93
  when Container::CBM2_5X0 then "xcbm5x0"
@@ -101,6 +103,7 @@ module Badline
101
103
  when :plus4 then Container::PLUS4
102
104
  when :pet then Container::PET
103
105
  when :cbm2 then computer.is_a?(CBM2::P500) ? Container::CBM2_5X0 : Container::CBM2
106
+ when :kim1 then Container::KIM1
104
107
  else Container::MACHINE
105
108
  end
106
109
  end
@@ -114,6 +117,7 @@ module Badline
114
117
  when "plus4" then Plus4Setup.read_fields(machine)
115
118
  when "pet" then PETSetup.read_fields(machine)
116
119
  when "cbm2" then CBM2Setup.read_fields(machine)
120
+ when "kim1" then KIM1Setup.read_fields(machine)
117
121
  else C64Setup.read_fields(machine)
118
122
  end
119
123
  end
@@ -129,13 +133,14 @@ module Badline
129
133
 
130
134
  # A new machine, built as the snapshot's was and restored from it. A
131
135
  # badline snapshot restores from its BL-* modules alone, and a VICE
132
- # one through the modules badline reads, x64sc's, xpet's, xcbm2's or xcbm5x0's. xvic's,
133
- # x128's and xplus4's own modules aren't read. Yields a line for
136
+ # one through the modules badline reads, x64sc's, xvic's, xpet's,
137
+ # xcbm2's or xcbm5x0's. x128's and xplus4's own modules aren't read. Yields a line for
134
138
  # each thing left out, or warns it without a block. `detached`
135
139
  # restores it without the host's files (Keyed::Modules).
136
140
  def load(detached: false, &)
137
141
  refuse_old_layout
138
142
  refuse_unread
143
+ return load_xvic(&) if vic20? && !badline?
139
144
  return load_xpet(&) if pet? && !badline?
140
145
  return load_xcbm2(&) if cbm2? && !badline?
141
146
  return load_xcbm5x0(&) if p500? && !badline?
@@ -172,6 +177,7 @@ module Badline
172
177
  def restore(computer, &)
173
178
  refuse_old_layout
174
179
  report = if badline? then restore_modules(computer)
180
+ elsif vic20? then Vice::Vic20::Restore.import(@container, computer)
175
181
  elsif pet? then Vice::PET::Restore.import(@container, computer)
176
182
  elsif cbm2? then Vice::CBM2::Restore.import(@container, computer)
177
183
  elsif p500? then Vice::P500::Restore.import(@container, computer)
@@ -189,6 +195,12 @@ module Badline
189
195
  computer
190
196
  end
191
197
 
198
+ def load_xvic(&)
199
+ computer = Vice::Vic20.setup(@container).build
200
+ tell(Vice::Vic20::Restore.apply(@container, computer), &)
201
+ computer
202
+ end
203
+
192
204
  def load_xpet(&)
193
205
  computer = Vice::PET.setup(@container).build
194
206
  tell(Vice::PET::Restore.apply(@container, computer), &)
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ # How a KIM-1 was built: its RAM, one of KIM1::Bus::RAM_CONFIGURATIONS
6
+ # by name, the argument KIM1.new takes. The ROMs are the board's own.
7
+ KIM1Setup = Data.define(:ram)
8
+
9
+ class KIM1Setup
10
+ RAM = KIM1::Bus::RAM_CONFIGURATIONS.map(&:to_s).freeze
11
+
12
+ # The setup of `machine`.
13
+ def self.of(machine) = new(ram: machine.bus.ram_configuration.to_s)
14
+
15
+ # The RAM by name, in a keyed snapshot module.
16
+ def write_fields(out)
17
+ out.text("ram", ram)
18
+ end
19
+
20
+ def self.read_fields(input) = new(ram: input.choice("ram", RAM, "1k"))
21
+
22
+ # A machine built this way, at power-on.
23
+ def build = KIM1.new(ram: ram.to_sym)
24
+
25
+ def to_s = "KIM-1 with #{ram == 'expanded' ? 'expanded' : ram.upcase} RAM"
26
+ end
27
+ end
28
+ end
@@ -5,9 +5,11 @@ module Badline
5
5
  # How a PET was built: its model's name, one of PET::Model::ALL, its
6
6
  # RAM below $8000 in K and its editor's frame rate, the arguments
7
7
  # PET.new takes, the ROMs it runs by file: BASIC, the editor, the
8
- # KERNAL and the characters, and the 8296 board's jumpers, by name and
9
- # comma-separated.
10
- PETSetup = Data.define(:model, :ram_kb, :refresh_hz, :basic, :editor, :kernal, :characters, :jumpers)
8
+ # KERNAL and the characters, the 8296 board's jumpers, by name and
9
+ # comma-separated, and the SuperPET's CPU and RAM switches, as
10
+ # PET::SuperPET::Board names their positions, empty on the others.
11
+ PETSetup = Data.define(:model, :ram_kb, :refresh_hz, :basic, :editor, :kernal, :characters, :jumpers,
12
+ :cpu_switch, :ram_switch)
11
13
 
12
14
  class PETSetup
13
15
  MODELS = PET::Model::ALL.map(&:name).freeze
@@ -20,20 +22,24 @@ module Badline
20
22
  refresh_hz = machine.refresh_hz
21
23
  new(model: model.name, ram_kb: machine.ram.length >> 10, refresh_hz:, basic: model.basic,
22
24
  editor: model.editor(refresh_hz), kernal: model.kernal, characters: model.characters,
23
- jumpers: machine.jumpers.join(","))
25
+ jumpers: machine.jumpers.join(","), cpu_switch: machine.superpet? ? machine.board.cpu_switch : "",
26
+ ram_switch: machine.superpet? ? machine.board.ram_switch : "")
24
27
  end
25
28
 
26
- # The model by name, the RAM, the frame rate, the ROMs and the
27
- # jumpers where a board has any, in a keyed snapshot module.
29
+ # The model by name, the RAM, the frame rate, the ROMs, the jumpers
30
+ # where a board has any and the switches where it has them, in a
31
+ # keyed snapshot module.
28
32
  def write_fields(out)
29
33
  out.text("model", model).int("ram_kb", ram_kb).int("refresh_hz", refresh_hz)
30
34
  out.text("rom.basic", basic).text("rom.editor", editor).text("rom.kernal", kernal)
31
35
  out.text("rom.characters", characters)
32
36
  out.text("jumpers", jumpers) unless jumpers.empty?
37
+ out.text("cpu_switch", cpu_switch).text("ram_switch", ram_switch) unless cpu_switch.empty?
33
38
  end
34
39
 
35
- # A ROM the snapshot doesn't name is the model's own, and a board
36
- # without jumpers named has none fitted.
40
+ # A ROM the snapshot doesn't name is the model's own, a board without
41
+ # jumpers named has none fitted, and a SuperPET without its switches
42
+ # named has them at 6502 and R/W.
37
43
  def self.read_fields(input)
38
44
  profile = PET::Model.named(input.choice("model", MODELS, PET::Model::DEFAULT))
39
45
  ram_kb = input.int("ram_kb", profile.ram_kb)
@@ -47,18 +53,27 @@ module Badline
47
53
  end
48
54
 
49
55
  refresh_hz = 60 unless profile.crtc?
56
+ superpet = profile.superpet?
50
57
  new(model: profile.name, ram_kb:, refresh_hz:, basic: input.text("rom.basic", profile.basic),
51
58
  editor: input.text("rom.editor", profile.editor(refresh_hz)),
52
59
  kernal: input.text("rom.kernal", profile.kernal),
53
- characters: input.text("rom.characters", profile.characters), jumpers: input.text("jumpers", ""))
60
+ characters: input.text("rom.characters", profile.characters), jumpers: input.text("jumpers", ""),
61
+ cpu_switch: superpet ? input.choice("cpu_switch", PET::SuperPET::Board::CPU_SWITCH, "6502") : "",
62
+ ram_switch: superpet ? input.choice("ram_switch", PET::SuperPET::Board::RAM_SWITCH, "rw") : "")
54
63
  end
55
64
 
56
65
  # A machine built this way, at power-on, with the BASIC, editor and
57
66
  # character ROMs PET::ROMSets names for its ROMs.
58
67
  def build
59
68
  version = PET::ROMSets::BASICS.find { |_, roms| roms.first == basic }&.first
60
- PET.new(model:, ram_kb:, refresh_hz:, basic: version, editor: PET::ROMSets::EDITORS.key(editor),
61
- characters: fitted_characters, jumpers: jumpers.split(","))
69
+ editor_name = PET::ROMSets::EDITORS.key(editor)
70
+ unless cpu_switch.empty?
71
+ return PET::SuperPET.new(refresh_hz:, basic: version, editor: editor_name, cpu: cpu_switch,
72
+ bank_ram: ram_switch)
73
+ end
74
+
75
+ PET.new(model:, ram_kb:, refresh_hz:, basic: version, editor: editor_name, characters: fitted_characters,
76
+ jumpers: jumpers.split(","))
62
77
  rescue ArgumentError => e
63
78
  raise FormatError, "the snapshot is of a #{self}, which badline doesn't build: #{e.message}"
64
79
  end
@@ -68,6 +83,7 @@ module Badline
68
83
  def to_s
69
84
  text = "#{model} with #{ram_kb}K at #{refresh_hz} Hz"
70
85
  text = "#{text} jumpered #{jumpers}" unless jumpers.empty?
86
+ text = "#{text} with its CPU switch at #{cpu_switch} and RAM switch at #{ram_switch}" unless cpu_switch.empty?
71
87
  roms = [basic, editor, kernal, characters]
72
88
  profile = PET::Model.named(model)
73
89
  stock = [profile.basic, profile.editor(refresh_hz), profile.kernal, profile.characters]
@@ -13,6 +13,7 @@ module Badline
13
13
  T64_ARCHIVE = 4
14
14
  D80 = 5
15
15
  D82 = 6
16
+ D67 = 7
16
17
 
17
18
  module_function
18
19
 
@@ -22,6 +23,7 @@ module Badline
22
23
  return D81 if image.is_a?(Storage::D81Image)
23
24
  return D82 if image.is_a?(Storage::D82Image)
24
25
  return D80 if image.is_a?(Storage::D80Image)
26
+ return D67 if image.is_a?(Storage::D67Image)
25
27
 
26
28
  raise ArgumentError, "#{image.class} can't go in a snapshot"
27
29
  end
@@ -41,7 +43,7 @@ module Badline
41
43
  end
42
44
  end
43
45
 
44
- KIND_NAMES = %w[directory d64 d71 d81 t64 d80 d82].freeze
46
+ KIND_NAMES = %w[directory d64 d71 d81 t64 d80 d82 d67].freeze
45
47
 
46
48
  # Opens the storage write_fields wrote: the same directory by its
47
49
  # path, an archive with the bytes it held, or a disk image with the
@@ -61,6 +63,7 @@ module Badline
61
63
  when D71 then Storage::D71Image.new(path, read_only:, bytes:)
62
64
  when D80 then Storage::D80Image.new(path, read_only:, bytes:)
63
65
  when D82 then Storage::D82Image.new(path, read_only:, bytes:)
66
+ when D67 then Storage::D67Image.new(path, read_only:, bytes:)
64
67
  else Storage::D81Image.new(path, read_only:, bytes:)
65
68
  end
66
69
  end
@@ -3,26 +3,30 @@
3
3
  module Badline
4
4
  module Snapshot
5
5
  # How a VIC-20 was built: its RAM expansion, a key of
6
- # Vic20::Bus::RAM_CONFIGURATIONS, the argument Vic20.new takes.
7
- Vic20Setup = Data.define(:ram)
6
+ # Vic20::Bus::RAM_CONFIGURATIONS, and its model, one of Vic20::MODELS,
7
+ # the arguments Vic20.new takes.
8
+ Vic20Setup = Data.define(:ram, :model)
8
9
 
9
10
  class Vic20Setup
10
11
  RAM_CONFIGURATIONS = Vic20::Bus::RAM_CONFIGURATIONS.keys.freeze
11
12
 
12
- # The RAM expansion by name, in a keyed snapshot module.
13
+ # The RAM expansion and the model by name, in a keyed snapshot module.
14
+ # A snapshot without a model is of a PAL machine, the only one badline
15
+ # built before the NTSC one.
13
16
  def write_fields(out)
14
- out.text("ram", ram.to_s)
17
+ out.text("ram", ram.to_s).text("model", model)
15
18
  end
16
19
 
17
20
  def self.read_fields(input)
18
21
  text = input.choice("ram", RAM_CONFIGURATIONS.map(&:to_s), "unexpanded")
19
- new(ram: RAM_CONFIGURATIONS.find { |name| name.to_s == text } || :unexpanded)
22
+ new(ram: RAM_CONFIGURATIONS.find { |name| name.to_s == text } || :unexpanded,
23
+ model: input.choice("model", Vic20::MODELS, "pal"))
20
24
  end
21
25
 
22
26
  # A machine built this way, at power-on.
23
- def build = Vic20.new(ram:)
27
+ def build = Vic20.new(ram:, model:)
24
28
 
25
- def to_s = ram.to_s
29
+ def to_s = model == "pal" ? ram.to_s : "#{ram} #{model}"
26
30
  end
27
31
  end
28
32
  end