ac6502 1.3.0 → 1.4.1

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 (141) hide show
  1. package/README.md +139 -32
  2. package/dist/components/IO/ACIA.d.ts +76 -0
  3. package/dist/components/IO/ACIA.js +282 -0
  4. package/dist/components/IO/ACIA.js.map +1 -0
  5. package/dist/components/IO/Attachments/Attachment.d.ts +112 -0
  6. package/dist/components/IO/Attachments/Attachment.js +71 -0
  7. package/dist/components/IO/Attachments/Attachment.js.map +1 -0
  8. package/dist/components/IO/Attachments/JoystickAttachment.d.ts +53 -0
  9. package/dist/components/IO/Attachments/JoystickAttachment.js +90 -0
  10. package/dist/components/IO/Attachments/JoystickAttachment.js.map +1 -0
  11. package/dist/components/IO/Attachments/KeyboardEncoderAttachment.d.ts +63 -0
  12. package/dist/components/IO/Attachments/KeyboardEncoderAttachment.js +489 -0
  13. package/dist/components/IO/Attachments/KeyboardEncoderAttachment.js.map +1 -0
  14. package/dist/components/IO/Attachments/KeyboardMatrixAttachment.d.ts +44 -0
  15. package/dist/components/IO/Attachments/KeyboardMatrixAttachment.js +274 -0
  16. package/dist/components/IO/Attachments/KeyboardMatrixAttachment.js.map +1 -0
  17. package/dist/components/IO/Attachments/KeypadAttachment.d.ts +47 -0
  18. package/dist/components/IO/Attachments/KeypadAttachment.js +141 -0
  19. package/dist/components/IO/Attachments/KeypadAttachment.js.map +1 -0
  20. package/dist/components/IO/Attachments/LCDAttachment.d.ts +110 -0
  21. package/dist/components/IO/Attachments/LCDAttachment.js +716 -0
  22. package/dist/components/IO/Attachments/LCDAttachment.js.map +1 -0
  23. package/dist/components/IO/Attachments/SNESAttachment.d.ts +85 -0
  24. package/dist/components/IO/Attachments/SNESAttachment.js +184 -0
  25. package/dist/components/IO/Attachments/SNESAttachment.js.map +1 -0
  26. package/dist/components/IO/Empty.d.ts +9 -0
  27. package/dist/components/IO/Empty.js +5 -7
  28. package/dist/components/IO/Empty.js.map +1 -1
  29. package/dist/components/IO/GPIOCard.d.ts +5 -5
  30. package/dist/components/IO/GPIOCard.js.map +1 -1
  31. package/dist/components/IO/RAMBank.d.ts +37 -0
  32. package/dist/components/IO/RAMBank.js +63 -0
  33. package/dist/components/IO/RAMBank.js.map +1 -0
  34. package/dist/components/IO/RTC.d.ts +107 -0
  35. package/dist/components/IO/RTC.js +483 -0
  36. package/dist/components/IO/RTC.js.map +1 -0
  37. package/dist/components/IO/Sound.d.ts +120 -0
  38. package/dist/components/IO/Sound.js +622 -0
  39. package/dist/components/IO/Sound.js.map +1 -0
  40. package/dist/components/IO/Storage.d.ts +74 -0
  41. package/dist/components/IO/Storage.js +409 -0
  42. package/dist/components/IO/Storage.js.map +1 -0
  43. package/dist/components/IO/Terminal.d.ts +19 -0
  44. package/dist/components/IO/Terminal.js +33 -0
  45. package/dist/components/IO/Terminal.js.map +1 -0
  46. package/dist/components/IO/VIA.d.ts +105 -0
  47. package/dist/components/IO/VIA.js +597 -0
  48. package/dist/components/IO/VIA.js.map +1 -0
  49. package/dist/components/IO/Video.d.ts +141 -0
  50. package/dist/components/IO/Video.js +630 -0
  51. package/dist/components/IO/Video.js.map +1 -0
  52. package/dist/components/Machine.d.ts +20 -24
  53. package/dist/components/Machine.js +249 -166
  54. package/dist/components/Machine.js.map +1 -1
  55. package/dist/index.js +28 -14
  56. package/dist/index.js.map +1 -1
  57. package/dist/lib.d.ts +16 -16
  58. package/dist/lib.js +32 -32
  59. package/dist/lib.js.map +1 -1
  60. package/dist/tests/IO/ACIA.test.d.ts +1 -0
  61. package/dist/tests/IO/ACIA.test.js +423 -0
  62. package/dist/tests/IO/ACIA.test.js.map +1 -0
  63. package/dist/tests/IO/Attachments/Attachment.test.d.ts +1 -0
  64. package/dist/tests/IO/Attachments/Attachment.test.js +339 -0
  65. package/dist/tests/IO/Attachments/Attachment.test.js.map +1 -0
  66. package/dist/tests/IO/Attachments/JoystickAttachment.test.d.ts +1 -0
  67. package/dist/tests/IO/Attachments/JoystickAttachment.test.js +126 -0
  68. package/dist/tests/IO/Attachments/JoystickAttachment.test.js.map +1 -0
  69. package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.d.ts +1 -0
  70. package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.js +779 -0
  71. package/dist/tests/IO/Attachments/KeyboardEncoderAttachment.test.js.map +1 -0
  72. package/dist/tests/IO/Attachments/KeyboardMatrixAttachment.test.d.ts +1 -0
  73. package/dist/tests/IO/Attachments/KeyboardMatrixAttachment.test.js +355 -0
  74. package/dist/tests/IO/Attachments/KeyboardMatrixAttachment.test.js.map +1 -0
  75. package/dist/tests/IO/Attachments/KeypadAttachment.test.d.ts +1 -0
  76. package/dist/tests/IO/Attachments/KeypadAttachment.test.js +323 -0
  77. package/dist/tests/IO/Attachments/KeypadAttachment.test.js.map +1 -0
  78. package/dist/tests/IO/Attachments/LCDAttachment.test.d.ts +1 -0
  79. package/dist/tests/IO/Attachments/LCDAttachment.test.js +627 -0
  80. package/dist/tests/IO/Attachments/LCDAttachment.test.js.map +1 -0
  81. package/dist/tests/IO/Attachments/SNESAttachment.test.d.ts +1 -0
  82. package/dist/tests/IO/Attachments/SNESAttachment.test.js +331 -0
  83. package/dist/tests/IO/Attachments/SNESAttachment.test.js.map +1 -0
  84. package/dist/tests/IO/Empty.test.d.ts +1 -0
  85. package/dist/tests/IO/Empty.test.js +121 -0
  86. package/dist/tests/IO/Empty.test.js.map +1 -0
  87. package/dist/tests/IO/GPIOCard.test.js.map +1 -1
  88. package/dist/tests/IO/RAMBank.test.d.ts +1 -0
  89. package/dist/tests/IO/RAMBank.test.js +229 -0
  90. package/dist/tests/IO/RAMBank.test.js.map +1 -0
  91. package/dist/tests/IO/RTC.test.d.ts +1 -0
  92. package/dist/tests/IO/RTC.test.js +177 -0
  93. package/dist/tests/IO/RTC.test.js.map +1 -0
  94. package/dist/tests/IO/Sound.test.d.ts +1 -0
  95. package/dist/tests/IO/Sound.test.js +528 -0
  96. package/dist/tests/IO/Sound.test.js.map +1 -0
  97. package/dist/tests/IO/Storage.test.d.ts +1 -0
  98. package/dist/tests/IO/Storage.test.js +656 -0
  99. package/dist/tests/IO/Storage.test.js.map +1 -0
  100. package/dist/tests/IO/VIA.test.d.ts +1 -0
  101. package/dist/tests/IO/VIA.test.js +503 -0
  102. package/dist/tests/IO/VIA.test.js.map +1 -0
  103. package/dist/tests/IO/Video.test.d.ts +1 -0
  104. package/dist/tests/IO/Video.test.js +549 -0
  105. package/dist/tests/IO/Video.test.js.map +1 -0
  106. package/dist/tests/Machine.test.js +27 -42
  107. package/dist/tests/Machine.test.js.map +1 -1
  108. package/package.json +1 -1
  109. package/src/components/IO/{SerialCard.ts → ACIA.ts} +2 -2
  110. package/src/components/IO/{GPIOAttachments/GPIOAttachment.ts → Attachments/Attachment.ts} +2 -2
  111. package/src/components/IO/{GPIOAttachments/GPIOJoystickAttachment.ts → Attachments/JoystickAttachment.ts} +3 -3
  112. package/src/components/IO/{GPIOAttachments/GPIOKeyboardEncoderAttachment.ts → Attachments/KeyboardEncoderAttachment.ts} +3 -3
  113. package/src/components/IO/{GPIOAttachments/GPIOKeyboardMatrixAttachment.ts → Attachments/KeyboardMatrixAttachment.ts} +5 -5
  114. package/src/components/IO/{GPIOAttachments/GPIOKeypadAttachment.ts → Attachments/KeypadAttachment.ts} +3 -3
  115. package/src/components/IO/{GPIOAttachments/GPIOLCDAttachment.ts → Attachments/LCDAttachment.ts} +7 -7
  116. package/src/components/IO/{EmptyCard.ts → Empty.ts} +1 -1
  117. package/src/components/IO/{RAMCard.ts → RAMBank.ts} +8 -8
  118. package/src/components/IO/{RTCCard.ts → RTC.ts} +1 -1
  119. package/src/components/IO/{SoundCard.ts → Sound.ts} +2 -2
  120. package/src/components/IO/{StorageCard.ts → Storage.ts} +70 -73
  121. package/src/components/IO/{DevOutputBoard.ts → Terminal.ts} +2 -2
  122. package/src/components/IO/{GPIOCard.ts → VIA.ts} +64 -64
  123. package/src/components/IO/{VideoCard.ts → Video.ts} +1 -1
  124. package/src/components/Machine.ts +276 -176
  125. package/src/index.ts +34 -21
  126. package/src/lib.ts +16 -16
  127. package/src/tests/IO/{SerialCard.test.ts → ACIA.test.ts} +5 -5
  128. package/src/tests/IO/{GPIOAttachments/GPIOAttachment.test.ts → Attachments/Attachment.test.ts} +12 -12
  129. package/src/tests/IO/{GPIOAttachments/GPIOJoystickAttachment.test.ts → Attachments/JoystickAttachment.test.ts} +23 -23
  130. package/src/tests/IO/{GPIOAttachments/GPIOKeyboardEncoderAttachment.test.ts → Attachments/KeyboardEncoderAttachment.test.ts} +4 -4
  131. package/src/tests/IO/{GPIOAttachments/GPIOKeyboardMatrixAttachment.test.ts → Attachments/KeyboardMatrixAttachment.test.ts} +5 -5
  132. package/src/tests/IO/{GPIOAttachments/GPIOKeypadAttachment.test.ts → Attachments/KeypadAttachment.test.ts} +38 -38
  133. package/src/tests/IO/{GPIOAttachments/GPIOLCDAttachment.test.ts → Attachments/LCDAttachment.test.ts} +12 -12
  134. package/src/tests/IO/Empty.test.ts +143 -0
  135. package/src/tests/IO/{RAMCard.test.ts → RAMBank.test.ts} +33 -33
  136. package/src/tests/IO/{RTCCard.test.ts → RTC.test.ts} +6 -6
  137. package/src/tests/IO/{SoundCard.test.ts → Sound.test.ts} +6 -6
  138. package/src/tests/IO/{StorageCard.test.ts → Storage.test.ts} +34 -25
  139. package/src/tests/IO/{GPIOCard.test.ts → VIA.test.ts} +7 -7
  140. package/src/tests/IO/{VideoCard.test.ts → Video.test.ts} +13 -13
  141. package/src/tests/Machine.test.ts +31 -38
