badline 0.16.1 → 0.18.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 (256) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +213 -0
  3. data/CLAUDE.md +11 -7
  4. data/CONTRIBUTING.md +7 -1
  5. data/README.md +265 -26
  6. data/doc/media.md +5 -2
  7. data/doc/pinned-behaviour.md +189 -0
  8. data/doc/snapshot-format.md +616 -74
  9. data/doc/snapshots.md +192 -24
  10. data/exe/badline-ruby +5 -0
  11. data/lib/badline/audio/terminal.rb +34 -2
  12. data/lib/badline/c128/keyed_state.rb +2 -0
  13. data/lib/badline/c128.rb +1 -0
  14. data/lib/badline/c64/keyed_state.rb +2 -0
  15. data/lib/badline/c64.rb +1 -0
  16. data/lib/badline/cbm2/acia.rb +10 -0
  17. data/lib/badline/cbm2/bus.rb +263 -0
  18. data/lib/badline/cbm2/cpu.rb +58 -0
  19. data/lib/badline/cbm2/crtc_video.rb +133 -0
  20. data/lib/badline/cbm2/ieee_drive.rb +18 -0
  21. data/lib/badline/cbm2/ieee_layout.rb +69 -0
  22. data/lib/badline/cbm2/ieee_ports.rb +104 -0
  23. data/lib/badline/cbm2/ieee_traps.rb +81 -0
  24. data/lib/badline/cbm2/keyboard_ports.rb +60 -0
  25. data/lib/badline/cbm2/keyed_modules.rb +67 -0
  26. data/lib/badline/cbm2/keyed_state.rb +95 -0
  27. data/lib/badline/cbm2/model.rb +44 -0
  28. data/lib/badline/cbm2/p500/control_ports.rb +80 -0
  29. data/lib/badline/cbm2/p500/ieee_layout.rb +41 -0
  30. data/lib/badline/cbm2/p500/keyed_state.rb +85 -0
  31. data/lib/badline/cbm2/p500/model.rb +39 -0
  32. data/lib/badline/cbm2/p500/pages.rb +57 -0
  33. data/lib/badline/cbm2/p500.rb +264 -0
  34. data/lib/badline/cbm2/sid_page.rb +19 -0
  35. data/lib/badline/cbm2.rb +270 -0
  36. data/lib/badline/{plus4 → chips}/acia/frames.rb +1 -1
  37. data/lib/badline/chips/acia/keyed_state.rb +47 -0
  38. data/lib/badline/chips/acia.rb +307 -0
  39. data/lib/badline/chips/cia/timer.rb +2 -2
  40. data/lib/badline/chips/cpu/instruction_set.rb +1 -1
  41. data/lib/badline/chips/crtc.rb +3 -2
  42. data/lib/badline/chips/m6809/arithmetic.rb +140 -0
  43. data/lib/badline/chips/m6809/bus_steps.rb +223 -0
  44. data/lib/badline/chips/m6809/core.rb +149 -0
  45. data/lib/badline/chips/m6809/indexed.rb +82 -0
  46. data/lib/badline/chips/m6809/interrupts.rb +159 -0
  47. data/lib/badline/chips/m6809/keyed_state.rb +147 -0
  48. data/lib/badline/chips/m6809/opcodes.rb +159 -0
  49. data/lib/badline/chips/m6809/operations.rb +258 -0
  50. data/lib/badline/chips/m6809/plans.rb +143 -0
  51. data/lib/badline/chips/m6809/registers.rb +82 -0
  52. data/lib/badline/chips/m6809/stack.rb +140 -0
  53. data/lib/badline/chips/m6809.rb +13 -0
  54. data/lib/badline/chips/riot/fast_forward.rb +72 -0
  55. data/lib/badline/chips/riot/keyed_parts.rb +69 -0
  56. data/lib/badline/chips/riot/timer.rb +91 -0
  57. data/lib/badline/chips/riot.rb +162 -0
  58. data/lib/badline/chips/rriot/keyed_state.rb +30 -0
  59. data/lib/badline/chips/rriot.rb +134 -0
  60. data/lib/badline/chips/sid/filter.rb +4 -4
  61. data/lib/badline/chips/sid/waveform.rb +6 -6
  62. data/lib/badline/chips/tpi/keyed_state.rb +38 -0
  63. data/lib/badline/chips/tpi.rb +233 -0
  64. data/lib/badline/chips/vic/bank.rb +60 -5
  65. data/lib/badline/chips/vic/sprite.rb +4 -3
  66. data/lib/badline/chips/vic_i/keyed_parts.rb +7 -2
  67. data/lib/badline/chips/vic_i.rb +84 -21
  68. data/lib/badline/data_path.rb +3 -3
  69. data/lib/badline/drives/datasette/keyed_state.rb +9 -1
  70. data/lib/badline/drives/datasette.rb +27 -3
  71. data/lib/badline/drives/ieee_models.rb +54 -0
  72. data/lib/badline/drives/model1001.rb +37 -0
  73. data/lib/badline/drives/model2031.rb +6 -0
  74. data/lib/badline/drives/model4040/disk.rb +62 -0
  75. data/lib/badline/drives/model4040/ip_bus.rb +46 -0
  76. data/lib/badline/drives/model4040/keyed_state.rb +57 -0
  77. data/lib/badline/drives/model4040/mechanism.rb +192 -0
  78. data/lib/badline/drives/model4040/parts.rb +20 -0
  79. data/lib/badline/drives/model4040.rb +179 -0
  80. data/lib/badline/drives/model8050/board.rb +77 -0
  81. data/lib/badline/drives/model8050/disk.rb +163 -0
  82. data/lib/badline/drives/model8050/dos_board.rb +91 -0
  83. data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
  84. data/lib/badline/drives/model8050/idle.rb +181 -0
  85. data/lib/badline/drives/model8050/ieee_port.rb +147 -0
  86. data/lib/badline/drives/model8050/ip_bus.rb +93 -0
  87. data/lib/badline/drives/model8050/keyed_state.rb +163 -0
  88. data/lib/badline/drives/model8050/mechanism.rb +230 -0
  89. data/lib/badline/drives/model8050/parts.rb +37 -0
  90. data/lib/badline/drives/model8050.rb +131 -0
  91. data/lib/badline/drives/model9060/idle.rb +149 -0
  92. data/lib/badline/drives/model9060/keyed_state.rb +123 -0
  93. data/lib/badline/drives/model9060/parts.rb +98 -0
  94. data/lib/badline/drives/model9060/sasi_bus.rb +88 -0
  95. data/lib/badline/drives/model9060/sasi_commands.rb +168 -0
  96. data/lib/badline/drives/model9060/sasi_target.rb +161 -0
  97. data/lib/badline/drives/model9060.rb +195 -0
  98. data/lib/badline/frontend/app.rb +18 -3
  99. data/lib/badline/frontend/boot.rb +4 -3
  100. data/lib/badline/frontend/controls.rb +107 -15
  101. data/lib/badline/frontend/gamepads.rb +3 -3
  102. data/lib/badline/frontend/keys.rb +97 -2
  103. data/lib/badline/frontend/kim1_panel.rb +208 -0
  104. data/lib/badline/frontend/media_slots.rb +8 -1
  105. data/lib/badline/frontend/menu/drive_types.rb +56 -0
  106. data/lib/badline/frontend/menu/menu_dialogs.rb +61 -14
  107. data/lib/badline/frontend/menu/menu_media.rb +47 -14
  108. data/lib/badline/frontend/menu/menu_pages.rb +113 -4
  109. data/lib/badline/frontend/menu/menu_ports.rb +77 -4
  110. data/lib/badline/frontend/menu/pause_menu.rb +6 -2
  111. data/lib/badline/frontend/pet_keyboard.rb +45 -7
  112. data/lib/badline/frontend/screen.rb +1 -1
  113. data/lib/badline/frontend/screens.rb +40 -2
  114. data/lib/badline/frontend/teletype_pane.rb +152 -0
  115. data/lib/badline/frontend/timeline.rb +49 -11
  116. data/lib/badline/frontend.rb +3 -0
  117. data/lib/badline/kernal_trap/dos/disk_commands.rb +80 -0
  118. data/lib/badline/kernal_trap/dos/status.rb +3 -0
  119. data/lib/badline/kernal_trap/dos.rb +18 -5
  120. data/lib/badline/kernal_trap/ieee.rb +4 -2
  121. data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
  122. data/lib/badline/kim1/bus.rb +117 -0
  123. data/lib/badline/kim1/cpu.rb +25 -0
  124. data/lib/badline/kim1/host_terminal.rb +74 -0
  125. data/lib/badline/kim1/key_entry.rb +74 -0
  126. data/lib/badline/kim1/keyed_modules.rb +53 -0
  127. data/lib/badline/kim1/keyed_state.rb +133 -0
  128. data/lib/badline/kim1/panel.rb +176 -0
  129. data/lib/badline/kim1/rriots.rb +28 -0
  130. data/lib/badline/kim1/sound.rb +29 -0
  131. data/lib/badline/kim1/tape.rb +116 -0
  132. data/lib/badline/kim1/teletype.rb +193 -0
  133. data/lib/badline/kim1.rb +190 -0
  134. data/lib/badline/machine/ieee_drives.rb +171 -0
  135. data/lib/badline/machine/keyboard_buffer.rb +20 -3
  136. data/lib/badline/machine/line_sound.rb +122 -0
  137. data/lib/badline/machine/settling.rb +4 -0
  138. data/lib/badline/machine.rb +62 -14
  139. data/lib/badline/media/cbm2_media.rb +29 -0
  140. data/lib/badline/media/extensions.rb +8 -4
  141. data/lib/badline/media/kim1_media.rb +150 -0
  142. data/lib/badline/media/pet_media.rb +3 -1
  143. data/lib/badline/media/program.rb +6 -3
  144. data/lib/badline/media/true_drive.rb +51 -25
  145. data/lib/badline/media.rb +23 -8
  146. data/lib/badline/options/family.rb +108 -6
  147. data/lib/badline/options/help.rb +58 -12
  148. data/lib/badline/options/table.rb +60 -14
  149. data/lib/badline/options/validation.rb +71 -11
  150. data/lib/badline/options.rb +46 -13
  151. data/lib/badline/pet/bus.rb +16 -2
  152. data/lib/badline/pet/cassettes.rb +22 -1
  153. data/lib/badline/pet/ieee_drive.rb +5 -64
  154. data/lib/badline/pet/ieee_ports.rb +7 -0
  155. data/lib/badline/pet/keyboard_ports.rb +9 -2
  156. data/lib/badline/pet/keyed_state.rb +4 -0
  157. data/lib/badline/pet/model.rb +11 -2
  158. data/lib/badline/pet/rom_sets.rb +1 -0
  159. data/lib/badline/pet/sound.rb +5 -105
  160. data/lib/badline/pet/superpet/acia.rb +12 -0
  161. data/lib/badline/pet/superpet/board.rb +221 -0
  162. data/lib/badline/pet/superpet/dongle.rb +81 -0
  163. data/lib/badline/pet/superpet/keyed_modules.rb +63 -0
  164. data/lib/badline/pet/superpet/keyed_parts.rb +76 -0
  165. data/lib/badline/pet/superpet/m6809.rb +32 -0
  166. data/lib/badline/pet/superpet/waterloo_traps.rb +199 -0
  167. data/lib/badline/pet/superpet.rb +216 -0
  168. data/lib/badline/pet.rb +33 -6
  169. data/lib/badline/plus4/acia.rb +4 -298
  170. data/lib/badline/plus4/keyed_parts.rb +0 -44
  171. data/lib/badline/plus4/keyed_state.rb +2 -0
  172. data/lib/badline/plus4.rb +2 -0
  173. data/lib/badline/roms/1001/fdc-251257-02a.rom +0 -0
  174. data/lib/badline/roms/4040/dos-1.2.rom +0 -0
  175. data/lib/badline/roms/4040/dos-2.1-rev.rom +0 -0
  176. data/lib/badline/roms/4040/dos-2.1.rom +0 -0
  177. data/lib/badline/roms/4040/fdc-901466-02.rom +0 -0
  178. data/lib/badline/roms/4040/fdc-901466-04.rom +0 -0
  179. data/lib/badline/roms/8050/dos-2.7.rom +0 -0
  180. data/lib/badline/roms/8050/fdc-901869-01.rom +0 -0
  181. data/lib/badline/roms/8050/fdc-901885-04.rom +0 -0
  182. data/lib/badline/roms/9060/dos-3.0.rom +0 -0
  183. data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
  184. data/lib/badline/roms/README +277 -0
  185. data/lib/badline/roms/cbm2/basic128-02b.rom +0 -0
  186. data/lib/badline/roms/cbm2/basic128.rom +0 -0
  187. data/lib/badline/roms/cbm2/basic256.rom +0 -0
  188. data/lib/badline/roms/cbm2/characters-600.rom +0 -0
  189. data/lib/badline/roms/cbm2/characters-700.rom +0 -0
  190. data/lib/badline/roms/cbm2/kernal-03b.rom +0 -0
  191. data/lib/badline/roms/cbm2/kernal-04a.rom +0 -0
  192. data/lib/badline/roms/cbm2/p500-basic.rom +0 -0
  193. data/lib/badline/roms/cbm2/p500-kernal.rom +0 -0
  194. data/lib/badline/roms/kim1/6530-002.rom +0 -0
  195. data/lib/badline/roms/kim1/6530-003.rom +0 -0
  196. data/lib/badline/roms/pet/characters-superpet.rom +0 -0
  197. data/lib/badline/roms/pet/superpet-a000.rom +0 -0
  198. data/lib/badline/roms/pet/superpet-c000.rom +0 -0
  199. data/lib/badline/roms/pet/superpet-e000-50hz.rom +0 -0
  200. data/lib/badline/roms/pet/superpet-e000.rom +0 -0
  201. data/lib/badline/snapshot/cbm2_setup.rb +53 -0
  202. data/lib/badline/snapshot/container.rb +9 -2
  203. data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
  204. data/lib/badline/snapshot/field_doc.rb +59 -3
  205. data/lib/badline/snapshot/image.rb +65 -9
  206. data/lib/badline/snapshot/kim1_setup.rb +28 -0
  207. data/lib/badline/snapshot/pet_setup.rb +27 -11
  208. data/lib/badline/snapshot/storage_setup.rb +4 -1
  209. data/lib/badline/snapshot/vic20_setup.rb +11 -7
  210. data/lib/badline/snapshot/vice/cbm2.rb +93 -0
  211. data/lib/badline/snapshot/vice/cbm2_acia.rb +75 -0
  212. data/lib/badline/snapshot/vice/cbm2_crtc.rb +127 -0
  213. data/lib/badline/snapshot/vice/cbm2_mains.rb +49 -0
  214. data/lib/badline/snapshot/vice/cbm2_mem.rb +75 -0
  215. data/lib/badline/snapshot/vice/cbm2_restore.rb +204 -0
  216. data/lib/badline/snapshot/vice/cbm2_tpi.rb +58 -0
  217. data/lib/badline/snapshot/vice/cias.rb +13 -9
  218. data/lib/badline/snapshot/vice/p500.rb +96 -0
  219. data/lib/badline/snapshot/vice/p500_restore.rb +130 -0
  220. data/lib/badline/snapshot/vice/p500_vicii.rb +166 -0
  221. data/lib/badline/snapshot/vice/pet.rb +20 -10
  222. data/lib/badline/snapshot/vice/pet_cpu.rb +42 -5
  223. data/lib/badline/snapshot/vice/pet_crtc.rb +25 -17
  224. data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
  225. data/lib/badline/snapshot/vice/pet_drive8050.rb +336 -0
  226. data/lib/badline/snapshot/vice/pet_mem.rb +49 -5
  227. data/lib/badline/snapshot/vice/pet_restore.rb +100 -7
  228. data/lib/badline/snapshot/vice/pet_via.rb +6 -5
  229. data/lib/badline/snapshot/vice/sid_registers.rb +2 -1
  230. data/lib/badline/snapshot/vice/vicii_import.rb +10 -6
  231. data/lib/badline/snapshot/vice.rb +3 -0
  232. data/lib/badline/snapshot.rb +15 -3
  233. data/lib/badline/status.rb +30 -30
  234. data/lib/badline/storage/crt_file.rb +7 -1
  235. data/lib/badline/storage/d64_image.rb +7 -0
  236. data/lib/badline/storage/d67_image.rb +55 -0
  237. data/lib/badline/storage/d80_image.rb +17 -1
  238. data/lib/badline/storage/d81_image.rb +14 -0
  239. data/lib/badline/storage/d90_image.rb +180 -0
  240. data/lib/badline/storage/disk_image/bam.rb +18 -3
  241. data/lib/badline/storage/disk_image/formatting.rb +69 -0
  242. data/lib/badline/storage/disk_image/writing.rb +37 -2
  243. data/lib/badline/storage/disk_image.rb +2 -0
  244. data/lib/badline/storage/kim_tape.rb +254 -0
  245. data/lib/badline/storage/papertape.rb +90 -0
  246. data/lib/badline/storage.rb +5 -0
  247. data/lib/badline/support/cbm2_checkpoint.rb +48 -0
  248. data/lib/badline/support/kim1_checkpoint.rb +41 -0
  249. data/lib/badline/support/p500_checkpoint.rb +35 -0
  250. data/lib/badline/support/superpet_checkpoint.rb +35 -0
  251. data/lib/badline/version.rb +1 -1
  252. data/lib/badline/vic20/keyed_modules.rb +6 -3
  253. data/lib/badline/vic20/keyed_state.rb +6 -4
  254. data/lib/badline/vic20.rb +26 -12
  255. data/lib/badline.rb +2 -0
  256. metadata +148 -2
