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
@@ -4,20 +4,24 @@ module Badline
4
4
  module Snapshot
5
5
  module Vice
6
6
  module VICII
7
- # Reads a VIC-II module and puts it into badline's VIC.
7
+ # Reads a VIC-II module and puts it into badline's VIC. A reader of
8
+ # another layout passes no fields and sets the same state itself.
8
9
  class Import
9
10
  def initialize(fields)
10
11
  @fields = fields
12
+ return if fields.nil?
13
+
11
14
  read_beam
12
15
  read_counters
13
16
  read_sprites
14
17
  end
15
18
 
16
- def apply(computer)
17
- vic = computer.vic
18
- computer.address_bus.color_ram.tap do |ram|
19
- @color_ram.each_with_index { |value, offset| ram.poke(0xd800 + offset, value) }
20
- end
19
+ def apply(computer) = apply_to(computer.vic, computer.address_bus.color_ram, 0xd800)
20
+
21
+ # Puts it into `vic`, with its colour RAM into `color_ram` from
22
+ # `base`.
23
+ def apply_to(vic, color_ram, base)
24
+ @color_ram.each_with_index { |value, offset| color_ram.poke(base + offset, value) }
21
25
  load_registers(vic)
22
26
  position(vic)
23
27
  load_counters(vic)
@@ -14,6 +14,9 @@ require "badline/snapshot/vice/vicii"
14
14
  require "badline/snapshot/vice/vicii_import"
15
15
  require "badline/snapshot/vice/restore"
16
16
  require "badline/snapshot/vice/pet"
17
+ require "badline/snapshot/vice/cbm2"
18
+ require "badline/snapshot/vice/p500"
19
+ require "badline/snapshot/vice/p500_restore"
17
20
 
18
21
  module Badline
19
22
  module Snapshot
@@ -36,8 +36,8 @@ module Badline
36
36
  # end of the instruction its CPU is in, and of an REU transfer under
37
37
  # way (Machine::Settling), so a save advances it by a few cycles, and a
38
38
  # second save straight after runs none. A VIC-20's, a C128's, a
39
- # Plus/4's or a PET's snapshot is in a container named as xvic, x128,
40
- # xplus4 or xpet names its machine.
39
+ # Plus/4's, a PET's or a CBM-II's snapshot is in a container named as
40
+ # xvic, x128, xplus4, xpet or xcbm2 names its machine.
41
41
  def dump(computer) = Image.of(computer).to_s
42
42
 
43
43
  def read(path) = Image.new(Container.parse(File.binread(path)), name: File.basename(path))
@@ -52,7 +52,7 @@ module Badline
52
52
  def restore(computer, path, &) = read(path).restore(computer, &)
53
53
 
54
54
  # A machine's snapshots, as .vsf files and in memory. C64, Vic20, C128,
55
- # Plus4 and PET each include it.
55
+ # Plus4, PET, CBM2 and CBM2::P500 each include it.
56
56
  module Files
57
57
  def save_snapshot(path) = Snapshot.save(self, path)
58
58
 
@@ -99,4 +99,16 @@ module Badline
99
99
  class PET
100
100
  include Snapshot::Files
101
101
  end
102
+
103
+ class KIM1
104
+ include Snapshot::Files
105
+ end
106
+
107
+ class CBM2
108
+ include Snapshot::Files
109
+
110
+ class P500
111
+ include Snapshot::Files
112
+ end
113
+ end
102
114
  end
@@ -36,7 +36,7 @@ module Badline
36
36
  @value = enabled && enabled != 0 ? @value | 0x01 : @value & ~0x01
37
37
  end
38
38
 
39
- def carry? = @value & 0x01 != 0
39
+ def carry? = @value.anybits?(0x01)
40
40
 
41
41
  def carry = @value.nobits?(0x01) ? 0 : 1
42
42
 
@@ -44,7 +44,7 @@ module Badline
44
44
  @value = enabled && enabled != 0 ? @value | 0x02 : @value & ~0x02
