ac6502 1.0.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 (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +261 -0
  3. package/dist/components/CPU.js +1170 -0
  4. package/dist/components/CPU.js.map +1 -0
  5. package/dist/components/Cart.js +23 -0
  6. package/dist/components/Cart.js.map +1 -0
  7. package/dist/components/IO/Empty.js +19 -0
  8. package/dist/components/IO/Empty.js.map +1 -0
  9. package/dist/components/IO/GPIOAttachments/GPIOAttachment.js +71 -0
  10. package/dist/components/IO/GPIOAttachments/GPIOAttachment.js.map +1 -0
  11. package/dist/components/IO/GPIOAttachments/GPIOJoystickAttachment.js +90 -0
  12. package/dist/components/IO/GPIOAttachments/GPIOJoystickAttachment.js.map +1 -0
  13. package/dist/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.js +489 -0
  14. package/dist/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.js.map +1 -0
  15. package/dist/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.js +274 -0
  16. package/dist/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.js.map +1 -0
  17. package/dist/components/IO/GPIOCard.js +597 -0
  18. package/dist/components/IO/GPIOCard.js.map +1 -0
  19. package/dist/components/IO/InputBoard.js +19 -0
  20. package/dist/components/IO/InputBoard.js.map +1 -0
  21. package/dist/components/IO/LCDCard.js +19 -0
  22. package/dist/components/IO/LCDCard.js.map +1 -0
  23. package/dist/components/IO/RAMCard.js +63 -0
  24. package/dist/components/IO/RAMCard.js.map +1 -0
  25. package/dist/components/IO/RTCCard.js +483 -0
  26. package/dist/components/IO/RTCCard.js.map +1 -0
  27. package/dist/components/IO/SerialCard.js +282 -0
  28. package/dist/components/IO/SerialCard.js.map +1 -0
  29. package/dist/components/IO/SoundCard.js +620 -0
  30. package/dist/components/IO/SoundCard.js.map +1 -0
  31. package/dist/components/IO/StorageCard.js +428 -0
  32. package/dist/components/IO/StorageCard.js.map +1 -0
  33. package/dist/components/IO/VGACard.js +9 -0
  34. package/dist/components/IO/VGACard.js.map +1 -0
  35. package/dist/components/IO/VideoCard.js +623 -0
  36. package/dist/components/IO/VideoCard.js.map +1 -0
  37. package/dist/components/IO.js +3 -0
  38. package/dist/components/IO.js.map +1 -0
  39. package/dist/components/Machine.js +310 -0
  40. package/dist/components/Machine.js.map +1 -0
  41. package/dist/components/RAM.js +24 -0
  42. package/dist/components/RAM.js.map +1 -0
  43. package/dist/components/ROM.js +23 -0
  44. package/dist/components/ROM.js.map +1 -0
  45. package/dist/index.js +441 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/tests/CPU.test.js +1626 -0
  48. package/dist/tests/CPU.test.js.map +1 -0
  49. package/dist/tests/Cart.test.js +119 -0
  50. package/dist/tests/Cart.test.js.map +1 -0
  51. package/dist/tests/IO/GPIOAttachments/GPIOAttachment.test.js +339 -0
  52. package/dist/tests/IO/GPIOAttachments/GPIOAttachment.test.js.map +1 -0
  53. package/dist/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.js +126 -0
  54. package/dist/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.js.map +1 -0
  55. package/dist/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.js +779 -0
  56. package/dist/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.js.map +1 -0
  57. package/dist/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.js +355 -0
  58. package/dist/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.js.map +1 -0
  59. package/dist/tests/IO/GPIOCard.test.js +503 -0
  60. package/dist/tests/IO/GPIOCard.test.js.map +1 -0
  61. package/dist/tests/IO/RAMCard.test.js +229 -0
  62. package/dist/tests/IO/RAMCard.test.js.map +1 -0
  63. package/dist/tests/IO/RTCCard.test.js +177 -0
  64. package/dist/tests/IO/RTCCard.test.js.map +1 -0
  65. package/dist/tests/IO/SerialCard.test.js +423 -0
  66. package/dist/tests/IO/SerialCard.test.js.map +1 -0
  67. package/dist/tests/IO/SoundCard.test.js +528 -0
  68. package/dist/tests/IO/SoundCard.test.js.map +1 -0
  69. package/dist/tests/IO/StorageCard.test.js +647 -0
  70. package/dist/tests/IO/StorageCard.test.js.map +1 -0
  71. package/dist/tests/IO/VideoCard.test.js +549 -0
  72. package/dist/tests/IO/VideoCard.test.js.map +1 -0
  73. package/dist/tests/Machine.test.js +383 -0
  74. package/dist/tests/Machine.test.js.map +1 -0
  75. package/dist/tests/RAM.test.js +160 -0
  76. package/dist/tests/RAM.test.js.map +1 -0
  77. package/dist/tests/ROM.test.js +123 -0
  78. package/dist/tests/ROM.test.js.map +1 -0
  79. package/jest.config.cjs +9 -0
  80. package/package.json +43 -0
  81. package/src/components/CPU.ts +1371 -0
  82. package/src/components/Cart.ts +20 -0
  83. package/src/components/IO/GPIOAttachments/GPIOAttachment.ts +189 -0
  84. package/src/components/IO/GPIOAttachments/GPIOJoystickAttachment.ts +99 -0
  85. package/src/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.ts +465 -0
  86. package/src/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.ts +287 -0
  87. package/src/components/IO/GPIOCard.ts +677 -0
  88. package/src/components/IO/RAMCard.ts +68 -0
  89. package/src/components/IO/RTCCard.ts +518 -0
  90. package/src/components/IO/SerialCard.ts +335 -0
  91. package/src/components/IO/SoundCard.ts +711 -0
  92. package/src/components/IO/StorageCard.ts +473 -0
  93. package/src/components/IO/VideoCard.ts +730 -0
  94. package/src/components/IO.ts +11 -0
  95. package/src/components/Machine.ts +364 -0
  96. package/src/components/RAM.ts +23 -0
  97. package/src/components/ROM.ts +19 -0
  98. package/src/index.ts +474 -0
  99. package/src/tests/CPU.test.ts +2045 -0
  100. package/src/tests/Cart.test.ts +149 -0
  101. package/src/tests/IO/GPIOAttachments/GPIOAttachment.test.ts +413 -0
  102. package/src/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.ts +147 -0
  103. package/src/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.ts +961 -0
  104. package/src/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.ts +449 -0
  105. package/src/tests/IO/GPIOCard.test.ts +644 -0
  106. package/src/tests/IO/RAMCard.test.ts +284 -0
  107. package/src/tests/IO/RTCCard.test.ts +222 -0
  108. package/src/tests/IO/SerialCard.test.ts +530 -0
  109. package/src/tests/IO/SoundCard.test.ts +659 -0
  110. package/src/tests/IO/StorageCard.test.ts +787 -0
  111. package/src/tests/IO/VideoCard.test.ts +668 -0
  112. package/src/tests/Machine.test.ts +437 -0
  113. package/src/tests/RAM.test.ts +196 -0
  114. package/src/tests/ROM.test.ts +154 -0
  115. package/tsconfig.json +12 -0
@@ -0,0 +1,473 @@
1
+ import { IO } from '../IO'
2
+ import { readFile, writeFile } from 'fs/promises'
3
+ import { existsSync } from 'fs'
4
+
5
+ /**
6
+ * StorageCard - Emulates a Compact Flash card in 8-bit IDE mode
7
+ *
8
+ * Emulates a 128MB CF card with ATA-style register interface.
9
+ * Uses LBA (Logical Block Addressing) for sector access.
10
+ *
11
+ * Register Map (address & 0x07):
12
+ * $00: Data Register (read/write)
13
+ * $01: Error Register (read) / Feature Register (write)
14
+ * $02: Sector Count Register (read/write)
15
+ * $03: LBA0 Register (read/write) - bits 0-7 of LBA
16
+ * $04: LBA1 Register (read/write) - bits 8-15 of LBA
17
+ * $05: LBA2 Register (read/write) - bits 16-23 of LBA
18
+ * $06: LBA3 Register (read/write) - bits 24-27 of LBA + mode bits
19
+ * $07: Status Register (read) / Command Register (write)
20
+ *
21
+ * Supported Commands:
22
+ * 0x20, 0x21: Read Sector(s)
23
+ * 0x30, 0x31: Write Sector(s)
24
+ * 0xC0: Erase Sector
25
+ * 0xEC: Identify Drive
26
+ * 0xEF: Set Features (accepted but not implemented)
27
+ */
28
+ export class StorageCard implements IO {
29
+
30
+ // Constants
31
+ private static readonly STORAGE_SIZE = 128 * 1024 * 1024 // 128MB
32
+ private static readonly SECTOR_SIZE = 512
33
+ private static readonly SECTOR_COUNT = StorageCard.STORAGE_SIZE / StorageCard.SECTOR_SIZE // 262144 sectors
34
+
35
+ // Status Register Flags
36
+ private static readonly STATUS_ERR = 0x01 // Error
37
+ private static readonly STATUS_DRQ = 0x08 // Data Request
38
+ private static readonly STATUS_RDY = 0x40 // Ready
39
+
40
+ // Error Register Flags
41
+ private static readonly ERR_AMNF = 0x01 // Address Mark Not Found
42
+ private static readonly ERR_ABRT = 0x04 // Aborted Command
43
+ private static readonly ERR_IDNF = 0x10 // ID Not Found
44
+
45
+ // Storage and Identity data (in-memory simulation)
46
+ private storage: Buffer
47
+ private identity: Buffer
48
+
49
+ // Data buffer (512 bytes)
50
+ private buffer: Buffer = Buffer.alloc(StorageCard.SECTOR_SIZE)
51
+ private bufferIndex: number = 0
52
+ private commandDataSize: number = StorageCard.SECTOR_SIZE
53
+ private sectorOffset: number = 0
54
+
55
+ // Registers
56
+ private error: number = 0x00
57
+ private feature: number = 0x00
58
+ private sectorCount: number = 0x00
59
+ private lba0: number = 0x00
60
+ private lba1: number = 0x00
61
+ private lba2: number = 0x00
62
+ private lba3: number = 0xE0
63
+ private status: number = 0x00
64
+ private command: number = 0x00
65
+
66
+ // State flags
67
+ private isIdentifying: boolean = false
68
+ private isTransferring: boolean = false
69
+
70
+ raiseIRQ = () => {}
71
+ raiseNMI = () => {}
72
+
73
+ constructor() {
74
+ // Initialize storage and identity buffers
75
+ this.storage = Buffer.alloc(StorageCard.STORAGE_SIZE, 0x00)
76
+ this.identity = Buffer.alloc(StorageCard.SECTOR_SIZE)
77
+ this.generateIdentity()
78
+ this.reset(true)
79
+ }
80
+
81
+ read(address: number): number {
82
+ switch (address & 0x0007) {
83
+ case 0x00: // Data Register
84
+ return this.readBuffer()
85
+ case 0x01: // Error Register
86
+ return this.error
87
+ case 0x02: // Sector Count Register
88
+ return this.sectorCount
89
+ case 0x03: // LBA0 Register
90
+ return this.lba0
91
+ case 0x04: // LBA1 Register
92
+ return this.lba1
93
+ case 0x05: // LBA2 Register
94
+ return this.lba2
95
+ case 0x06: // LBA3 Register
96
+ return this.lba3
97
+ case 0x07: // Status Register
98
+ return this.status
99
+ default:
100
+ return 0x00
101
+ }
102
+ }
103
+
104
+ write(address: number, data: number): void {
105
+ switch (address & 0x0007) {
106
+ case 0x00: // Data Register
107
+ this.writeBuffer(data)
108
+ break
109
+ case 0x01: // Feature Register
110
+ this.feature = data
111
+ break
112
+ case 0x02: // Sector Count Register
113
+ this.sectorCount = data
114
+ break
115
+ case 0x03: // LBA0 Register
116
+ this.lba0 = data
117
+ break
118
+ case 0x04: // LBA1 Register
119
+ this.lba1 = data
120
+ break
121
+ case 0x05: // LBA2 Register
122
+ this.lba2 = data
123
+ break
124
+ case 0x06: // LBA3 Register
125
+ this.lba3 = (data & 0x0F) | 0xE0
126
+ break
127
+ case 0x07: // Command Register
128
+ this.command = data
129
+ this.executeCommand()
130
+ break
131
+ }
132
+ }
133
+
134
+ tick(frequency: number): void {
135
+ // No timing behavior needed for this implementation
136
+ }
137
+
138
+ reset(coldStart: boolean): void {
139
+ this.bufferIndex = 0x0000
140
+ this.commandDataSize = StorageCard.SECTOR_SIZE
141
+ this.sectorOffset = 0
142
+
143
+ this.error = 0x00
144
+ this.feature = 0x00
145
+ this.sectorCount = 0x00
146
+ this.lba0 = 0x00
147
+ this.lba1 = 0x00
148
+ this.lba2 = 0x00
149
+ this.lba3 = 0xE0
150
+ this.status = 0x00 | StorageCard.STATUS_RDY
151
+ this.command = 0x00
152
+
153
+ this.isIdentifying = false
154
+ this.isTransferring = false
155
+
156
+ this.buffer.fill(0x00)
157
+ }
158
+
159
+ //
160
+ // Private methods
161
+ //
162
+
163
+ private executeCommand(): void {
164
+ // New command so clear errors and flags
165
+ this.status &= ~StorageCard.STATUS_ERR
166
+ this.status &= ~StorageCard.STATUS_DRQ
167
+ this.error = 0x00
168
+ this.commandDataSize = StorageCard.SECTOR_SIZE * this.sectorCount
169
+ this.bufferIndex = 0
170
+ this.sectorOffset = 0
171
+
172
+ // Check if already executing a command
173
+ if (this.isTransferring || this.isIdentifying) {
174
+ this.status |= StorageCard.STATUS_ERR
175
+ this.error |= StorageCard.ERR_ABRT
176
+ return
177
+ }
178
+
179
+ switch (this.command) {
180
+ case 0xC0: { // Erase sector
181
+ if (!this.sectorValid()) {
182
+ this.status |= StorageCard.STATUS_ERR
183
+ this.error |= StorageCard.ERR_ABRT | StorageCard.ERR_IDNF
184
+ } else {
185
+ const offset = this.sectorIndex() * StorageCard.SECTOR_SIZE
186
+ this.storage.fill(0x00, offset, offset + StorageCard.SECTOR_SIZE)
187
+ }
188
+ break
189
+ }
190
+
191
+ 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
195
+ this.isIdentifying = true
196
+ break
197
+ }
198
+
199
+ case 0x20: // Read sector
200
+ case 0x21:
201
+ if (!this.sectorValid()) {
202
+ this.status |= StorageCard.STATUS_ERR
203
+ this.error |= StorageCard.ERR_ABRT | StorageCard.ERR_IDNF
204
+ } else {
205
+ // 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
209
+ this.isTransferring = true
210
+ }
211
+ break
212
+
213
+ case 0xEF: // Set features
214
+ // We don't support setting features but accept them without error
215
+ break
216
+
217
+ case 0x30: // Write sector
218
+ case 0x31:
219
+ if (!this.sectorValid()) {
220
+ this.status |= StorageCard.STATUS_ERR
221
+ this.error |= StorageCard.ERR_ABRT | StorageCard.ERR_IDNF
222
+ } else {
223
+ this.status |= StorageCard.STATUS_DRQ
224
+ this.isTransferring = true
225
+ }
226
+ break
227
+
228
+ default:
229
+ // Unsupported command
230
+ this.status |= StorageCard.STATUS_ERR
231
+ this.error |= StorageCard.ERR_ABRT
232
+ break
233
+ }
234
+ }
235
+
236
+ private readBuffer(): number {
237
+ if (this.isIdentifying) {
238
+ const data = this.buffer[this.bufferIndex]
239
+
240
+ if (this.bufferIndex < this.commandDataSize - 1) {
241
+ this.bufferIndex++
242
+ } else {
243
+ this.bufferIndex = 0
244
+ this.isIdentifying = false
245
+ this.status &= ~StorageCard.STATUS_DRQ
246
+ }
247
+
248
+ return data
249
+ } else if (this.isTransferring) {
250
+ const data = this.buffer[this.bufferIndex]
251
+
252
+ if (this.bufferIndex < StorageCard.SECTOR_SIZE - 1) {
253
+ this.bufferIndex++
254
+ } else {
255
+ this.bufferIndex = 0
256
+ this.sectorOffset++
257
+
258
+ if (this.sectorOffset < this.sectorCount) {
259
+ // 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)
262
+ } else {
263
+ this.isTransferring = false
264
+ this.status &= ~StorageCard.STATUS_DRQ
265
+ }
266
+ }
267
+
268
+ return data
269
+ } else {
270
+ return 0x00
271
+ }
272
+ }
273
+
274
+ private writeBuffer(value: number): void {
275
+ this.buffer[this.bufferIndex] = value
276
+
277
+ if (this.bufferIndex < StorageCard.SECTOR_SIZE - 1) {
278
+ this.bufferIndex++
279
+ } else {
280
+ this.bufferIndex = 0
281
+
282
+ // 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)
285
+
286
+ this.sectorOffset++
287
+
288
+ // Check if all sectors have been written
289
+ if (this.sectorOffset >= this.sectorCount) {
290
+ this.isTransferring = false
291
+ this.status &= ~StorageCard.STATUS_DRQ
292
+ }
293
+ }
294
+ }
295
+
296
+ private sectorIndex(): number {
297
+ return ((this.lba3 & 0x0F) << 24) | (this.lba2 << 16) | (this.lba1 << 8) | this.lba0
298
+ }
299
+
300
+ private sectorValid(): boolean {
301
+ return this.sectorIndex() < StorageCard.SECTOR_COUNT
302
+ }
303
+
304
+ private generateIdentity(): void {
305
+ // Generate emulated 128MB CF card identity
306
+ // Based on real Promaster 128MB CF card data
307
+
308
+ // Fill with zeros first
309
+ this.identity.fill(0x00)
310
+
311
+ // Word 0: General configuration
312
+ this.identity[0] = 0x84
313
+ this.identity[1] = 0x8A // Removable Disk
314
+
315
+ // Word 1: Number of cylinders
316
+ this.identity[2] = 0x00
317
+ this.identity[3] = 0x04
318
+
319
+ // Word 2: Reserved
320
+ this.identity[4] = 0x00
321
+ this.identity[5] = 0x00
322
+
323
+ // Word 3: Number of heads
324
+ this.identity[6] = 0x08
325
+ this.identity[7] = 0x00
326
+
327
+ // Word 4: Unformatted bytes per track
328
+ this.identity[8] = 0x00
329
+ this.identity[9] = 0x40
330
+
331
+ // Word 5: Unformatted bytes per sector
332
+ this.identity[10] = 0x00
333
+ this.identity[11] = 0x02
334
+
335
+ // Word 6: Sectors per track
336
+ this.identity[12] = 0x20
337
+ this.identity[13] = 0x00
338
+
339
+ // Words 7-9: Reserved
340
+ this.identity[14] = 0x04
341
+ this.identity[15] = 0x00
342
+ this.identity[16] = 0x00
343
+ this.identity[17] = 0x00
344
+ this.identity[18] = 0x00
345
+ this.identity[19] = 0x00
346
+
347
+ // Words 10-19: Serial number (20 ASCII characters)
348
+ const serial = 'ACWD6502EMUCF1010101'
349
+ for (let i = 0; i < serial.length; i++) {
350
+ this.identity[20 + i] = serial.charCodeAt(i)
351
+ }
352
+
353
+ // Word 20: Buffer type
354
+ this.identity[40] = 0x01
355
+ this.identity[41] = 0x00
356
+
357
+ // Word 21: Buffer size in 512 byte increments
358
+ this.identity[42] = 0x04
359
+ this.identity[43] = 0x00
360
+
361
+ // Word 22: ECC bytes
362
+ this.identity[44] = 0x04
363
+ this.identity[45] = 0x00
364
+
365
+ // Words 23-26: Firmware revision (8 ASCII characters)
366
+ const firmware = '1.0 '
367
+ for (let i = 0; i < firmware.length; i++) {
368
+ this.identity[46 + i] = firmware.charCodeAt(i)
369
+ }
370
+
371
+ // Words 27-46: Model number (40 ASCII characters)
372
+ const model = 'ACWD6502EMUCF '
373
+ for (let i = 0; i < model.length; i++) {
374
+ this.identity[54 + i] = model.charCodeAt(i)
375
+ }
376
+
377
+ // Word 47: Multiple sector setting
378
+ this.identity[94] = 0x01
379
+ this.identity[95] = 0x00
380
+
381
+ // Word 48: Double word not supported
382
+ this.identity[96] = 0x00
383
+ this.identity[97] = 0x00
384
+
385
+ // Word 49: Capabilities (LBA supported)
386
+ this.identity[98] = 0x00
387
+ this.identity[99] = 0x02
388
+
389
+ // Word 50: Reserved
390
+ this.identity[100] = 0x00
391
+ this.identity[101] = 0x00
392
+
393
+ // Word 51: PIO data transfer cycle timing
394
+ this.identity[102] = 0x00
395
+ this.identity[103] = 0x02
396
+
397
+ // Word 52: DMA transfer cycle timing
398
+ this.identity[104] = 0x00
399
+ this.identity[105] = 0x00
400
+
401
+ // Word 53: Field validity
402
+ this.identity[106] = 0x01
403
+ this.identity[107] = 0x00
404
+
405
+ // Word 54: Current number of cylinders
406
+ this.identity[108] = 0x00
407
+ this.identity[109] = 0x04
408
+
409
+ // Word 55: Current number of heads
410
+ this.identity[110] = 0x08
411
+ this.identity[111] = 0x00
412
+
413
+ // Word 56: Current sectors per track
414
+ this.identity[112] = 0x20
415
+ this.identity[113] = 0x00
416
+
417
+ // Words 57-58: Current capacity in sectors
418
+ this.identity[114] = 0x00
419
+ this.identity[115] = 0x00
420
+ this.identity[116] = 0x04
421
+ this.identity[117] = 0x00
422
+
423
+ // Word 59: Multiple sector setting
424
+ this.identity[118] = 0x01
425
+ this.identity[119] = 0x01
426
+
427
+ // Words 60-61: Total number of sectors in LBA mode
428
+ this.identity[120] = 0x00
429
+ this.identity[121] = 0x00
430
+ this.identity[122] = 0x04
431
+ this.identity[123] = 0x00
432
+
433
+ // Remaining words are zero
434
+ }
435
+
436
+ /**
437
+ * Load storage data from a file
438
+ * If the file doesn't exist, storage remains empty (initialized to 0x00)
439
+ */
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)
458
+ }
459
+ }
460
+
461
+ /**
462
+ * Save storage data to a file
463
+ */
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
+ }
471
+ }
472
+
473
+ }