@@ -7,17 +7,21 @@ module Badline
7
7
  # It loads on its own, for Options.
8
8
  #
9
9
  # - :disk, a disk image that the traps mount and a true drive reads
10
- # - :ieee_disk, an 8050's or 8250's disk image, for a PET's IEEE-488
11
- # drive
10
+ # - :ieee_disk, an 8050's, 8250's or 2040's disk image, for a PET's
11
+ # IEEE-488 drive
12
+ # - :hard_disk, a D9060's or D9090's hard disk, which only a PET's or a
13
+ # CBM-II's true drive reads
12
14
  # - :gcr, a disk's raw GCR, which only a true drive reads
13
15
  # - :archive, a .t64, which the traps mount read-only
14
16
  # - :tape, :cartridge and :tune
17
+ # - :papertape, a MOS papertape, which only the KIM-1 loads
15
18
  # - :disk_list, a list of the disks of a set (DiskList)
16
19
  module Extensions
17
20
  KINDS = {
18
21
  ".d64" => :disk, ".d71" => :disk, ".d81" => :disk, ".g64" => :gcr, ".g71" => :gcr, ".t64" => :archive,
19
- ".d80" => :ieee_disk, ".d82" => :ieee_disk, ".tap" => :tape, ".crt" => :cartridge, ".sid" => :tune,
20
- ".m3u" => :disk_list, ".vfl" => :disk_list
22
+ ".d80" => :ieee_disk, ".d82" => :ieee_disk, ".d67" => :ieee_disk, ".d90" => :hard_disk, ".tap" => :tape,
23
+ ".crt" => :cartridge, ".sid" => :tune, ".m3u" => :disk_list, ".vfl" => :disk_list, ".ptp" => :papertape,
24
+ ".pap" => :papertape
21
25
  }.freeze