45
45
  end
46
46
 
47
- def zero? = @value & 0x02 != 0
47
+ def zero? = @value.anybits?(0x02)
48
48
 
49
49
  def zero = @value.nobits?(0x02) ? 0 : 1
50
50
 
@@ -52,7 +52,7 @@ module Badline
52
52
  @value = enabled && enabled != 0 ? @value | 0x04 : @value & ~0x04
53
53
  end
54
54
 
55
- def interrupt? = @value & 0x04 != 0
55
+ def interrupt? = @value.anybits?(0x04)
56
56
 
57
57
  def interrupt = @value.nobits?(0x04) ? 0 : 1
58
58
 
@@ -60,7 +60,7 @@ module Badline
60
60
  @value = enabled && enabled != 0 ? @value | 0x08 : @value & ~0x08
61
61
  end
62
62
 
63
- def decimal? = @value & 0x08 != 0
63
+ def decimal? = @value.anybits?(0x08)
64
64
 
65
65
  def decimal = @value.nobits?(0x08) ? 0 : 1
66
66
 
@@ -68,7 +68,7 @@ module Badline
68
68
  @value = enabled && enabled != 0 ? @value | 0x10 : @value & ~0x10
69
69
  end
70
70
 
71
- def break? = @value & 0x10 != 0
71
+ def break? = @value.anybits?(0x10)
72
72
 
73
73
  def break = @value.nobits?(0x10) ? 0 : 1
74
74
 
@@ -76,7 +76,7 @@ module Badline
76
76
  @value = enabled && enabled != 0 ? @value | 0x40 : @value & ~0x40
77
77
  end
78
78
 
79
- def overflow? = @value & 0x40 != 0
79
+ def overflow? = @value.anybits?(0x40)
80
80
 
81
81
  def overflow = @value.nobits?(0x40) ? 0 : 1
82
82
 
@@ -84,7 +84,7 @@ module Badline
84
84
  @value = enabled && enabled != 0 ? @value | 0x80 : @value & ~0x80
85
85
  end
86
86
 
87
- def negative? = @value & 0x80 != 0
87
+ def negative? = @value.anybits?(0x80)
88
88
 
89
89
  def negative = @value.nobits?(0x80) ? 0 : 1
90
90
  end
@@ -95,7 +95,7 @@ module Badline
95
95
  @value = enabled && enabled != 0 ? @value | 0x01 : @value & ~0x01
96
96
  end
97
97
 
98
- def basic? = @value & 0x01 != 0
98
+ def basic? = @value.anybits?(0x01)
99
99
 
100
100
  def basic = @value.nobits?(0x01) ? 0 : 1
101
101
 
@@ -103,7 +103,7 @@ module Badline
103
103
  @value = enabled && enabled != 0 ? @value | 0x02 : @value & ~0x02
104
104
  end
105
105
 
106
- def kernal? = @value & 0x02 != 0
106
+ def kernal? = @value.anybits?(0x02)
107
107
 
108
108
  def kernal = @value.nobits?(0x02) ? 0 : 1
109
109
 
@@ -111,7 +111,7 @@ module Badline
111
111
  @value = enabled && enabled != 0 ? @value | 0x04 : @value & ~0x04
112
112
  end
113
113
 
114
- def io? = @value & 0x04 != 0
114
+ def io? = @value.anybits?(0x04)
115
115
 
116
116
  def io = @value.nobits?(0x04) ? 0 : 1
117
117
 
@@ -119,7 +119,7 @@ module Badline
119
119
  @value = enabled && enabled != 0 ? @value | 0x08 : @value & ~0x08
120
120
  end
121
121
 
122
- def tape_out? = @value & 0x08 != 0
122
+ def tape_out? = @value.anybits?(0x08)
123
123
 
124
124
  def tape_out = @value.nobits?(0x08) ? 0 : 1
125
125
 
