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
package/src/index.ts ADDED
@@ -0,0 +1,474 @@
1
+ #! /usr/bin/env node
2
+
3
+ import figlet from 'figlet'
4
+ import { Machine } from './components/Machine'
5
+ import { Command } from 'commander'
6
+ import { SerialPort } from 'serialport'
7
+ import sdl from '@kmamal/sdl'
8
+
9
+ const VERSION = '1.0.0'
10
+ const WIDTH = 320
11
+ const HEIGHT = 240
12
+
13
+ // Audio constants
14
+ const AUDIO_SAMPLE_RATE = 44100
15
+ const AUDIO_CHANNELS = 1
16
+ const AUDIO_FORMAT = 'f32'
17
+ const AUDIO_BUFFERED = 2048
18
+
19
+ // Joystick button bit masks (matching GPIOJoystickAttachment)
20
+ const BUTTON_UP = 0x01
21
+ const BUTTON_DOWN = 0x02
22
+ const BUTTON_LEFT = 0x04
23
+ const BUTTON_RIGHT = 0x08
24
+ const BUTTON_A = 0x10
25
+ const BUTTON_B = 0x20
26
+ const BUTTON_SELECT = 0x40
27
+ const BUTTON_START = 0x80
28
+
29
+ // Axis threshold for converting analog input to digital (50% of normalized range)
30
+ const AXIS_THRESHOLD = 0.5
31
+
32
+ interface EmulatorOptions {
33
+ cart?: string
34
+ freq?: string
35
+ rom?: string
36
+ scale?: string
37
+ baudrate?: string
38
+ parity?: string
39
+ databits?: string
40
+ stopbits?: string
41
+ port?: string
42
+ storage?: string
43
+ }
44
+
45
+ class Emulator {
46
+ private machine: Machine
47
+ private serialPort?: SerialPort
48
+ private window?: any
49
+ private audioDevice?: any
50
+ private controllers: Map<number, any>
51
+ private joystickButtonState: number
52
+ private options: EmulatorOptions
53
+
54
+ constructor(options: EmulatorOptions) {
55
+ this.options = options
56
+ this.machine = new Machine()
57
+ this.controllers = new Map()
58
+ this.joystickButtonState = 0x00
59
+ }
60
+
61
+ async initialize(): Promise<void> {
62
+ this.validateOptions()
63
+ await this.loadBinaries()
64
+ this.configureFrequency()
65
+ this.configureScale()
66
+ this.setupSerialPort()
67
+ this.setupAudio()
68
+ this.setupWindow()
69
+ this.setupControllers()
70
+ }
71
+
72
+ private validateOptions(): void {
73
+ const dataBits = parseInt(this.options.databits || '8') as 5 | 6 | 7 | 8
74
+ const stopBits = parseFloat(this.options.stopbits || '1') as 1 | 1.5 | 2
75
+
76
+ if (dataBits !== 5 && dataBits !== 6 && dataBits !== 7 && dataBits !== 8) {
77
+ console.log('Error: Invalid Data Bits')
78
+ process.exit(1)
79
+ }
80
+ if (stopBits !== 1 && stopBits !== 1.5 && stopBits !== 2) {
81
+ console.log('Error: Invalid Stop Bits')
82
+ process.exit(1)
83
+ }
84
+ }
85
+
86
+ private async loadBinaries(): Promise<void> {
87
+ if (this.options.rom) {
88
+ await this.machine.loadROM(this.options.rom)
89
+ console.log(`Loaded ROM: ${this.options.rom}`)
90
+ } else {
91
+ console.log('Loaded ROM: NONE')
92
+ }
93
+
94
+ if (this.options.cart) {
95
+ await this.machine.loadCart(this.options.cart)
96
+ console.log(`Loaded Cart: ${this.options.cart}`)
97
+ } else {
98
+ console.log('Loaded Cart: NONE')
99
+ }
100
+
101
+ if (this.options.storage) {
102
+ await this.machine.io4.loadFromFile(this.options.storage)
103
+ }
104
+ }
105
+
106
+ private configureFrequency(): void {
107
+ if (this.options.freq) {
108
+ const frequency = Number(this.options.freq)
109
+
110
+ if (!isNaN(frequency)) {
111
+ this.machine.frequency = frequency
112
+ console.log(`Frequency: ${this.options.freq} Hz`)
113
+ } else {
114
+ console.log()
115
+ console.error(`Aborting... Error Invalid Frequency: '${this.options.freq}'`)
116
+ process.exit(1)
117
+ }
118
+ } else {
119
+ console.log("Frequency: 1000000 Hz")
120
+ }
121
+ }
122
+
123
+ private configureScale(): void {
124
+ if (this.options.scale) {
125
+ const scale = Number(this.options.scale)
126
+
127
+ if (!isNaN(scale)) {
128
+ this.machine.scale = scale
129
+ console.log(`Scale: ${this.options.scale}x`)
130
+ } else {
131
+ console.log()
132
+ console.error(`Aborting... Error Invalid Scale: '${this.options.scale}'`)
133
+ process.exit(1)
134
+ }
135
+ } else {
136
+ console.log(`Scale: 1x`)
137
+ }
138
+ }
139
+
140
+ private setupSerialPort(): void {
141
+ if (!this.options.port) {
142
+ return
143
+ }
144
+
145
+ const baudRate = parseInt(this.options.baudrate || '9600')
146
+ const parity = (this.options.parity || 'none') as 'odd' | 'even' | 'none'
147
+ const dataBits = parseInt(this.options.databits || '8') as 5 | 6 | 7 | 8
148
+ const stopBits = parseFloat(this.options.stopbits || '1') as 1 | 1.5 | 2
149
+
150
+ this.serialPort = new SerialPort({
151
+ path: this.options.port,
152
+ baudRate: baudRate,
153
+ parity: parity,
154
+ dataBits: dataBits,
155
+ stopBits: stopBits
156
+ }, (err) => {
157
+ if (err) {
158
+ console.log('Error: ', err.message)
159
+ }
160
+ })
161
+
162
+ this.serialPort.on('data', (data: Buffer<ArrayBuffer>) => {
163
+ for (let i = 0; i < data.length; i++) {
164
+ this.machine.onReceive(data[i])
165
+ }
166
+ })
167
+
168
+ this.machine.transmit = (data: number) => {
169
+ if (this.serialPort && this.serialPort.isOpen) {
170
+ this.serialPort.write(Buffer.from([data]), (err) => {
171
+ if (err) {
172
+ console.log('Error sending serial data: ', err.message)
173
+ }
174
+ })
175
+ }
176
+ }
177
+ }
178
+
179
+ private setupAudio(): void {
180
+ try {
181
+ this.audioDevice = sdl.audio.openDevice({ type: 'playback' }, {
182
+ channels: AUDIO_CHANNELS as 1,
183
+ frequency: AUDIO_SAMPLE_RATE,
184
+ format: AUDIO_FORMAT as any,
185
+ buffered: AUDIO_BUFFERED,
186
+ })
187
+
188
+ // Configure SoundCard sample rate to match audio device
189
+ this.machine.io7.sampleRate = this.audioDevice.frequency
190
+
191
+ // Connect the Machine's audio callback to the SDL audio device
192
+ this.machine.pushAudioSamples = (samples: Float32Array) => {
193
+ if (!this.audioDevice || this.audioDevice.closed) return
194
+
195
+ const { channels, bytesPerSample } = this.audioDevice
196
+ const buffer = Buffer.alloc(samples.length * channels * bytesPerSample)
197
+ let offset = 0
198
+ for (let i = 0; i < samples.length; i++) {
199
+ for (let ch = 0; ch < channels; ch++) {
200
+ offset = this.audioDevice.writeSample(buffer, samples[i], offset)
201
+ }
202
+ }
203
+ this.audioDevice.enqueue(buffer)
204
+ }
205
+
206
+ this.audioDevice.play()
207
+ console.log(`Audio: ${this.audioDevice.frequency} Hz, ${AUDIO_FORMAT}, buffer ${AUDIO_BUFFERED}`)
208
+ } catch (error) {
209
+ console.error('Failed to initialize audio:', error)
210
+ }
211
+ }
212
+
213
+ private setupWindow(): void {
214
+ this.window = sdl.video.createWindow({
215
+ title: "6502 Emulator",
216
+ width: WIDTH * this.machine.scale,
217
+ height: HEIGHT * this.machine.scale,
218
+ accelerated: true,
219
+ vsync: true
220
+ })
221
+
222
+ this.window.on('keyDown', (event: any) => {
223
+ if (!event.scancode) { return }
224
+ this.machine.onKeyDown(event.scancode)
225
+ })
226
+
227
+ this.window.on('keyUp', (event: any) => {
228
+ if (!event.scancode) { return }
229
+ this.machine.onKeyUp(event.scancode)
230
+ })
231
+
232
+ this.machine.render = (buffer: Buffer) => {
233
+ if (!this.window) { return }
234
+ this.window.render(WIDTH, HEIGHT, WIDTH * 4, 'rgba32', buffer)
235
+ }
236
+
237
+ this.window.on('close', () => this.shutdown())
238
+ }
239
+
240
+ private setupControllers(): void {
241
+ // Controller device add/remove handlers
242
+ (sdl.controller as any).on('deviceAdd', (device: any) => {
243
+ console.log(`Controller added: ${device.name || device.id}`)
244
+
245
+ try {
246
+ const controller = sdl.controller.openDevice(device)
247
+ this.controllers.set(device.id, controller)
248
+
249
+ this.setupControllerHandlers(controller, device)
250
+
251
+ console.log(`Controller ${device.name || device.id} opened successfully`)
252
+ } catch (error) {
253
+ console.error(`Failed to open controller ${device.name || device.id}:`, error)
254
+ }
255
+ })
256
+
257
+ ;(sdl.controller as any).on('deviceRemove', (device: any) => {
258
+ console.log(`Controller removed: ${device.name || device.id}`)
259
+
260
+ const controller = this.controllers.get(device.id)
261
+ if (controller && !controller.closed) {
262
+ controller.close()
263
+ }
264
+ this.controllers.delete(device.id)
265
+
266
+ // Clear joystick state when all controllers are removed
267
+ if (this.controllers.size === 0) {
268
+ this.joystickButtonState = 0x00
269
+ this.machine.onJoystick(this.joystickButtonState)
270
+ }
271
+ })
272
+
273
+ // Initialize controllers - detect any already connected
274
+ console.log('Scanning for controllers...')
275
+ const devices = sdl.controller.devices
276
+ if (devices && devices.length > 0) {
277
+ console.log(`Found ${devices.length} controller(s)`)
278
+ devices.forEach((device: any) => {
279
+ try {
280
+ const controller = sdl.controller.openDevice(device)
281
+ this.controllers.set(device.id, controller)
282
+
283
+ this.setupControllerHandlers(controller, device)
284
+
285
+ console.log(`Controller ${device.name || device.id} opened successfully`)
286
+ } catch (error) {
287
+ console.error(`Failed to open controller ${device.name || device.id}:`, error)
288
+ }
289
+ })
290
+ } else {
291
+ console.log('No controllers found')
292
+ }
293
+ }
294
+
295
+ private setupControllerHandlers(controller: any, device: any): void {
296
+ (controller as any).on('buttonDown', (button: string) => {
297
+ switch (button) {
298
+ case 'dpadUp':
299
+ this.joystickButtonState |= BUTTON_UP
300
+ break
301
+ case 'dpadDown':
302
+ this.joystickButtonState |= BUTTON_DOWN
303
+ break
304
+ case 'dpadLeft':
305
+ this.joystickButtonState |= BUTTON_LEFT
306
+ break
307
+ case 'dpadRight':
308
+ this.joystickButtonState |= BUTTON_RIGHT
309
+ break
310
+ case 'a':
311
+ this.joystickButtonState |= BUTTON_A
312
+ break
313
+ case 'b':
314
+ this.joystickButtonState |= BUTTON_B
315
+ break
316
+ case 'back':
317
+ this.joystickButtonState |= BUTTON_SELECT
318
+ break
319
+ case 'start':
320
+ this.joystickButtonState |= BUTTON_START
321
+ break
322
+ }
323
+ this.machine.onJoystick(this.joystickButtonState)
324
+ })
325
+
326
+ ;(controller as any).on('buttonUp', (button: string) => {
327
+ switch (button) {
328
+ case 'dpadUp':
329
+ this.joystickButtonState &= ~BUTTON_UP
330
+ break
331
+ case 'dpadDown':
332
+ this.joystickButtonState &= ~BUTTON_DOWN
333
+ break
334
+ case 'dpadLeft':
335
+ this.joystickButtonState &= ~BUTTON_LEFT
336
+ break
337
+ case 'dpadRight':
338
+ this.joystickButtonState &= ~BUTTON_RIGHT
339
+ break
340
+ case 'a':
341
+ this.joystickButtonState &= ~BUTTON_A
342
+ break
343
+ case 'b':
344
+ this.joystickButtonState &= ~BUTTON_B
345
+ break
346
+ case 'back':
347
+ this.joystickButtonState &= ~BUTTON_SELECT
348
+ break
349
+ case 'start':
350
+ this.joystickButtonState &= ~BUTTON_START
351
+ break
352
+ }
353
+ this.machine.onJoystick(this.joystickButtonState)
354
+ })
355
+
356
+ controller.on('axisMotion', ({ axis, value }: { axis: string; value: number }) => {
357
+ if (axis === 'leftStickX') {
358
+ if (value < -AXIS_THRESHOLD) {
359
+ this.joystickButtonState |= BUTTON_LEFT
360
+ this.joystickButtonState &= ~BUTTON_RIGHT
361
+ } else if (value > AXIS_THRESHOLD) {
362
+ this.joystickButtonState |= BUTTON_RIGHT
363
+ this.joystickButtonState &= ~BUTTON_LEFT
364
+ } else {
365
+ this.joystickButtonState &= ~(BUTTON_LEFT | BUTTON_RIGHT)
366
+ }
367
+ this.machine.onJoystick(this.joystickButtonState)
368
+ } else if (axis === 'leftStickY') {
369
+ if (value < -AXIS_THRESHOLD) {
370
+ this.joystickButtonState |= BUTTON_UP
371
+ this.joystickButtonState &= ~BUTTON_DOWN
372
+ } else if (value > AXIS_THRESHOLD) {
373
+ this.joystickButtonState |= BUTTON_DOWN
374
+ this.joystickButtonState &= ~BUTTON_UP
375
+ } else {
376
+ this.joystickButtonState &= ~(BUTTON_UP | BUTTON_DOWN)
377
+ }
378
+ this.machine.onJoystick(this.joystickButtonState)
379
+ }
380
+ })
381
+
382
+ controller.on('close', () => {
383
+ console.log(`Controller closed: ${device.name || device.id}`)
384
+ this.controllers.delete(device.id)
385
+ })
386
+ }
387
+
388
+ private shutdown(): void {
389
+ // Close all connected controllers
390
+ for (const [id, controller] of this.controllers.entries()) {
391
+ if (!controller.closed) {
392
+ console.log(`Closing controller ${id}`)
393
+ controller.close()
394
+ }
395
+ }
396
+ this.controllers.clear()
397
+
398
+ // Close the audio device
399
+ if (this.audioDevice && !this.audioDevice.closed) {
400
+ this.audioDevice.pause()
401
+ this.audioDevice.close()
402
+ }
403
+
404
+ if (this.serialPort && this.serialPort.isOpen) {
405
+ this.serialPort.close((err) => {
406
+ if (err) {
407
+ console.log('Error closing serial port: ', err.message)
408
+ }
409
+ })
410
+ }
411
+ this.machine.end()
412
+
413
+ const uptime = Date.now() - this.machine.startTime
414
+
415
+ console.log()
416
+ console.log('Result:')
417
+ console.table({
418
+ 'Time Elapsed': uptime / 1000,
419
+ 'CPU Cycles': this.machine.cpu.cycles,
420
+ 'Frames': this.machine.frames,
421
+ 'Avg FPS': parseFloat((this.machine.frames / (uptime / 1000)).toFixed(2))
422
+ })
423
+
424
+ // Save storage data if path was provided
425
+ if (this.options.storage) {
426
+ this.machine.io4.saveToFile(this.options.storage).then(() => {
427
+ process.exit(0)
428
+ }).catch(() => {
429
+ process.exit(1)
430
+ })
431
+ } else {
432
+ process.exit(0)
433
+ }
434
+ }
435
+
436
+ start(): void {
437
+ this.machine.start()
438
+ }
439
+ }
440
+
441
+ // Parse command line arguments
442
+ const program = new Command()
443
+ program
444
+ .name('ac6502')
445
+ .description('Emulator for the A.C. Wright 6502 project.')
446
+ .version(VERSION, '-v, --version', 'Output the current emulator version')
447
+ .helpOption('-h, --help', 'Output help / options')
448
+ .option('-c, --cart <path>', 'Path to 32K Cart binary file')
449
+ .option('-f, --freq <freq>', 'Set the clock frequency in Hz', '2000000')
450
+ .option('-r, --rom <path>', 'Path to 32K ROM binary file')
451
+ .option('-s, --scale <scale>', 'Set the emulator scale', '2')
452
+ .option('-b, --baudrate <baudrate>', 'Baud Rate', '9600')
453
+ .option('-a, --parity <parity>', 'Parity (odd | even | none)', 'none')
454
+ .option('-d, --databits <databits>', 'Data Bits (5 | 6 | 7 | 8)', '8')
455
+ .option('-t, --stopbits <stopbits>', 'Stop Bits (1 | 1.5 | 2)', '1')
456
+ .option('-p, --port <port>', 'Path to the serial port (e.g., /dev/ttyUSB0)')
457
+ .option('-S, --storage <path>', 'Path to storage data file for Compact Flash card persistence')
458
+ .addHelpText('beforeAll', figlet.textSync('6502 Emulator', { font: 'cricket' }) + '\n' + `Version: ${VERSION} | A.C. Wright Design\n`)
459
+ .parse(process.argv)
460
+
461
+ const options = program.opts()
462
+
463
+ // Main initialization function
464
+ async function main() {
465
+ const emulator = new Emulator(options)
466
+ await emulator.initialize()
467
+ emulator.start()
468
+ }
469
+
470
+ // Run the main function
471
+ main().catch((error) => {
472
+ console.error('Error:', error)
473
+ process.exit(1)
474
+ })