22
26
 
23
27
  # The kind of the medium at `path`, by its extension.
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Media
5
+ # What a KIM-1 loads (Media.attach): a MOS papertape, a .ptp or .pap
6
+ # (Storage::Papertape), at the addresses its records give, or a .bin
7
+ # of bare bytes, which says nothing of where it goes, at its Loading's
8
+ # load address.
9
+ #
10
+ # Once the monitor runs, the bytes go straight into memory, after
11
+ # the NMI and IRQ vectors at $17FA and $17FE, which nothing sets at
12
+ # power-on, are pointed at the monitor's $1C00, so ST and BRK stop
13
+ # the program and show where (User Manual, section 4.1). A papertape
14
+ # can go in through the teletype instead, as the monitor's L command
15
+ # reads it, in real time. The program then starts at its Loading's
16
+ # start address, or else where it loads, as the user would start it:
17
+ # AD, the address and GO on the keypad, or the address, a space and G
18
+ # on the teletype. Without autostart the monitor stops at the
19
+ # address.
20
+ #
21
+ # A .wav, a recording of a KIM tape (Storage::KIMTape), goes in the
22
+ # cassette deck, for the monitor's LOADT to read once PLAY is down.
23
+ module KIM1Media
24
+ PAPERTAPES = Extensions.of(%i[papertape])
25
+
26
+ # A .bin, bare bytes.
27
+ BINARY = ".bin"
28
+
29
+ # A recording of a tape.
30
+ TAPE = ".wav"
31
+
32
+ # What a KIM-1 takes.
33
+ EXTENSIONS = (PAPERTAPES + [BINARY, TAPE]).freeze
34
+
35
+ # Where a .bin loads without a load address, as most of the First
36
+ # Book of KIM's programs do.
37
+ DEFAULT_LOAD_ADDRESS = 0x0200
38
+
39
+ # Where the monitor saves the registers and shows the PC, for the
40
+ # NMI and IRQ vectors.
41
+ MONITOR = 0x1c00
42
+
43
+ NMI_VECTOR = 0x17fa
44
+ IRQ_VECTOR = 0x17fe
45
+
46
+ # How a KIM-1 loads and starts a program: at `load_address`, a .bin's,
47
+ # or -1 for DEFAULT_LOAD_ADDRESS, from `start_address`, or -1 for
48
+ # where it loads, and through the teletype's L command if
49
+ # `teletype`.
50
+ class Loading
51
+ attr_reader :load_address, :start_address
52
+
53
+ def initialize(load_address: -1, start_address: -1, teletype: false)
54
+ @load_address = load_address
55
+ @start_address = start_address
56
+ @teletype = teletype
57
+ end
58
+
59
+ def teletype? = @teletype
60
+ end
61
+
62
+ # Loads straight into memory, from where it loads.
63
+ DIRECT = Loading.new
64
+
65
+ class << self
66
+ # The Loading the command line's `options` ask for, with
67
+ # --load-address, --start-address and --tty-load.
68
+ def loading(options)
69
+ load_address = options.load_address
70
+ start_address = options.start_address
71
+ Loading.new(load_address: load_address.nil? ? -1 : load_address,
72
+ start_address: start_address.nil? ? -1 : start_address, teletype: options.tty_load?)
73
+ end
74
+
75
+ # Whether a KIM-1 takes the file at `path`.
76
+ def takes?(path) = EXTENSIONS.include?(File.extname(path).downcase)
77
+
78
+ # Loads the program at `path` into the KIM-1 `machine` as
79
+ # `loading` says, and starts it if `autostart`. Raises
80
+ # Machine::Unsupported when it loads outside the RAM.
81
+ def attach(machine, path, loading, autostart:)
82
+ raise Machine::Unsupported, "doesn't go in a KIM-1" unless takes?(path)
83
+ return insert_tape(machine, path) if File.extname(path).downcase == TAPE
84
+
85
+ papertape = PAPERTAPES.include?(File.extname(path).downcase)
86
+ tape = papertape ? Storage::Papertape.read(path) : nil
87
+ records = tape.nil? ? [binary(path, loading.load_address)] : tape.records
88
+ refuse_outside_ram(machine, records)
89
+ start = loading.start_address.negative? ? records.first.address : loading.start_address
90
+ if !tape.nil? && loading.teletype? && machine.tty?
91
+ machine.teletype.type("L#{tape.text}")
92
+ else
93
+ machine.on_init { deposit(machine, records) }
94
+ end
95
+ machine.on_init { machine.type_text(start_keys(machine, start, autostart)) }
96
+ "Loading #{path} at $#{hex(records.first.address)}"
97
+ end
98
+
99
+ private
100
+
101
+ def insert_tape(machine, path)
102
+ machine.tape.insert_tape(Storage::KIMTape.read(path))
103
+ "Inserted #{path} in the tape deck"
104
+ end
105
+
106
+ def binary(path, load_address)
107
+ Storage::Papertape::Record.new(load_address.negative? ? DEFAULT_LOAD_ADDRESS : load_address,
108
+ File.binread(path).bytes)
109
+ end
110
+
111
+ def refuse_outside_ram(machine, records)
112
+ records.each do |record|
113
+ address = record.address
114
+ last = address + record.bytes.size - 1
115
+ raise Machine::Unsupported, "has no bytes to load" if last < address
116
+ next if last <= 0xffff && (address..last).all? { |byte_address| machine.bus.ram?(byte_address) }
117
+
118
+ raise Machine::Unsupported, "loads at $#{hex(address)}-$#{hex(last)}, outside the KIM-1's " \
119
+ "#{machine.bus.ram_kb}K of RAM; --ram 5k or --ram expanded adds more"
120
+ end
121
+ end
122
+
123
+ def deposit(machine, records)
124
+ [NMI_VECTOR, IRQ_VECTOR].each do |vector|
125
+ machine.poke(vector, MONITOR & 0xff)
126
+ machine.poke(vector + 1, MONITOR >> 8)
127
+ end
128
+ records.each do |record|
129
+ address = record.address
130
+ record.bytes.each_with_index { |byte, offset| machine.poke(address + offset, byte) }
131
+ end
132
+ end
133
+
134
+ # What starts the program at `start`, or stops at it without
135
+ # `autostart`.
136
+ def start_keys(machine, start, autostart)
137
+ if machine.tty?
138
+ opened = "#{hex(start)} "
139
+ return autostart ? "#{opened}G" : opened
140
+ end
141
+
142
+ keys = "AD #{hex(start).chars.join(' ')}"
143
+ autostart ? "#{keys} GO" : keys
144
+ end
145
+
146
+ def hex(value) = value.to_s(16).upcase.rjust(4, "0")
147
+ end
148
+ end
149
+ end
150
+ end
@@ -27,8 +27,10 @@ module Badline
27
27
  # BASIC 2 and 4 load the first program off the disk and run it.