@@ -127,7 +127,7 @@ module Badline
127
127
  @value = enabled && enabled != 0 ? @value | 0x10 : @value & ~0x10
128
128
  end
129
129
 
130
- def tape_switch? = @value & 0x10 != 0
130
+ def tape_switch? = @value.anybits?(0x10)
131
131
 
132
132
  def tape_switch = @value.nobits?(0x10) ? 0 : 1
133
133
 
@@ -135,7 +135,7 @@ module Badline
135
135
  @value = enabled && enabled != 0 ? @value | 0x20 : @value & ~0x20
136
136
  end
137
137
 
138
- def tape_motor? = @value & 0x20 != 0
138
+ def tape_motor? = @value.anybits?(0x20)
139
139
 
140
140
  def tape_motor = @value.nobits?(0x20) ? 0 : 1
141
141
  end
@@ -146,7 +146,7 @@ module Badline
146
146
  @value = enabled && enabled != 0 ? @value | 0x01 : @value & ~0x01
147
147
  end
148
148
 
149
- def start? = @value & 0x01 != 0
149
+ def start? = @value.anybits?(0x01)
150
150
 
151
151
  def start = @value.nobits?(0x01) ? 0 : 1
152
152
 
@@ -154,7 +154,7 @@ module Badline
154
154
  @value = enabled && enabled != 0 ? @value | 0x02 : @value & ~0x02
155
155
  end
156
156
 
157
- def output? = @value & 0x02 != 0
157
+ def output? = @value.anybits?(0x02)
158
158
 
159
159
  def output = @value.nobits?(0x02) ? 0 : 1
160
160
 
@@ -162,7 +162,7 @@ module Badline
162
162
  @value = enabled && enabled != 0 ? @value | 0x04 : @value & ~0x04
163
163
  end
164
164
 
165
- def out_mode? = @value & 0x04 != 0
165
+ def out_mode? = @value.anybits?(0x04)
166
166
 
167
167
  def out_mode = @value.nobits?(0x04) ? 0 : 1
168
168
 
@@ -170,7 +170,7 @@ module Badline
170
170
  @value = enabled && enabled != 0 ? @value | 0x08 : @value & ~0x08
171
171
  end
172
172
 
173
- def run_mode? = @value & 0x08 != 0
173
+ def run_mode? = @value.anybits?(0x08)
174
174
 
175
175
  def run_mode = @value.nobits?(0x08) ? 0 : 1
176
176
 
@@ -178,7 +178,7 @@ module Badline
178
178
  @value = enabled && enabled != 0 ? @value | 0x10 : @value & ~0x10
179
179
  end
180
180
 
181
- def load? = @value & 0x10 != 0
181
+ def load? = @value.anybits?(0x10)
182
182
 
183
183
  def load = @value.nobits?(0x10) ? 0 : 1
184
184
 
@@ -186,7 +186,7 @@ module Badline
186
186
  @value = enabled && enabled != 0 ? @value | 0x20 : @value & ~0x20
187
187
  end
188
188
 
189
- def in_mode? = @value & 0x20 != 0
189
+ def in_mode? = @value.anybits?(0x20)
190
190
 
191
191
  def in_mode = @value.nobits?(0x20) ? 0 : 1
192
192
 
@@ -194,7 +194,7 @@ module Badline
194
194
  @value = enabled && enabled != 0 ? @value | 0x40 : @value & ~0x40
195
195
  end
196
196
 
197
- def serial_mode? = @value & 0x40 != 0
197
+ def serial_mode? = @value.anybits?(0x40)
198
198
 
199
199
  def serial_mode = @value.nobits?(0x40) ? 0 : 1
200
200
 
@@ -202,7 +202,7 @@ module Badline
202
202
  @value = enabled && enabled != 0 ? @value | 0x80 : @value & ~0x80
203
203
  end
204
204
 
205
- def clock_frequency? = @value & 0x80 != 0
205
+ def clock_frequency? = @value.anybits?(0x80)
206
206
 