@@ -1,9 +1,7 @@
1
1
  import { IO } from '../IO'
2
- import { readFile, writeFile } from 'fs/promises'
3
- import { existsSync } from 'fs'
4
2
 
5
3
  /**
6
- * StorageCard - Emulates a Compact Flash card in 8-bit IDE mode
4
+ * Storage - Emulates a Compact Flash card in 8-bit IDE mode
7
5
  *
8
6
  * Emulates a 128MB CF card with ATA-style register interface.
9
7
  * Uses LBA (Logical Block Addressing) for sector access.
@@ -25,12 +23,12 @@ import { existsSync } from 'fs'
25
23
  * 0xEC: Identify Drive
26
24
  * 0xEF: Set Features (accepted but not implemented)
27
25
  */
28
- export class StorageCard implements IO {
26
+ export class Storage implements IO {
29
27
 
30
28
  // Constants
31
29
  private static readonly STORAGE_SIZE = 128 * 1024 * 1024 // 128MB
32
30
  private static readonly SECTOR_SIZE = 512
33
- private static readonly SECTOR_COUNT = StorageCard.STORAGE_SIZE / StorageCard.SECTOR_SIZE // 262144 sectors
31
+ private static readonly SECTOR_COUNT = Storage.STORAGE_SIZE / Storage.SECTOR_SIZE // 262144 sectors
34
32
 
35
33
  // Status Register Flags
36
34
  private static readonly STATUS_ERR = 0x01 // Error
@@ -43,13 +41,13 @@ export class StorageCard implements IO {
43
41
  private static readonly ERR_IDNF = 0x10 // ID Not Found
44
42
 
45
43
  // Storage and Identity data (in-memory simulation)
46
- private storage: Buffer
47
- private identity: Buffer
44
+ private storage: Uint8Array
45
+ private identity: Uint8Array
48
46
 
49
47
  // Data buffer (512 bytes)
50
- private buffer: Buffer = Buffer.alloc(StorageCard.SECTOR_SIZE)
48
+ private buffer: Uint8Array = new Uint8Array(Storage.SECTOR_SIZE)
51
49
  private bufferIndex: number = 0
52
- private commandDataSize: number = StorageCard.SECTOR_SIZE
50
+ private commandDataSize: number = Storage.SECTOR_SIZE
53
51
  private sectorOffset: number = 0
54
52
 
55
53
  // Registers
@@ -72,8 +70,8 @@ export class StorageCard implements IO {
72
70
 
73
71
  constructor() {
74
72
  // Initialize storage and identity buffers
75
- this.storage = Buffer.alloc(StorageCard.STORAGE_SIZE, 0x00)
76
- this.identity = Buffer.alloc(StorageCard.SECTOR_SIZE)
73
+ this.storage = new Uint8Array(Storage.STORAGE_SIZE)
74
+ this.identity = new Uint8Array(Storage.SECTOR_SIZE)
77
75
  this.generateIdentity()
78
76
  this.reset(true)
79
77
  }
@@ -137,7 +135,7 @@ export class StorageCard implements IO {
137
135
 
138
136
  reset(coldStart: boolean): void {
139
137
  this.bufferIndex = 0x0000
140
- this.commandDataSize = StorageCard.SECTOR_SIZE
138
+ this.commandDataSize = Storage.SECTOR_SIZE
141
139
  this.sectorOffset = 0
142
140
 
143
141
  this.error = 0x00
@@ -147,7 +145,7 @@ export class StorageCard implements IO {
147
145
  this.lba1 = 0x00
148
146
  this.lba2 = 0x00
149
147
  this.lba3 = 0xE0
150
- this.status = 0x00 | StorageCard.STATUS_RDY
148
+ this.status = 0x00 | Storage.STATUS_RDY
151
149
  this.command = 0x00
152
150
 
153
151
  this.isIdentifying = false
@@ -162,36 +160,36 @@ export class StorageCard implements IO {
162
160
 
163
161
  private executeCommand(): void {
164
162
  // New command so clear errors and flags
165
- this.status &= ~StorageCard.STATUS_ERR
166
- this.status &= ~StorageCard.STATUS_DRQ
163
+ this.status &= ~Storage.STATUS_ERR
164
+ this.status &= ~Storage.STATUS_DRQ
167
165
  this.error = 0x00
168
- this.commandDataSize = StorageCard.SECTOR_SIZE * this.sectorCount
166
+ this.commandDataSize = Storage.SECTOR_SIZE * this.sectorCount
169
167
  this.bufferIndex = 0
170
168
  this.sectorOffset = 0
171
169
 
172
170
  // Check if already executing a command
173
171
  if (this.isTransferring || this.isIdentifying) {
174
- this.status |= StorageCard.STATUS_ERR
175
- this.error |= StorageCard.ERR_ABRT
172
+ this.status |= Storage.STATUS_ERR
173
+ this.error |= Storage.ERR_ABRT
176
174
  return
177
175
  }
178
176
 
179
177
  switch (this.command) {
180
178
  case 0xC0: { // Erase sector
181
179
  if (!this.sectorValid()) {
182
- this.status |= StorageCard.STATUS_ERR
183
- this.error |= StorageCard.ERR_ABRT | StorageCard.ERR_IDNF
180
+ this.status |= Storage.STATUS_ERR
181
+ this.error |= Storage.ERR_ABRT | Storage.ERR_IDNF
184
182
  } else {
185
- const offset = this.sectorIndex() * StorageCard.SECTOR_SIZE
186
- this.storage.fill(0x00, offset, offset + StorageCard.SECTOR_SIZE)
183
+ const offset = this.sectorIndex() * Storage.SECTOR_SIZE
184
+ this.storage.fill(0x00, offset, offset + Storage.SECTOR_SIZE)
187
185
  }
188
186
  break
189
187
  }
190
188
 
191
189
  case 0xEC: { // Identify drive
192
- this.identity.copy(this.buffer, 0, 0, StorageCard.SECTOR_SIZE)
193
- this.commandDataSize = StorageCard.SECTOR_SIZE
194
- this.status |= StorageCard.STATUS_DRQ
190
+ this.buffer.set(this.identity.subarray(0, Storage.SECTOR_SIZE))
191
+ this.commandDataSize = Storage.SECTOR_SIZE
192
+ this.status |= Storage.STATUS_DRQ
195
193
  this.isIdentifying = true
196
194
  break
197
195
  }
@@ -199,13 +197,13 @@ export class StorageCard implements IO {
199
197
  case 0x20: // Read sector
200
198
  case 0x21:
201
199
  if (!this.sectorValid()) {
202
- this.status |= StorageCard.STATUS_ERR
203
- this.error |= StorageCard.ERR_ABRT | StorageCard.ERR_IDNF
200
+ this.status |= Storage.STATUS_ERR
201
+ this.error |= Storage.ERR_ABRT | Storage.ERR_IDNF
204
202
  } else {
205
203
  // Load first sector into buffer
206
- const offset = this.sectorIndex() * StorageCard.SECTOR_SIZE
207
- this.storage.copy(this.buffer, 0, offset, offset + StorageCard.SECTOR_SIZE)
208
- this.status |= StorageCard.STATUS_DRQ
204
+ const offset = this.sectorIndex() * Storage.SECTOR_SIZE
205
+ this.buffer.set(this.storage.subarray(offset, offset + Storage.SECTOR_SIZE))
206
+ this.status |= Storage.STATUS_DRQ
209
207
  this.isTransferring = true
210
208
  }
211
209
  break
@@ -217,18 +215,18 @@ export class StorageCard implements IO {
217
215
  case 0x30: // Write sector
218
216
  case 0x31:
219
217
  if (!this.sectorValid()) {
220
- this.status |= StorageCard.STATUS_ERR
221
- this.error |= StorageCard.ERR_ABRT | StorageCard.ERR_IDNF
218
+ this.status |= Storage.STATUS_ERR
219
+ this.error |= Storage.ERR_ABRT | Storage.ERR_IDNF
222
220
  } else {
223
- this.status |= StorageCard.STATUS_DRQ
221
+ this.status |= Storage.STATUS_DRQ
224
222
  this.isTransferring = true
225
223
  }
226
224
  break
227
225
 
228
226
  default:
229
227
  // Unsupported command
230
- this.status |= StorageCard.STATUS_ERR
231
- this.error |= StorageCard.ERR_ABRT
228
+ this.status |= Storage.STATUS_ERR
229
+ this.error |= Storage.ERR_ABRT
232
230
  break
233
231
  }
234
232
  }
@@ -242,14 +240,14 @@ export class StorageCard implements IO {
242
240
  } else {
243
241
  this.bufferIndex = 0
244
242
  this.isIdentifying = false
245
- this.status &= ~StorageCard.STATUS_DRQ
243
+ this.status &= ~Storage.STATUS_DRQ
246
244
  }
247
245
 
248
246
  return data
249
247
  } else if (this.isTransferring) {
250
248
  const data = this.buffer[this.bufferIndex]
251
249
 
252
- if (this.bufferIndex < StorageCard.SECTOR_SIZE - 1) {
250
+ if (this.bufferIndex < Storage.SECTOR_SIZE - 1) {
253
251
  this.bufferIndex++
254
252
  } else {
255
253
  this.bufferIndex = 0
@@ -257,11 +255,11 @@ export class StorageCard implements IO {
257
255
 
258
256
  if (this.sectorOffset < this.sectorCount) {
259
257
  // Load the next sector
260
- const offset = (this.sectorIndex() + this.sectorOffset) * StorageCard.SECTOR_SIZE
261
- this.storage.copy(this.buffer, 0, offset, offset + StorageCard.SECTOR_SIZE)
258
+ const offset = (this.sectorIndex() + this.sectorOffset) * Storage.SECTOR_SIZE
259
+ this.buffer.set(this.storage.subarray(offset, offset + Storage.SECTOR_SIZE))
262
260
  } else {
263
261
  this.isTransferring = false
264
- this.status &= ~StorageCard.STATUS_DRQ
262
+ this.status &= ~Storage.STATUS_DRQ
265
263
  }
266
264
  }
267
265
 
@@ -274,21 +272,21 @@ export class StorageCard implements IO {
274
272
  private writeBuffer(value: number): void {
275
273
  this.buffer[this.bufferIndex] = value
276
274
 
277
- if (this.bufferIndex < StorageCard.SECTOR_SIZE - 1) {
275
+ if (this.bufferIndex < Storage.SECTOR_SIZE - 1) {
278
276
  this.bufferIndex++
279
277
  } else {
280
278
  this.bufferIndex = 0
281
279
 
282
280
  // Write the current sector to storage
283
- const offset = (this.sectorIndex() + this.sectorOffset) * StorageCard.SECTOR_SIZE
284
- this.buffer.copy(this.storage, offset, 0, StorageCard.SECTOR_SIZE)
281
+ const offset = (this.sectorIndex() + this.sectorOffset) * Storage.SECTOR_SIZE
282
+ this.storage.set(this.buffer.subarray(0, Storage.SECTOR_SIZE), offset)
285
283
 
286
284
  this.sectorOffset++
287
285
 
288
286
  // Check if all sectors have been written
289
287
  if (this.sectorOffset >= this.sectorCount) {
290
288
  this.isTransferring = false
291
- this.status &= ~StorageCard.STATUS_DRQ
289
+ this.status &= ~Storage.STATUS_DRQ
292
290
  }
293
291
  }
294
292
  }
@@ -298,7 +296,7 @@ export class StorageCard implements IO {
298
296
  }
299
297
 
300
298
  private sectorValid(): boolean {
301
- return this.sectorIndex() < StorageCard.SECTOR_COUNT
299
+ return this.sectorIndex() < Storage.SECTOR_COUNT
302
300
  }
303
301
 
304
302
  private generateIdentity(): void {
@@ -434,40 +432,39 @@ export class StorageCard implements IO {
434
432
  }
435
433
 
436
434
  /**
437
- * Load storage data from a file
438
- * If the file doesn't exist, storage remains empty (initialized to 0x00)
435
+ * Load storage data from a Uint8Array, ArrayBuffer, or number array
436
+ * If data is not provided or wrong size, storage remains empty
439
437
  */
440
- async loadFromFile(filePath: string): Promise<void> {
441
- try {
442
- if (existsSync(filePath)) {
443
- const data = await readFile(filePath)
444
- // Ensure the file is exactly the expected size
445
- if (data.length === StorageCard.STORAGE_SIZE) {
446
- data.copy(this.storage, 0, 0, StorageCard.STORAGE_SIZE)
447
- console.log(`Storage loaded from: ${filePath}`)
448
- } else {
449
- console.warn(`Warning: Storage file size mismatch. Expected ${StorageCard.STORAGE_SIZE} bytes, got ${data.length} bytes.`)
450
- console.warn('Storage will remain empty.')
451
- }
452
- } else {
453
- console.log(`Storage file not found: ${filePath}`)
454
- console.log('A new storage file will be created on exit.')
455
- }
456
- } catch (error) {
457
- console.error('Error loading storage file:', error)
438
+ loadData(data: Uint8Array | ArrayBuffer | number[] | null): void {
439
+ if (!data) {
440
+ console.log('No storage data provided. Storage will remain empty.')
441
+ return
442
+ }
443
+
444
+ let uint8Data: Uint8Array
445
+ if (data instanceof ArrayBuffer) {
446
+ uint8Data = new Uint8Array(data)
447
+ } else if (data instanceof Uint8Array) {
448
+ uint8Data = data
449
+ } else {
450
+ uint8Data = new Uint8Array(data)
451
+ }
452
+
453
+ // Ensure the data is exactly the expected size
454
+ if (uint8Data.length === Storage.STORAGE_SIZE) {
455
+ this.storage.set(uint8Data)
456
+ console.log(`Storage loaded (${Storage.STORAGE_SIZE} bytes)`)
457
+ } else {
458
+ console.warn(`Warning: Storage data size mismatch. Expected ${Storage.STORAGE_SIZE} bytes, got ${uint8Data.length} bytes.`)
459
+ console.warn('Storage will remain empty.')
458
460
  }
459
461
  }
460
462
 
461
463
  /**
462
- * Save storage data to a file
464
+ * Get storage data as Uint8Array for saving
463
465
  */
464
- async saveToFile(filePath: string): Promise<void> {
465
- try {
466
- await writeFile(filePath, this.storage)
467
- console.log(`Storage saved to: ${filePath}`)
468
- } catch (error) {
469
- console.error('Error saving storage file:', error)
470
- }
466
+ getData(): Uint8Array {
467
+ return new Uint8Array(this.storage)
471
468
  }
472
469
 
473
470
  }
@@ -2,14 +2,14 @@ import { IO } from '../IO'
2
2
  import { VTAC } from 'vtac-terminal'
3
3
 
4
4
  /**
5
- * DevOutputBoard - Development output board using a VTAC terminal controller
5
+ * Terminal - Emulates the VTAC fantasy terminal
6
6
  *
7
7
  * Register Map:
8
8
  * $00: Data / Status Register
9
9
  * Write: sends byte to VTAC for processing
10
10
  * Read: always returns 0 (bit 7 is a busy flag on the real device; busy is never set here)
11
11
  */
12
- export class DevOutputBoard implements IO {
12
+ export class Terminal implements IO {
13
13
 
14
14
  raiseIRQ = () => {}
15
15
  raiseNMI = () => {}
@@ -1,8 +1,8 @@
1
1
  import { IO } from '../IO'
2
- import { GPIOAttachment } from './GPIOAttachments/GPIOAttachment'
2
+ import { Attachment } from './Attachments/Attachment'
3
3
 
4
4
  /**
5
- * GPIOCard - Emulates the 65C22 VIA (Versatile Interface Adapter)
5
+ * VIA - Emulates the 65C22 VIA (Versatile Interface Adapter)
6
6
  *
7
7
  * The 65C22 VIA provides:
8
8
  * - Two 8-bit bidirectional I/O ports (Port A and Port B)
@@ -10,7 +10,7 @@ import { GPIOAttachment } from './GPIOAttachments/GPIOAttachment'
10
10
  * - Shift register for serial I/O
11
11
  * - Handshaking lines for data transfer
12
12
  */
13
- export class GPIOCard implements IO {
13
+ export class VIA implements IO {
14
14
  // VIA Register addresses (offset from base address)
15
15
  private static readonly VIA_ORB = 0x00 // Output Register B
16
16
  private static readonly VIA_ORA = 0x01 // Output Register A
@@ -73,8 +73,8 @@ export class GPIOCard implements IO {
73
73
  private ticksPerMicrosecond: number = 1
74
74
 
75
75
  // Attachments
76
- private portA_attachments: (GPIOAttachment | null)[] = []
77
- private portB_attachments: (GPIOAttachment | null)[] = []
76
+ private portA_attachments: (Attachment | null)[] = []
77
+ private portB_attachments: (Attachment | null)[] = []
78
78
  private portA_attachmentCount: number = 0
79
79
  private portB_attachmentCount: number = 0
80
80
 
@@ -116,7 +116,7 @@ export class GPIOCard implements IO {
116
116
  // Initialize attachment arrays
117
117
  this.portA_attachmentCount = 0
118
118
  this.portB_attachmentCount = 0
119
- for (let i = 0; i < GPIOCard.MAX_ATTACHMENTS_PER_PORT; i++) {
119
+ for (let i = 0; i < VIA.MAX_ATTACHMENTS_PER_PORT; i++) {
120
120
  this.portA_attachments[i] = null
121
121
  this.portB_attachments[i] = null
122
122
  }
@@ -143,9 +143,9 @@ export class GPIOCard implements IO {
143
143
  let value = 0x00
144
144
 
145
145
  switch (reg) {
146
- case GPIOCard.VIA_ORB:
146
+ case VIA.VIA_ORB:
147
147
  // Reading ORB clears CB1 and CB2 interrupt flags
148
- this.clearIRQFlag(GPIOCard.IRQ_CB1 | GPIOCard.IRQ_CB2)
148
+ this.clearIRQFlag(VIA.IRQ_CB1 | VIA.IRQ_CB2)
149
149
  value = this.readPortB()
150
150
  // Notify attachments that interrupts were cleared
151
151
  for (let i = 0; i < this.portB_attachmentCount; i++) {
@@ -155,9 +155,9 @@ export class GPIOCard implements IO {
155
155
  }
156
156
  break
157
157
 
158
- case GPIOCard.VIA_ORA:
158
+ case VIA.VIA_ORA:
159
159
  // Reading ORA clears CA1 and CA2 interrupt flags
160
- this.clearIRQFlag(GPIOCard.IRQ_CA1 | GPIOCard.IRQ_CA2)
160
+ this.clearIRQFlag(VIA.IRQ_CA1 | VIA.IRQ_CA2)
161
161
  value = this.readPortA()
162
162
  // Notify attachments that interrupts were cleared
163
163
  for (let i = 0; i < this.portA_attachmentCount; i++) {
@@ -167,69 +167,69 @@ export class GPIOCard implements IO {
167
167
  }
168
168
  break
169
169
 
170
- case GPIOCard.VIA_DDRB:
170
+ case VIA.VIA_DDRB:
171
171
  value = this.regDDRB
172
172
  break
173
173
 
174
- case GPIOCard.VIA_DDRA:
174
+ case VIA.VIA_DDRA:
175
175
  value = this.regDDRA
176
176
  break
177
177
 
178
- case GPIOCard.VIA_T1CL:
178
+ case VIA.VIA_T1CL:
179
179
  // Reading T1CL clears T1 interrupt flag
180
- this.clearIRQFlag(GPIOCard.IRQ_T1)
180
+ this.clearIRQFlag(VIA.IRQ_T1)
181
181
  value = this.regT1C & 0xFF
182
182
  break
183
183
 
184
- case GPIOCard.VIA_T1CH:
184
+ case VIA.VIA_T1CH:
185
185
  value = (this.regT1C >> 8) & 0xFF
186
186
  break
187
187
 
188
- case GPIOCard.VIA_T1LL:
188
+ case VIA.VIA_T1LL:
189
189
  value = this.regT1L & 0xFF
190
190
  break
191
191
 
192
- case GPIOCard.VIA_T1LH:
192
+ case VIA.VIA_T1LH:
193
193
  value = (this.regT1L >> 8) & 0xFF
194
194
  break
195
195
 
196
- case GPIOCard.VIA_T2CL:
196
+ case VIA.VIA_T2CL:
197
197
  // Reading T2CL clears T2 interrupt flag
198
- this.clearIRQFlag(GPIOCard.IRQ_T2)
198
+ this.clearIRQFlag(VIA.IRQ_T2)
199
199
  value = this.regT2C & 0xFF
200
200
  break
201
201
 
202
- case GPIOCard.VIA_T2CH:
202
+ case VIA.VIA_T2CH:
203
203
  value = (this.regT2C >> 8) & 0xFF
204
204
  break
205
205
 
206
- case GPIOCard.VIA_SR:
206
+ case VIA.VIA_SR:
207
207
  // Reading SR clears SR interrupt flag
208
- this.clearIRQFlag(GPIOCard.IRQ_SR)
208
+ this.clearIRQFlag(VIA.IRQ_SR)
209
209
  value = this.regSR
210
210
  break
211
211
 
212
- case GPIOCard.VIA_ACR:
212
+ case VIA.VIA_ACR:
213
213
  value = this.regACR
214
214
  break
215
215
 
216
- case GPIOCard.VIA_PCR:
216
+ case VIA.VIA_PCR:
217
217
  value = this.regPCR
218
218
  break
219
219
 
220
- case GPIOCard.VIA_IFR:
220
+ case VIA.VIA_IFR:
221
221
  value = this.regIFR
222
222
  // Bit 7 is set if any enabled interrupt is active
223
223
  if (this.regIFR & this.regIER & 0x7F) {
224
- value |= GPIOCard.IRQ_IRQ
224
+ value |= VIA.IRQ_IRQ
225
225
  }
226
226
  break
227
227
 
228
- case GPIOCard.VIA_IER:
228
+ case VIA.VIA_IER:
229
229
  value = this.regIER | 0x80 // Bit 7 always reads as 1
230
230
  break
231
231
 
232
- case GPIOCard.VIA_ORA_NH:
232
+ case VIA.VIA_ORA_NH:
233
233
  // Reading ORA without handshake (no interrupt flag clearing)
234
234
  value = this.readPortA()
235
235
  break
@@ -243,84 +243,84 @@ export class GPIOCard implements IO {
243
243
  const value = data & 0xFF
244
244
 
245
245
  switch (reg) {
246
- case GPIOCard.VIA_ORB:
246
+ case VIA.VIA_ORB:
247
247
  // Writing ORB clears CB1 and CB2 interrupt flags
248
- this.clearIRQFlag(GPIOCard.IRQ_CB1 | GPIOCard.IRQ_CB2)
248
+ this.clearIRQFlag(VIA.IRQ_CB1 | VIA.IRQ_CB2)
249
249
  this.regORB = value
250
250
  this.writePortB(value)
251
251
  break
252
252
 
253
- case GPIOCard.VIA_ORA:
253
+ case VIA.VIA_ORA:
254
254
  // Writing ORA clears CA1 and CA2 interrupt flags
255
- this.clearIRQFlag(GPIOCard.IRQ_CA1 | GPIOCard.IRQ_CA2)
255
+ this.clearIRQFlag(VIA.IRQ_CA1 | VIA.IRQ_CA2)
256
256
  this.regORA = value
257
257
  this.writePortA(value)
258
258
  break
259
259
 
260
- case GPIOCard.VIA_DDRB:
260
+ case VIA.VIA_DDRB:
261
261
  this.regDDRB = value
262
262
  break
263
263
 
264
- case GPIOCard.VIA_DDRA:
264
+ case VIA.VIA_DDRA:
265
265
  this.regDDRA = value
266
266
  break
267
267
 
268
- case GPIOCard.VIA_T1CL:
269
- case GPIOCard.VIA_T1LL:
268
+ case VIA.VIA_T1CL:
269
+ case VIA.VIA_T1LL:
270
270
  // Write to T1 low latch
271
271
  this.regT1L = (this.regT1L & 0xFF00) | value
272
272
  break
273
273
 
274
- case GPIOCard.VIA_T1CH:
274
+ case VIA.VIA_T1CH:
275
275
  // Write to T1 high counter - loads latch into counter and starts timer
276
276
  this.regT1L = (this.regT1L & 0x00FF) | (value << 8)
277
277
  this.regT1C = this.regT1L
278
- this.clearIRQFlag(GPIOCard.IRQ_T1)
278
+ this.clearIRQFlag(VIA.IRQ_T1)
279
279
  this.T1_running = true
280
280
  break
281
281
 
282
- case GPIOCard.VIA_T1LH:
282
+ case VIA.VIA_T1LH:
283
283
  // Write to T1 high latch
284
284
  this.regT1L = (this.regT1L & 0x00FF) | (value << 8)
285
- this.clearIRQFlag(GPIOCard.IRQ_T1)
285
+ this.clearIRQFlag(VIA.IRQ_T1)
286
286
  break
287
287
 
288
- case GPIOCard.VIA_T2CL:
288
+ case VIA.VIA_T2CL:
289
289
  // Write to T2 low latch
290
290
  this.regT2L = value
291
291
  break
292
292
 
293
- case GPIOCard.VIA_T2CH:
293
+ case VIA.VIA_T2CH:
294
294
  // Write to T2 high counter - loads latch into counter and starts timer
295
295
  this.regT2C = (value << 8) | this.regT2L
296
- this.clearIRQFlag(GPIOCard.IRQ_T2)
296
+ this.clearIRQFlag(VIA.IRQ_T2)
297
297
  this.T2_running = true
298
298
  break
299
299
 
300
- case GPIOCard.VIA_SR:
300
+ case VIA.VIA_SR:
301
301
  this.regSR = value
302
- this.clearIRQFlag(GPIOCard.IRQ_SR)
302
+ this.clearIRQFlag(VIA.IRQ_SR)
303
303
  break
304
304
 
305
- case GPIOCard.VIA_ACR:
305
+ case VIA.VIA_ACR:
306
306
  this.regACR = value
307
307
  // ACR controls timer modes, shift register, and latching
308
308
  break
309
309
 
310
- case GPIOCard.VIA_PCR:
310
+ case VIA.VIA_PCR:
311
311
  this.regPCR = value
312
312
  // PCR controls CA1, CA2, CB1, CB2 behavior
313
313
  this.updateCA2()
314
314
  this.updateCB2()
315
315
  break
316
316
 
317
- case GPIOCard.VIA_IFR:
317
+ case VIA.VIA_IFR:
318
318
  // Writing to IFR clears the corresponding interrupt flags
319
319
  this.regIFR &= ~(value & 0x7F)
320
320
  this.updateIRQ()
321
321
  break
322
322
 
323
- case GPIOCard.VIA_IER:
323
+ case VIA.VIA_IER:
324
324
  // Bit 7 determines set (1) or clear (0)
325
325
  if (value & 0x80) {
326
326
  this.regIER |= (value & 0x7F)
@@ -330,7 +330,7 @@ export class GPIOCard implements IO {
330
330
  this.updateIRQ()
331
331
  break
332
332
 
333
- case GPIOCard.VIA_ORA_NH:
333
+ case VIA.VIA_ORA_NH:
334
334
  // Writing ORA without handshake (no interrupt flag clearing)
335
335
  this.regORA = value
336
336
  this.writePortA(value)
@@ -345,7 +345,7 @@ export class GPIOCard implements IO {
345
345
  if (this.T1_running && this.regT1C > 0) {
346
346
  this.regT1C--
347
347
  if (this.regT1C === 0) {
348
- this.setIRQFlag(GPIOCard.IRQ_T1)
348
+ this.setIRQFlag(VIA.IRQ_T1)
349
349
 
350
350
  // Check if timer is in free-run mode (ACR bit 6)
351
351
  if (this.regACR & 0x40) {
@@ -365,7 +365,7 @@ export class GPIOCard implements IO {
365
365
  if (this.T2_running && this.regT2C > 0) {
366
366
  this.regT2C--
367
367
  if (this.regT2C === 0) {
368
- this.setIRQFlag(GPIOCard.IRQ_T2)
368
+ this.setIRQFlag(VIA.IRQ_T2)
369
369
  this.T2_running = false
370
370
  }
371
371
  }
@@ -386,20 +386,20 @@ export class GPIOCard implements IO {
386
386
  for (let i = 0; i < this.portA_attachmentCount; i++) {
387
387
  if (this.portA_attachments[i] !== null) {
388
388
  if (this.portA_attachments[i]!.hasCA1Interrupt()) {
389
- this.setIRQFlag(GPIOCard.IRQ_CA1)
389
+ this.setIRQFlag(VIA.IRQ_CA1)
390
390
  }
391
391
  if (this.portA_attachments[i]!.hasCA2Interrupt()) {
392
- this.setIRQFlag(GPIOCard.IRQ_CA2)
392
+ this.setIRQFlag(VIA.IRQ_CA2)
393
393
  }
394
394
  }
395
395
  }
396
396
  for (let i = 0; i < this.portB_attachmentCount; i++) {
397
397
  if (this.portB_attachments[i] !== null) {
398
398
  if (this.portB_attachments[i]!.hasCB1Interrupt()) {
399
- this.setIRQFlag(GPIOCard.IRQ_CB1)
399
+ this.setIRQFlag(VIA.IRQ_CB1)
400
400
  }
401
401
  if (this.portB_attachments[i]!.hasCB2Interrupt()) {
402
- this.setIRQFlag(GPIOCard.IRQ_CB2)
402
+ this.setIRQFlag(VIA.IRQ_CB2)
403
403
  }
404
404
  }
405
405
  }
@@ -413,9 +413,9 @@ export class GPIOCard implements IO {
413
413
  private updateIRQ(): void {
414
414
  // Update bit 7 of IFR based on enabled interrupts
415
415
  if (this.regIFR & this.regIER & 0x7F) {
416
- this.regIFR |= GPIOCard.IRQ_IRQ
416
+ this.regIFR |= VIA.IRQ_IRQ
417
417
  } else {
418
- this.regIFR &= ~GPIOCard.IRQ_IRQ
418
+ this.regIFR &= ~VIA.IRQ_IRQ
419
419
  }
420
420
  }
421
421
 
@@ -629,8 +629,8 @@ export class GPIOCard implements IO {
629
629
  * Attach a GPIO device to Port A
630
630
  * @param attachment - The attachment to add
631
631
  */
632
- attachToPortA(attachment: GPIOAttachment): void {
633
- if (attachment !== null && this.portA_attachmentCount < GPIOCard.MAX_ATTACHMENTS_PER_PORT) {
632
+ attachToPortA(attachment: Attachment): void {
633
+ if (attachment !== null && this.portA_attachmentCount < VIA.MAX_ATTACHMENTS_PER_PORT) {
634
634
  this.portA_attachments[this.portA_attachmentCount++] = attachment
635
635
  this.sortAttachmentsByPriority()
636
636
  // Notify the attachment of current control line states
@@ -642,8 +642,8 @@ export class GPIOCard implements IO {
642
642
  * Attach a GPIO device to Port B
643
643
  * @param attachment - The attachment to add
644
644
  */
645
- attachToPortB(attachment: GPIOAttachment): void {
646
- if (attachment !== null && this.portB_attachmentCount < GPIOCard.MAX_ATTACHMENTS_PER_PORT) {
645
+ attachToPortB(attachment: Attachment): void {
646
+ if (attachment !== null && this.portB_attachmentCount < VIA.MAX_ATTACHMENTS_PER_PORT) {
647
647
  this.portB_attachments[this.portB_attachmentCount++] = attachment
648
648
  this.sortAttachmentsByPriority()
649
649
  // Notify the attachment of current control line states
@@ -656,7 +656,7 @@ export class GPIOCard implements IO {
656
656
  * @param index - The attachment index
657
657
  * @returns The attachment or null if not found
658
658
  */
659
- getPortAAttachment(index: number): GPIOAttachment | null {
659
+ getPortAAttachment(index: number): Attachment | null {
660
660
  if (index < this.portA_attachmentCount) {
661
661
  return this.portA_attachments[index]
662
662
  }
@@ -668,7 +668,7 @@ export class GPIOCard implements IO {
668
668
  * @param index - The attachment index
669
669
  * @returns The attachment or null if not found
670
670
  */
671
- getPortBAttachment(index: number): GPIOAttachment | null {
671
+ getPortBAttachment(index: number): Attachment | null {
672
672
  if (index < this.portB_attachmentCount) {
673
673
  return this.portB_attachments[index]
674
674
  }
@@ -136,7 +136,7 @@ const CYCLES_PER_TICK = 128 // Must match Machine.ts ioTickInterval
136
136
  const BORDER_X = (DISPLAY_WIDTH - TMS_PIXELS_X) / 2 // 32
137
137
  const BORDER_Y = (DISPLAY_HEIGHT - TMS_PIXELS_Y) / 2 // 24
138
138
 
139
- export class VideoCard implements IO {
139
+ export class Video implements IO {
140
140
 
141
141
  raiseIRQ = () => {}
142
142
  raiseNMI = () => {}