28
28
  # BASIC 1's LOAD finds no device on the IEEE-488 bus, so the first
29
29
  # program, which the block gives, goes in as a program file does,
30
- # once the KERNAL has booted.
30
+ # once the KERNAL has booted. The SuperPET's 6809 starts its own
31
+ # menu instead.
31
32
  def disk_autostart(machine)
33
+ return unless machine.boots_basic?
32
34
  return machine.type_text(AUTOSTART) unless machine.model.basic1?
33
35
 
34
36
  data = yield
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Badline
4
4
  module Media
5
- # A program loading into a C64, a C128, a 264 series machine or a PET
6
- # once it has booted (Media.attach). The VIC-20 has its own
5
+ # A program loading into a C64, a C128, a 264 series machine, a PET or
6
+ # a CBM-II once it has booted (Media.attach). The VIC-20 has its own
7
7
  # (Vic20Basic).
8
8
  module Program
9
9
  # Where BASIC 3.5 starts on the 264 series.
@@ -35,10 +35,13 @@ module Badline
35
35
  # KERNAL's end address (EAL). BASIC 7.0 starts at $1C01 and keeps
36
36
  # the end of its text at $1210, and the 264 KERNAL keeps EAL at $9D.
37
37
  # PET BASIC 2 and 4 keep the end of their text at $2A and EAL at