207
207
  def clock_frequency = @value.nobits?(0x80) ? 0 : 1
208
208
 
@@ -210,7 +210,7 @@ module Badline
210
210
  @value = enabled && enabled != 0 ? @value | 0x20 : @value & ~0x20
211
211
  end
212
212
 
213
- def in_cnt? = @value & 0x20 != 0
213
+ def in_cnt? = @value.anybits?(0x20)
214
214
 
215
215
  def in_cnt = @value.nobits?(0x20) ? 0 : 1
216
216
 
@@ -218,7 +218,7 @@ module Badline
218
218
  @value = enabled && enabled != 0 ? @value | 0x40 : @value & ~0x40
219
219
  end
220
220
 
221
- def in_timer_a? = @value & 0x40 != 0
221
+ def in_timer_a? = @value.anybits?(0x40)
222
222
 
223
223
  def in_timer_a = @value.nobits?(0x40) ? 0 : 1
224
224
 
@@ -226,7 +226,7 @@ module Badline
226
226
  @value = enabled && enabled != 0 ? @value | 0x80 : @value & ~0x80
227
227
  end
228
228
 
229
- def alarm? = @value & 0x80 != 0
229
+ def alarm? = @value.anybits?(0x80)
230
230
 
231
231
  def alarm = @value.nobits?(0x80) ? 0 : 1
232
232
  end
@@ -237,7 +237,7 @@ module Badline
237
237
  @value = enabled && enabled != 0 ? @value | 0x01 : @value & ~0x01
238
238
  end
239
239
 
240
- def timer_a? = @value & 0x01 != 0
240
+ def timer_a? = @value.anybits?(0x01)
241
241
 
242
242
  def timer_a = @value.nobits?(0x01) ? 0 : 1
243
243
 
@@ -245,7 +245,7 @@ module Badline
245
245
  @value = enabled && enabled != 0 ? @value | 0x02 : @value & ~0x02
246
246
  end
247
247
 
248
- def timer_b? = @value & 0x02 != 0
248
+ def timer_b? = @value.anybits?(0x02)
249
249
 
250
250
  def timer_b = @value.nobits?(0x02) ? 0 : 1
251
251
 
@@ -253,7 +253,7 @@ module Badline
253
253
  @value = enabled && enabled != 0 ? @value | 0x04 : @value & ~0x04
254
254
  end
255
255
 
256
- def alarm? = @value & 0x04 != 0
256
+ def alarm? = @value.anybits?(0x04)
257
257
 
258
258
  def alarm = @value.nobits?(0x04) ? 0 : 1
259
259
 
@@ -261,7 +261,7 @@ module Badline
261
261
  @value = enabled && enabled != 0 ? @value | 0x08 : @value & ~0x08
262
262
  end
263
263
 
264
- def serial? = @value & 0x08 != 0
264
+ def serial? = @value.anybits?(0x08)
265
265
 
266
266
  def serial = @value.nobits?(0x08) ? 0 : 1
267
267
 
@@ -269,7 +269,7 @@ module Badline
269
269
  @value = enabled && enabled != 0 ? @value | 0x10 : @value & ~0x10
270
270
  end
271
271
 
272
- def flag? = @value & 0x10 != 0
272
+ def flag? = @value.anybits?(0x10)
273
273
 
274
274
  def flag = @value.nobits?(0x10) ? 0 : 1
275
275
 
@@ -277,7 +277,7 @@ module Badline
277
277
  @value = enabled && enabled != 0 ? @value | 0x80 : @value & ~0x80
278
278
  end
279
279
 
280
- def interrupt? = @value & 0x80 != 0
280
+ def interrupt? = @value.anybits?(0x80)
281
281
 
282
282
  def interrupt = @value.nobits?(0x80) ? 0 : 1
283
283
  end
@@ -8,6 +8,7 @@ module Badline
8
8
  SIGNATURE = "C64 CARTRIDGE ".b
9
9
  VIC20_SIGNATURE = "VIC20 CARTRIDGE ".b
