badline 0.17.0 → 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 (166) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +153 -0
  3. data/CLAUDE.md +11 -7
  4. data/CONTRIBUTING.md +7 -1
  5. data/README.md +169 -20
  6. data/doc/pinned-behaviour.md +128 -0
  7. data/doc/snapshot-format.md +253 -18
  8. data/doc/snapshots.md +57 -23
  9. data/exe/badline-ruby +5 -0
  10. data/lib/badline/audio/terminal.rb +34 -2
  11. data/lib/badline/c128/keyed_state.rb +2 -0
  12. data/lib/badline/c128.rb +1 -0
  13. data/lib/badline/c64/keyed_state.rb +2 -0
  14. data/lib/badline/c64.rb +1 -0
  15. data/lib/badline/cbm2/keyed_state.rb +4 -0
  16. data/lib/badline/cbm2/p500.rb +5 -3
  17. data/lib/badline/cbm2.rb +6 -3
  18. data/lib/badline/chips/cia/timer.rb +2 -2
  19. data/lib/badline/chips/cpu/instruction_set.rb +1 -1
  20. data/lib/badline/chips/m6809/arithmetic.rb +140 -0
  21. data/lib/badline/chips/m6809/bus_steps.rb +223 -0
  22. data/lib/badline/chips/m6809/core.rb +149 -0
  23. data/lib/badline/chips/m6809/indexed.rb +82 -0
  24. data/lib/badline/chips/m6809/interrupts.rb +159 -0
  25. data/lib/badline/chips/m6809/keyed_state.rb +147 -0
  26. data/lib/badline/chips/m6809/opcodes.rb +159 -0
  27. data/lib/badline/chips/m6809/operations.rb +258 -0
  28. data/lib/badline/chips/m6809/plans.rb +143 -0
  29. data/lib/badline/chips/m6809/registers.rb +82 -0
  30. data/lib/badline/chips/m6809/stack.rb +140 -0
  31. data/lib/badline/chips/m6809.rb +13 -0
  32. data/lib/badline/chips/riot/timer.rb +14 -3
  33. data/lib/badline/chips/rriot.rb +4 -2
  34. data/lib/badline/chips/sid/filter.rb +4 -4
  35. data/lib/badline/chips/sid/waveform.rb +6 -6
  36. data/lib/badline/chips/vic_i/keyed_parts.rb +7 -2
  37. data/lib/badline/chips/vic_i.rb +84 -21
  38. data/lib/badline/data_path.rb +3 -3
  39. data/lib/badline/drives/ieee_models.rb +54 -0
  40. data/lib/badline/drives/model1001.rb +37 -0
  41. data/lib/badline/drives/model2031.rb +6 -0
  42. data/lib/badline/drives/model4040/disk.rb +62 -0
  43. data/lib/badline/drives/model4040/ip_bus.rb +46 -0
  44. data/lib/badline/drives/model4040/keyed_state.rb +57 -0
  45. data/lib/badline/drives/model4040/mechanism.rb +192 -0
  46. data/lib/badline/drives/model4040/parts.rb +20 -0
  47. data/lib/badline/drives/model4040.rb +179 -0
  48. data/lib/badline/drives/model8050/board.rb +77 -0
  49. data/lib/badline/drives/model8050/dos_board.rb +91 -0
  50. data/lib/badline/drives/model8050/idle.rb +11 -7
  51. data/lib/badline/drives/model8050/keyed_state.rb +5 -2
  52. data/lib/badline/drives/model8050.rb +18 -130
  53. data/lib/badline/drives/model9060/idle.rb +149 -0
  54. data/lib/badline/drives/model9060/keyed_state.rb +123 -0
  55. data/lib/badline/drives/model9060/parts.rb +98 -0
  56. data/lib/badline/drives/model9060/sasi_bus.rb +88 -0
  57. data/lib/badline/drives/model9060/sasi_commands.rb +168 -0
  58. data/lib/badline/drives/model9060/sasi_target.rb +161 -0
  59. data/lib/badline/drives/model9060.rb +195 -0
  60. data/lib/badline/frontend/app.rb +8 -0
  61. data/lib/badline/frontend/boot.rb +3 -2
  62. data/lib/badline/frontend/controls.rb +62 -6
  63. data/lib/badline/frontend/keys.rb +36 -0
  64. data/lib/badline/frontend/kim1_panel.rb +208 -0
  65. data/lib/badline/frontend/media_slots.rb +8 -1
  66. data/lib/badline/frontend/menu/drive_types.rb +56 -0
  67. data/lib/badline/frontend/menu/menu_dialogs.rb +61 -14
  68. data/lib/badline/frontend/menu/menu_media.rb +42 -12
  69. data/lib/badline/frontend/menu/menu_pages.rb +97 -2
  70. data/lib/badline/frontend/menu/menu_ports.rb +38 -1
  71. data/lib/badline/frontend/menu/pause_menu.rb +5 -1
  72. data/lib/badline/frontend/pet_keyboard.rb +23 -1
  73. data/lib/badline/frontend/screen.rb +1 -1
  74. data/lib/badline/frontend/screens.rb +40 -2
  75. data/lib/badline/frontend/teletype_pane.rb +152 -0
  76. data/lib/badline/frontend/timeline.rb +31 -2
  77. data/lib/badline/frontend.rb +3 -0
  78. data/lib/badline/kim1/bus.rb +117 -0
  79. data/lib/badline/kim1/cpu.rb +25 -0
  80. data/lib/badline/kim1/host_terminal.rb +74 -0
  81. data/lib/badline/kim1/key_entry.rb +74 -0
  82. data/lib/badline/kim1/keyed_modules.rb +53 -0
  83. data/lib/badline/kim1/keyed_state.rb +133 -0
  84. data/lib/badline/kim1/panel.rb +176 -0
  85. data/lib/badline/kim1/rriots.rb +28 -0
  86. data/lib/badline/kim1/sound.rb +29 -0
  87. data/lib/badline/kim1/tape.rb +116 -0
  88. data/lib/badline/kim1/teletype.rb +193 -0
  89. data/lib/badline/kim1.rb +190 -0
  90. data/lib/badline/machine/ieee_drives.rb +94 -117
  91. data/lib/badline/machine/keyboard_buffer.rb +20 -3
  92. data/lib/badline/machine/line_sound.rb +122 -0
  93. data/lib/badline/machine.rb +46 -15
  94. data/lib/badline/media/extensions.rb +8 -4
  95. data/lib/badline/media/kim1_media.rb +150 -0
  96. data/lib/badline/media/pet_media.rb +3 -1
  97. data/lib/badline/media/true_drive.rb +42 -45
  98. data/lib/badline/media.rb +12 -6
  99. data/lib/badline/options/family.rb +98 -8
  100. data/lib/badline/options/help.rb +44 -12
  101. data/lib/badline/options/table.rb +43 -11
  102. data/lib/badline/options/validation.rb +44 -8
  103. data/lib/badline/options.rb +27 -8
  104. data/lib/badline/pet/bus.rb +16 -2
  105. data/lib/badline/pet/keyboard_ports.rb +9 -2
  106. data/lib/badline/pet/keyed_state.rb +4 -0
  107. data/lib/badline/pet/model.rb +11 -2
  108. data/lib/badline/pet/rom_sets.rb +1 -0
  109. data/lib/badline/pet/sound.rb +5 -105
  110. data/lib/badline/pet/superpet/acia.rb +12 -0
  111. data/lib/badline/pet/superpet/board.rb +221 -0
  112. data/lib/badline/pet/superpet/dongle.rb +81 -0
  113. data/lib/badline/pet/superpet/keyed_modules.rb +63 -0
  114. data/lib/badline/pet/superpet/keyed_parts.rb +76 -0
  115. data/lib/badline/pet/superpet/m6809.rb +32 -0
  116. data/lib/badline/pet/superpet/waterloo_traps.rb +199 -0
  117. data/lib/badline/pet/superpet.rb +216 -0
  118. data/lib/badline/pet.rb +19 -3
  119. data/lib/badline/plus4/keyed_state.rb +2 -0
  120. data/lib/badline/plus4.rb +1 -0
  121. data/lib/badline/roms/1001/fdc-251257-02a.rom +0 -0
  122. data/lib/badline/roms/4040/dos-1.2.rom +0 -0
  123. data/lib/badline/roms/4040/dos-2.1-rev.rom +0 -0
  124. data/lib/badline/roms/4040/dos-2.1.rom +0 -0
  125. data/lib/badline/roms/4040/fdc-901466-02.rom +0 -0
  126. data/lib/badline/roms/4040/fdc-901466-04.rom +0 -0
  127. data/lib/badline/roms/9060/dos-3.0.rom +0 -0
  128. data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
  129. data/lib/badline/roms/README +166 -0
  130. data/lib/badline/roms/kim1/6530-002.rom +0 -0
  131. data/lib/badline/roms/kim1/6530-003.rom +0 -0
  132. data/lib/badline/roms/pet/characters-superpet.rom +0 -0
  133. data/lib/badline/roms/pet/superpet-a000.rom +0 -0
  134. data/lib/badline/roms/pet/superpet-c000.rom +0 -0
  135. data/lib/badline/roms/pet/superpet-e000-50hz.rom +0 -0
  136. data/lib/badline/roms/pet/superpet-e000.rom +0 -0
  137. data/lib/badline/snapshot/container.rb +2 -0
  138. data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
  139. data/lib/badline/snapshot/field_doc.rb +27 -6
  140. data/lib/badline/snapshot/image.rb +4 -1
  141. data/lib/badline/snapshot/kim1_setup.rb +28 -0
  142. data/lib/badline/snapshot/pet_setup.rb +27 -11
  143. data/lib/badline/snapshot/storage_setup.rb +4 -1
  144. data/lib/badline/snapshot/vic20_setup.rb +11 -7
  145. data/lib/badline/snapshot/vice/pet.rb +13 -7
  146. data/lib/badline/snapshot/vice/pet_cpu.rb +39 -5
  147. data/lib/badline/snapshot/vice/pet_crtc.rb +15 -2
  148. data/lib/badline/snapshot/vice/pet_drive8050.rb +188 -68
  149. data/lib/badline/snapshot/vice/pet_mem.rb +49 -5
  150. data/lib/badline/snapshot/vice/pet_restore.rb +40 -11
  151. data/lib/badline/snapshot.rb +4 -0
  152. data/lib/badline/status.rb +30 -30
  153. data/lib/badline/storage/d67_image.rb +55 -0
  154. data/lib/badline/storage/d90_image.rb +180 -0
  155. data/lib/badline/storage/disk_image/writing.rb +2 -2
  156. data/lib/badline/storage/kim_tape.rb +254 -0
  157. data/lib/badline/storage/papertape.rb +90 -0
  158. data/lib/badline/storage.rb +4 -0
  159. data/lib/badline/support/kim1_checkpoint.rb +41 -0
  160. data/lib/badline/support/superpet_checkpoint.rb +35 -0
  161. data/lib/badline/version.rb +1 -1
  162. data/lib/badline/vic20/keyed_modules.rb +6 -3
  163. data/lib/badline/vic20/keyed_state.rb +6 -4
  164. data/lib/badline/vic20.rb +26 -12
  165. data/lib/badline.rb +1 -0
  166. metadata +77 -1
@@ -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
@@ -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
@@ -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
@@ -151,8 +151,8 @@ module Badline
151
151
  end
152
152
 
153
153
  def put_sector(track, sector, data)
154
- block = data.first(SECTOR_SIZE)
155
- @bytes[sector_offset(track, sector), SECTOR_SIZE] = block + Array.new(SECTOR_SIZE - block.length, 0)
154
+ offset = sector_offset(track, sector)
155
+ SECTOR_SIZE.times { |i| @bytes[offset + i] = data[i] || 0 }
156
156
  @errors[track_offset(track) + sector] = 1 if @errors
157
157
  end
158
158
  end