38
- # $C9, and BASIC 1 at $7C and $E5.
38
+ # $C9, and BASIC 1 at $7C and $E5. The CBM-II's BASIC starts at
39
+ # $0003 of segment 1 and keeps the end of its text at $2F, and its
40
+ # KERNAL EAL at $96, in segment 15.
39
41
  def basic(computer)
40
42
  return [PLUS4_BASIC_START, 0x2d, KernalTrap::PLUS4_ZERO_PAGE.end_address] if computer.family == :plus4
41
43
  return pet_basic(computer) if computer.family == :pet
44
+ return [CBM2Media::BASIC_START, CBM2Media::TEXT_END, CBM2Media::EAL] if computer.family == :cbm2
42
45
  return [0x1c01, 0x1210, 0xae] if computer.family == :c128 && computer.mode == :c128
43
46
 
44
47
  [BASIC_START, 0x2d, 0xae]
@@ -4,19 +4,28 @@ module Badline
4
4
  module Media
5
5
  # Device 8 as a true drive instead of the KERNAL traps: a 1541, or on
6
6
  # the C128 its 1571, or on the Plus/4 a 1551 if it asks for one, or a
7
- # 1581, or on the PET a 2031 on the IEEE-488 bus. Once a true drive is plugged in as device 8, disk media go into
8
- # it rather than into the traps, so a LOAD runs the DOS and every byte
9
- # crosses the serial bus or TCBM. A .g64 holds the
10
- # disk's raw GCR, which only a true drive reads, so it plugs one in
11
- # when there's none yet. The 1541 reads only .d64 and .g64 images, the
12
- # 1571 .d71 and .g71 as well, and the 1581 only .d81, so a .d81 swaps
13
- # the drive on device 8 for a 1581 (Drives::Model1581::Slot), and another disk
14
- # image swaps a 1581 back for the machine's own drive. The PET has no
15
- # 1581, and its 2031 reads .d64 and .g64 images only. The traps' other
16
- # disk media, a .t64, a host directory, or a .d80 or .d82, raise Error.
7
+ # 1581, or on the PET or the CBM-II a drive on the IEEE-488 bus. Once a
8
+ # true drive is plugged in as device 8, disk media go into it rather
9
+ # than into the traps, so a LOAD runs the DOS and every byte crosses the
10
+ # serial bus, TCBM or the IEEE-488 bus. A .g64 holds the disk's raw
11
+ # GCR, which only a true drive reads, so it plugs one in when there's
12
+ # none yet. The 1541 reads only .d64 and .g64 images, the 1571 .d71 and
13
+ # .g71 as well, and the 1581 only .d81, so a .d81 swaps the drive on
14
+ # device 8 for a 1581 (Drives::Model1581::Slot), and another disk image
15
+ # swaps a 1581 back for the machine's own drive. On the PET and
16
+ # the CBM-II an image goes in the drive on device 8 if it reads it, or
17
+ # else in a drive of the machine's own model, plug_true_drive's, if
18
+ # that reads it, or else in the model made for it, swapped in
19
+ # (Drives::IEEEModels): a 2031 for a .d64, an 8050 for a .d80 and an
20
+ # 8250 for a .d82, in the drive of a dual unit the machine names
21
+ # (disk_drive_number): drive 1 on a SuperPET running its 6809. The
22
+ # traps' other disk media, a .t64 or a host directory, raise Error.
17
23
  module TrueDrive