10
10
  PLUS4_SIGNATURE = "PLUS4 CARTRIDGE ".b
11
+ CBM2_SIGNATURE = "CBM2 CARTRIDGE ".b
11
12
  CHIP_SIGNATURE = "CHIP".b
12
13
  CHIP_HEADER_SIZE = 0x10
13
14
  RAM_CHIP = 1
@@ -19,6 +20,9 @@ module Badline
19
20
  # What .write needs of an image: the fields a parsed CRTFile reads.
20
21
  Image = Data.define(:hardware_type, :subtype, :exrom, :game, :name, :chips)
21
22
 
23
+ # Whether the file at +path+ is signed as a CBM-II's cartridge.
24
+ def self.cbm2?(path) = File.binread(path, CBM2_SIGNATURE.length) == CBM2_SIGNATURE
25
+
22
26
  def self.write(path, image)
23
27
  File.binwrite(path, encode(image))
24
28
  end
@@ -36,7 +40,8 @@ module Badline
36
40
  end
37
41
 
38
42
  # `bytes` stands in for the file's contents. `machine` is the one the
39
- # image has to be for, :c64, :vic20 or :plus4, or nil for any.
43
+ # image has to be for, :c64, :vic20, :plus4 or :cbm2, or nil for
44
+ # any.
40
45
  def initialize(path = nil, bytes: nil, machine: :c64)
41
46
  parse(bytes || File.binread(path))
42
47
  return if machine.nil? || @machine == machine
@@ -60,6 +65,7 @@ module Badline
60
65
  return :c64 if bytes.start_with?(SIGNATURE)
61
66
  return :vic20 if bytes.start_with?(VIC20_SIGNATURE)
62
67
  return :plus4 if bytes.start_with?(PLUS4_SIGNATURE)
68
+ return :cbm2 if bytes.start_with?(CBM2_SIGNATURE)
63
69
 
64
70
  raise FormatError, "Missing CRT signature"
65
71
  end
@@ -52,6 +52,13 @@ module Badline
52
52
  def bam_tracks = 1..35
53
53
  def bam_count(track) = [18, 0, 4 * track]
54
54
  def bam_bitmap(track) = [18, 0, (4 * track) + 1]
55
+ def bitmap_size = 3
56
+
57
+ # The header links the directory and holds the BAM itself.
58
+ def header_link = [18, 1]
59
+ def dos_type = "2A"
60
+ def header_tail = 4
61
+ def bam_headers(_id) = []
55
62
  end
56
63
  end
57
64
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Storage
5
+ # A 2040's disk, as DOS 1 formats it: the 1541's 35 tracks and speed
6
+ # zones, with 20 sectors on tracks 18 to 24 in place of 19, 690 blocks
7
+ # in all. The BAM and the directory are where a 1541's are, on 18/0
8
+ # and 18/1, but the BAM's format byte is $01 and the header has no DOS
9
+ # type after the ID, as c1541 formats a .d67.
10
+ class D67Image < DiskImage
11
+ ERROR_TABLES = { 177_330 => 690 }.freeze
12
+
13
+ # The sectors a track holds in each of the 1541's speed zones, by its
14
+ # number, from zone 0, the innermost.
15
+ ZONE_SECTORS = [17, 18, 20, 21].freeze
16
+
17
+ # The tracks the image holds.
18
+ def track_count = 35
19
+
20
+ def sides = 1
21
+
22
+ def sectors_in(track) = ZONE_SECTORS[D64Image.zone(track)]
23
+
24
+ private
25
+
26
+ def error_tables = ERROR_TABLES
27
+
28
+ def header_name = 0x90
29
+ def directory_track = 18
30
+ def directory_sector = 1
31
+ def directory_interleave = 3
32
+ def interleave = 10
33
+ def reserved_tracks = [18]
34
+
35
+ def bam_tracks = 1..35
36
+ def bam_count(track) = [18, 0, 4 * track]
37
+ def bam_bitmap(track) = [18, 0, (4 * track) + 1]
38
+ def bitmap_size = 3
39
+
40
+ def header_link = [18, 1]
41
+ def dos_type = " "
42
+ def header_tail = 5
43
+ def bam_headers(_id) = []
44
+
45
+ # The link to the directory, the format byte $01, and after the name
46
+ # the ID and two spaces.
47
+ def write_header(name, id)
48
+ write_at(*header_block, 0, [*header_link, 0x01, 0])
49
+ write_at(*header_block, header_name,
50
+ encode_name(name) + [NAME_PADDING, NAME_PADDING] + id + dos_type.bytes +
51
+ Array.new(header_tail, NAME_PADDING))
52
+ end
53
+ end
54
+ end
55
+ end
@@ -31,7 +31,7 @@ module Badline
31
31
  def directory_track = 39