18
24
  class Error < ArgumentError; end
19
25
 
26
+ # The families whose drives sit on the IEEE-488 bus.
27
+ IEEE_FAMILIES = %i[pet cbm2].freeze
28
+
20
29
  class << self
21
30
  # Plugs the machine's true drive in as device 8, unless one is there
22
31
  # already, and returns it.
@@ -30,12 +39,12 @@ module Badline
30
39
  drive if drive && drive.device == KernalTrap::Routine::DEVICE
31
40
  end
32
41
 
33
- # Whether the path goes in a true drive: a .g64 or .g71 always, and
34
- # other disk media, which the traps would mount, when a true drive
42
+ # Whether the path goes in a true drive: a .g64, .g71 or .d90 always,
43
+ # and other disk media, which the traps would mount, when a true drive
35
44
  # is device 8.
36
45
  def takes?(computer, path)
37
46
  kind = Extensions.kind(path)
38
- return true if kind == :gcr
47
+ return true if %i[gcr hard_disk].include?(kind)
39
48
 
40
49
  !drive(computer).nil? && (File.directory?(path) || %i[disk archive ieee_disk].include?(kind))
41
50
  end
@@ -45,15 +54,13 @@ module Badline
45
54
  # mounted through the traps, so LOAD and SAVE reach the drive too.
46
55
  # `read_only` puts it in write-protected.
47
56
  def insert(computer, path, read_only: false)
48
- pet = computer.family == :pet
49
- return insert_d81(computer, path, read_only) if !pet && File.extname(path).casecmp?(".d81")
57
+ return insert_ieee(computer, path, read_only) if IEEE_FAMILIES.include?(computer.family)
58
+ return insert_d81(computer, path, read_only) if File.extname(path).casecmp?(".d81")
50
59
 
51
60
  drive = own_drive(computer)
52
61
  name = drive&.model_name || default_model(computer)
53
62
  types = name == "1571" ? %w[.d64 .g64 .d71 .g71] : %w[.d64 .g64]
54
63
  unless types.include?(File.extname(path).downcase)
55
- raise Error, "#{path} is not a #{types.join(' or ')} image, which the #{name} reads" if pet
56
-
57
64
  raise Error, "#{path} is not a #{types.join(', ')} or .d81 image, which the #{name} and the 1581 read"
58
65
  end
59
66
 
@@ -66,20 +73,39 @@ module Badline
66
73
  private
67
74
 
68
75
  # The model plug_true_drive plugs in.
69
- def default_model(computer)
70
- case computer.family
71
- when :c128 then "1571"
72
- when :pet then "2031"
73
- else "1541"
74
- end
75
- end
76
+ def default_model(computer) = computer.family == :c128 ? "1571" : "1541"
76
77
 
77
- # The drive on device 8 unless it's a 1581.
78
+ # The drive on device 8 unless it's a 1581, which a .d64 swaps back
79
+ # out.
78
80
  def own_drive(computer)
79
81
  drive = drive(computer)
80
82
  drive unless drive&.model_name == "1581"
81
83
  end
82
84
 
85
+ # The image in the model Drives::IEEEModels.taking picks, swapped in
86
+ # for the drive on device 8 unless it's that model already.
87
+ def insert_ieee(computer, path, read_only)
88
+ drive = drive(computer)
89
+ model = Drives::IEEEModels.taking(path, drive&.model_name, computer.true_drive_model)
90
+ unless model
91
+ name = computer.true_drive_model
92
+ types = Drives::IEEEModels::MEDIA.fetch(name)
93
+ raise Error, "#{path} is not a #{types.join(' or ')} image, which the #{name} reads"
94
+ end
95
+
96
+ drive = computer.plug_ieee_drive(model) unless drive&.model_name == model
97
+ number = computer.disk_drive_number
98
+ if number.positive? && drive.is_a?(Drives::Model8050)
99
+ drive.insert_image(path, drive: number, read_only:)
100
+ computer.unmount
101
+ return "Inserted #{path} in the #{model}'s drive #{number} as device 8"
102
+ end
103
+
104
+ drive.insert_image(path, read_only:)
105
+ computer.unmount
106
+ "Inserted #{path} in the #{model} as device 8"
107
+ end
108
+
83
109
  def insert_d81(computer, path, read_only)
84
110
  drive = drive(computer)
85
111
  drive = computer.plug_drive1581 unless drive&.model_name == "1581"
data/lib/badline/media.rb CHANGED
@@ -12,6 +12,8 @@ require "badline/media/vic20_cartridge"
12
12
  require "badline/media/vic20_media"
13
13
  require "badline/media/plus4_media"
14
14
  require "badline/media/pet_media"