32
32
  def directory_sector = 1
33
33
  def directory_interleave = 3
34
- def interleave = 6
34
+ def interleave = 5
35
35
 
36
36
  # Only the directory track is kept from files. The BAM's blocks on
37
37
  # track 38 are marked in use, and the rest of that track holds files.
@@ -43,6 +43,22 @@ module Badline
43
43
  def bam_tracks = 1..SIDE_TRACKS
44
44
  def bam_count(track) = [38, 3 * ((track - 1) / 50), 6 + (5 * ((track - 1) % 50))]
45
45
  def bam_bitmap(track) = [38, 3 * ((track - 1) / 50), 7 + (5 * ((track - 1) % 50))]
46
+ def bitmap_size = 4
47
+
48
+ # The header links the BAM's first block, and each BAM block the next,
49
+ # the last one the directory. A BAM block names the format and the
50
+ # first track it covers and the one past its last.
51
+ def header_link = [38, 0]
52
+ def dos_type = "2C"
53
+ def header_tail = 4
54
+
55
+ def bam_headers(_id)
56
+ groups = bam_tracks.each_slice(50).to_a
57
+ groups.each_with_index.map do |tracks, block|
58
+ link = block == groups.length - 1 ? [directory_track, directory_sector] : [38, 3 * (block + 1)]
59
+ [38, 3 * block, [*link, dos_type.getbyte(1), 0, tracks.first, tracks.last + 1]]
60
+ end
61
+ end
46
62
  end
47
63
  end
48
64
  end
@@ -21,6 +21,20 @@ module Badline
21
21
  def bam_tracks = 1..80
22
22
  def bam_count(track) = [40, track > 40 ? 2 : 1, 0x10 + (((track - 1) % 40) * 6)]
23
23
  def bam_bitmap(track) = [40, track > 40 ? 2 : 1, 0x11 + (((track - 1) % 40) * 6)]
24
+ def bitmap_size = 5
25
+
26
+ # The header links the directory's first block, at 40/3. Each BAM
27
+ # block links the next, names the format and its complement, and
28
+ # holds the ID and the I/O byte.
29
+ def header_link = [40, 3]
30
+ def dos_type = "3D"
31
+ def header_tail = 2
32
+
33
+ def bam_headers(id)
34
+ [[40, 1, [40, 2]], [40, 2, [0, 0xff]]].map do |track, sector, link|
35
+ [track, sector, [*link, 0x44, 0xbb, *id, 0xc0, 0]]
36
+ end
37
+ end
24
38
 
25
39
  def sectors_in(_track) = 40
26
40
  end
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Storage
5
+ # A D9060 or D9090 hard disk: 153 cylinders of 32 blocks a head, four
6
+ # heads on the D9060 and six on the D9090. DOS 3.0 numbers a block by
7
+ # its cylinder as the track, from 0, and by head × 32 + sector as the
8
+ # sector. The controller numbers the same block by its place in the
9
+ # image (LBA), and reads and writes it there.
10
+ #
11
+ # The image's size tells the two apart. One of another size, such as an
12
+ # empty file to format, takes the heads it's given, or four, and reads as
13
+ # zeros past its end.
14
+ class D90Image < DiskImage
15
+ CYLINDERS = 153
16
+ SECTORS_PER_HEAD = 32
17
+
18
+ # The heads of each drive, by the size of its image.
19
+ HEADS = { 5_013_504 => 4, 7_520_256 => 6 }.freeze
20
+
21
+ # The BAM entry of a head: a free count and a four-byte bitmap.
22
+ ENTRY = 5
23
+
24
+ # The heads of the drive an image of the size belongs to, or nil for
25
+ # a size neither has.
26
+ def self.heads_for(size) = HEADS[size]
27
+
28
+ attr_reader :heads
29
+
30
+ def initialize(path, read_only: false, bytes: nil, heads: nil)
31
+ super(path, read_only:, bytes:)
32
+ @heads = heads || HEADS.fetch(@bytes.length, 4)
33
+ size = block_count * SECTOR_SIZE
34
+ @bytes.concat(Array.new(size - @bytes.length, 0)) if @bytes.length < size
35
+ end
36
+
37
+ def sectors_in(_track) = @heads * SECTORS_PER_HEAD
38
+
39
+ # The blocks the disk holds.
40
+ def block_count = CYLINDERS * sectors_in(0)
41
+
42
+ # The controller's number for the block.
43
+ def lba(track, sector) = (track * sectors_in(track)) + sector
44
+
45
+ def header_block = [76, 20]
46
+
47
+ # Whether the disk holds the blocks from the LBA on.
48
+ def blocks?(lba, count) = lba >= 0 && lba + count <= block_count
49
+
50
+ # The bytes of the blocks from the LBA on, or nil when the disk
51
+ # doesn't hold them all.
52
+ def read_blocks(lba, count)
53
+ @bytes[lba * SECTOR_SIZE, count * SECTOR_SIZE] if blocks?(lba, count)
54
+ end
55
+
56
+ # Writes whole blocks from the LBA on, straight to the host file. It
57
+ # writes nothing when the disk doesn't hold them all. A disk that
58
+ # takes no writes raises WRITE PROTECT ON and keeps its blocks.
59
+ def write_blocks(lba, bytes)
60
+ return unless (bytes.length % SECTOR_SIZE).zero? && blocks?(lba, bytes.length / SECTOR_SIZE)
61
+ raise WriteError, WriteError::WRITE_PROTECT_ON unless writable?
62
+
63
+ store(lba * SECTOR_SIZE, bytes)
64
+ @bytes[lba * SECTOR_SIZE, bytes.length] = bytes
65
+ @entries = nil
66
+ end
67
+
68
+ def bam_block?(track, sector) = block?(track, sector)
69
+
70
+ def block_free?(track, sector)
71
+ block?(track, sector) && @bytes[bitmap_byte(track, sector)][sector % 8] == 1
72
+ end
73
+
74
+ private
75
+
76
+ def error_tables = {}
77
+
78
+ def store(offset, bytes)
79
+ File.open(@path, "r+b") do |file|
80
+ file.seek(offset)
81
+ file.write(bytes.pack("C*"))
82
+ end
83
+ rescue SystemCallError
84
+ raise WriteError, WriteError::WRITE_PROTECT_ON
85
+ end
86
+
87
+ def block?(track, sector)
88
+ track.between?(0, CYLINDERS - 1) && sector.between?(0, sectors_in(track) - 1)
89
+ end
90
+
91
+ def track_offset(track) = track * sectors_in(track)
92
+
93
+ def clear_blocks
94
+ header = sector_offset(*header_block)
95
+ @bytes.fill(0, 0, header)
96
+ @bytes.fill(0, header + SECTOR_SIZE, (block_count * SECTOR_SIZE) - header - SECTOR_SIZE)
97
+ end
98
+
99
+ # The header at 76/20 holds the disk name at $06 and the ID at $18,
100
+ # after the link to the directory's first block, 76/10.
101
+ def header_name = 0x06
102
+ def directory_track = 76
103
+ def directory_sector = 10
104
+ def directory_interleave = 3
105
+ def interleave = 10
106
+ def reserved_tracks = [76]
107
+
108
+ # A link to track 0 ends a chain, so files never go there.
109
+ def allocation_order = super - [0]
110
+
111
+ def bam_tracks = 0..(CYLINDERS - 1)
112
+ def header_link = [76, 10]
113
+ def dos_type = "3A"
114
+ def header_tail = 4
115
+
116
+ # The header holds no format letter after its link.
117
+ def write_header(name, id)
118
+ super
119
+ write_at(*header_block, 2, [0])
120
+ end
121
+
122
+ # The BAM takes sector 0 of a cylinder for each group of cylinders
123
+ # its entries fit in, twelve on a D9060 and eight on a D9090: the
124
+ # cylinder after the group's first, or the last one for a group of
125
+ # one at the end.
126
+ def tracks_per_bam_block = (SECTOR_SIZE - 16) / (@heads * ENTRY)
127
+
128
+ def bam_block_track(track)
129
+ first = track - (track % tracks_per_bam_block)
130
+ [first + 1, CYLINDERS - 1].min
131
+ end
132
+
133
+ def entry_offset(track, sector)
134
+ group = track % tracks_per_bam_block
135
+ sector_offset(bam_block_track(track), 0) + 16 +
136
+ (((group * @heads) + (sector / SECTORS_PER_HEAD)) * ENTRY)
137
+ end
138
+
139
+ def bitmap_byte(track, sector) = entry_offset(track, sector) + 1 + ((sector % SECTORS_PER_HEAD) / 8)
140
+
141
+ def mark(track, sector, free)
142
+ return if !block?(track, sector) || block_free?(track, sector) == free
143
+
144
+ entry = entry_offset(track, sector)
145
+ byte = bitmap_byte(track, sector)
146
+ bit = 1 << (sector % 8)
147
+ @bytes[byte] = free ? @bytes[byte] | bit : @bytes[byte] & ~bit
148
+ @bytes[entry] = (@bytes[entry] + (free ? 1 : -1)) & 0xff
149
+ end
150
+
151
+ def free_track(track)
152
+ @heads.times do |head|
153
+ @bytes[entry_offset(track, head * SECTORS_PER_HEAD), ENTRY] = [SECTORS_PER_HEAD, 0xff, 0xff, 0xff, 0xff]
154
+ end
155
+ end
156
+
157
+ def blocks_free
158
+ (bam_tracks.to_a - reserved_tracks).sum do |track|
159
+ (0...@heads).sum { |head| @bytes[entry_offset(track, head * SECTORS_PER_HEAD)] }
160
+ end
161
+ end
162
+
163
+ # Block 0/0 points at the bad-block list in 0/1, the directory, the
164
+ # header and the BAM's first block, and holds the ID. Each BAM block
165
+ # links the next and the one before it, $FF $FF at either end, and
166
+ # names the first cylinder it covers and the one past its last.
167
+ def bam_headers(id)
168
+ groups = bam_tracks.each_slice(tracks_per_bam_block).to_a
169
+ blocks = groups.map { |tracks| bam_block_track(tracks.first) }
170
+ bam = groups.each_with_index.map do |tracks, i|
171
+ following = i == groups.length - 1 ? [0xff, 0xff] : [blocks[i + 1], 0]
172
+ previous = i.zero? ? [0xff, 0xff] : [blocks[i - 1], 0]
173
+ [blocks[i], 0, [*following, *previous, tracks.first, tracks.last + 1]]
174
+ end
175
+ [[0, 0, [0, 1, 0, 0xff, *header_link, *header_block, blocks.first, 0, *id]],
176
+ [0, 1, Array.new(SECTOR_SIZE, 0xff)], *bam]
177
+ end
178
+ end
179
+ end
180
+ end