15
+ require "badline/media/cbm2_media"
16
+ require "badline/media/kim1_media"
15
17
  require "badline/media/tune"
16
18
  require "badline/media/program"
17
19
 
@@ -28,7 +30,8 @@ module Badline
28
30
  ".d81" => Storage::D81Image,
29
31
  ".t64" => Storage::T64,
30
32
  ".d80" => Storage::D80Image,
31
- ".d82" => Storage::D82Image
33
+ ".d82" => Storage::D82Image,
34
+ ".d67" => Storage::D67Image
32
35
  }.freeze
33
36
 
34
37
  class << self
@@ -53,9 +56,13 @@ module Badline
53
56
  # .crt cartridges and raw .bin ROM images in its two cartridges'
54
57
  # slots (Plus4Media), and no .sid tunes either. The PET mounts .d80
55
58
  # and .d82 disks too, which no other machine takes, and has no
56
- # cartridges, tunes or true drives (PETMedia). The C128 in C64 mode
57
- # takes a C64's.
58
- def attach(computer, path, autostart: true, subtune: nil, **)
59
+ # cartridges, tunes or true drives (PETMedia), and so does the CBM-II,
60
+ # which takes its own .crt cartridges and no tapes (CBM2Media). The
61
+ # C128 in C64 mode takes a C64's. The KIM-1 takes only its programs,
62
+ # which load as `kim1`, a KIM1Media::Loading, says (KIM1Media).
63
+ def attach(computer, path, autostart: true, subtune: nil, kim1: KIM1Media::DIRECT, **)
64
+ return KIM1Media.attach(computer, path, kim1, autostart:) if computer.family == :kim1
65
+
59
66
  path = DiskList.disk(path)
60
67
  refuse_unsupported(computer, path)
61
68
 
@@ -126,17 +133,20 @@ module Badline
126
133
  private
127
134
 
128
135
  # The VIC-20 and the 264 series have no SID to play a .sid tune on,
129
- # and only the PET has the IEEE-488 drive a .d80 or .d82 goes in.
136
+ # only the PET and the CBM-II have the IEEE-488 drive a .d80, .d82
137
+ # or .d90 goes in, and only the KIM-1 reads papertape.
130
138
  def refuse_unsupported(computer, path)
131
139
  family = computer.family
132
- if Extensions.kind(path) == :ieee_disk && family != :pet
133
- raise ArgumentError, "#{path} needs a PET's IEEE-488 drive"
140
+ raise Machine::Unsupported, "#{path} needs a KIM-1" if Extensions.kind(path) == :papertape
141
+ if %i[ieee_disk hard_disk].include?(Extensions.kind(path)) && !%i[pet cbm2].include?(family)
142
+ raise ArgumentError, "#{path} needs a PET's or a CBM-II's IEEE-488 drive"
134
143
  end
135
144
 
136
145
  refused, name = case family
137
146
  when :vic20 then [Vic20Media::UNSUPPORTED, "a VIC-20"]
138
147
  when :plus4 then [Vic20Media::UNSUPPORTED, "a Plus/4"]
139
148
  when :pet then [PETMedia::UNSUPPORTED, "a PET"]
149
+ when :cbm2 then [CBM2Media::UNSUPPORTED, "a CBM-II"]
140
150
  else [[], ""]
141
151
  end
142
152
  raise ArgumentError, "#{path} doesn't go in #{name}" if refused.include?(File.extname(path).downcase)
@@ -182,7 +192,11 @@ module Badline
182
192
  # VIC-20 goes by that program, which the block gives
183
193
  # (Vic20Media.disk_autostart).
184
194
  def disk_autostart(computer)
185
- computer.family == :vic20 ? Vic20Media.disk_autostart(yield) : AUTOSTART
195
+ case computer.family
196
+ when :vic20 then Vic20Media.disk_autostart(yield)
197
+ when :cbm2 then CBM2Media::AUTOSTART
198
+ else AUTOSTART
199
+ end
186
200
  end
187
201
 
188
202
  # A VIC-20 or 264 series cartridge is its ROM chips. On the 264, the
@@ -194,6 +208,7 @@ module Badline
194
208
  crt = Storage::CRTFile.new(path, machine: :vic20)
195
209
  computer.attach_cartridge(crt.chips, crt.name.empty? ? File.basename(path, File.extname(path)) : crt.name)
196
210
  when :plus4 then computer.attach_cartridge(Plus4Media.cartridge_chips(path, slot: options.fetch(:slot, nil)))
211
+ when :cbm2 then computer.attach_cartridge(CBM2Media.cartridge_chips(path))
197
212
  else
198
213
  computer.attach_cartridge(C64::Cartridge.from_file(path, **options))
199
214
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "badline/storage/crt_file"
3
4
  require "badline/storage/p00"
4
5
  require "badline/storage/tap"
5
6
 
@@ -11,9 +12,94 @@ module Badline
11
12
  # Where C128 BASIC starts, which a program for the C128 loads at.
12
13
  C128_BASIC_START = 0x1c01
13
14
 
15
+ # Whether the KIM-1 talks to a teletype: with --tty or --tty-load,
16
+ # and always on the terminal with --headless.
17
+ def tty? = @tty || @tty_load || kim1_terminal?
18
+
19
+ # The KIM-1 teletype's baud.
20
+ def baud = @baud
21
+
22
+ # Whether the KIM-1 loads a papertape through its monitor's L
23
+ # command, with --tty-load.
24
+ def tty_load? = @tty_load
25
+
26
+ # Where the KIM-1 loads a .bin, and where it starts its program, as
27
+ # --load-address and --start-address give them, or nil.
28
+ def load_address = @load_address
29
+
30
+ def start_address = @start_address
31
+
32
+ # Whether the KIM-1 runs on the terminal, its teletype there.
33
+ def kim1_terminal? = @family == :kim1 && @headless
34
+
14
35
  private
15
36
 
16
- # Takes `sid`, `c64`, `vic20`, `c128`, `plus4` or `pet` when given first.
37
+ # The KIM-1's keypad, until --tty or --baud name its teletype, and
38
+ # its programs where they load.
39
+ def initialize_kim1
40
+ @tty = false
41
+ @tty_load = false
42
+ @baud = 9600
43
+ @load_address = nil
44
+ @start_address = nil
45
+ end
46
+
47
+ # The KIM-1 teletype's baud, and where it loads a .bin and starts a
48
+ # program.
49
+ def kim1_option(name, flag, value)
50
+ case name
51
+ when "--baud" then @baud = baud_for(value)
52
+ when "--load-address" then @load_address = address(flag, value)
53
+ else @start_address = address(flag, value)
54
+ end
55
+ end
56
+
57
+ def kim1_switch(name)
58
+ if name == "--tty"
59
+ @tty = true
60
+ else
61
+ @tty_load = true
62
+ end
63
+ end
64
+
65
+ # An address in hex, with or without a $ or 0x before it.
66
+ def address(flag, value)
67
+ digits = value.delete_prefix("$").delete_prefix("0x").delete_prefix("0X")
68
+ raise Error, "invalid argument: #{flag} #{value}" unless digits.match?(/\A[0-9a-fA-F]{1,4}\z/)
69
+
70
+ digits.to_i(16)
71
+ end
72
+
73
+ def kim1_ram(value)
74
+ raise Error, "invalid argument: --ram #{value}" unless KIM1_RAM.key?(value)
75
+
76
+ KIM1_RAM[value]
77
+ end
78
+
79
+ # The KIM-1 runs in the window, or with --headless on the terminal,
80
+ # without the C64's SID and REU. Only a papertape goes in through
81
+ # the teletype, and only a .bin has a load address to give.
82
+ def validate_kim1
83
+ raise Error, "--ntsc needs the C64" if @models.include?("ntsc")
84
+ raise Error, "--sid needs the C64" unless @sid_model.nil?
85
+ raise Error, "--reu needs the C64" unless @reu.nil?
86
+ raise Error, "--audio-out needs a .sid tune" unless @audio_out.nil?
87
+
88
+ extension = @media_path.nil? ? "" : File.extname(@media_path).downcase
89
+ raise Error, "--tty-load needs a .ptp or .pap" if @tty_load && !%w[.ptp .pap].include?(extension)
90
+ raise Error, "--load-address needs a .bin" unless @load_address.nil? || extension == ".bin"
91
+ end
92
+
93
+ # Only the KIM-1 has a teletype, and programs without a load address.
94
+ def refuse_kim1_options
95
+ raise Error, "#{@tty ? '--tty' : '--baud'} needs kim1" if @tty || @baud != 9600
96
+ raise Error, "--tty-load needs kim1" if @tty_load
97
+ raise Error, "--load-address needs kim1" unless @load_address.nil?
98
+ raise Error, "--start-address needs kim1" unless @start_address.nil?
99
+ end
100
+
101
+ # Takes `sid`, `c64`, `vic20`, `c128`, `plus4`, `pet`, `cbm2` or `kim1`
102
+ # when given first.
17
103
  def subcommand(args)
18
104
  case args.first
19
105
  when "sid" then @sid_command = true
@@ -22,6 +108,8 @@ module Badline
22
108
  when "c128" then @family = :c128
23
109
  when "plus4" then @family = :plus4
24
110
  when "pet" then @family = :pet
111
+ when "cbm2" then @family = :cbm2
112
+ when "kim1" then @family = :kim1
25
113
  else return
26
114
  end
27
115
  @family_named = !@sid_command
@@ -34,27 +122,41 @@ module Badline
34
122
  when :c128 then C128_MODELS
35
123
  when :plus4 then PLUS4_MODELS
36
124
  when :pet then PET_MODELS
125
+ when :cbm2 then CBM2_MODELS
126
+ when :kim1 then KIM1_MODELS
37
127
  else MODELS
38
128
  end
39
129
  end
40
130
 
41
131
  # Without a family named, a program that loads at C128_BASIC_START is
42
- # the C128's, and an 8050's or 8250's disk or a PET tape the PET's. A
43
- # program that loads where PET BASIC starts could be a VIC-20's too.
132
+ # the C128's, an 8050's or 8250's disk or a PET tape the PET's, a
133
+ # cartridge signed as a CBM-II's the CBM-II's, and a papertape the
134
+ # KIM-1's. A program that loads where PET BASIC starts could be a
135
+ # VIC-20's too.
44
136
  def pick_family
45
137
  return if @family_named || @sid_command || @media_path.nil? || !File.file?(@media_path)
46
138
 
47
- if pet_media?
139
+ if Media::Extensions.kind(@media_path) == :papertape
140
+ @family = :kim1
141
+ elsif pet_media?
48
142
  @family = :pet
143
+ elsif cbm2_cartridge?
144
+ @family = :cbm2
49
145
  elsif Media::Extensions::PROGRAMS.include?(File.extname(@media_path).downcase)
50
146
  @family = :c128 if Storage::P00.load_address(@media_path) == C128_BASIC_START
51
147
  end
52
148
  end
53
149
 
54
- # Whether the media is an 8050's or 8250's disk or a PET tape.
150
+ # Whether the media is a cartridge signed as a CBM-II's.
151
+ def cbm2_cartridge?
152
+ Media::Extensions.kind(@media_path) == :cartridge && Storage::CRTFile.cbm2?(@media_path)
153
+ end
154
+
155
+ # Whether the media is an 8050's or 8250's disk, a D9060's or D9090's
156
+ # hard disk, or a PET tape.
55
157
  def pet_media?
56
158
  kind = Media::Extensions.kind(@media_path)
57
- kind == :ieee_disk || (kind == :tape && Storage::TAP.platform_of(@media_path) == Storage::TAP::PET_PLATFORM)
159
+ %i[ieee_disk hard_disk].include?(kind) || (kind == :tape && Storage::TAP.platform_of(@media_path) == Storage::TAP::PET_PLATFORM)
58
160
  end
59
161
  end
60
162
  end