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.
- package/LICENSE +21 -0
- package/README.md +261 -0
- package/dist/components/CPU.js +1170 -0
- package/dist/components/CPU.js.map +1 -0
- package/dist/components/Cart.js +23 -0
- package/dist/components/Cart.js.map +1 -0
- package/dist/components/IO/Empty.js +19 -0
- package/dist/components/IO/Empty.js.map +1 -0
- package/dist/components/IO/GPIOAttachments/GPIOAttachment.js +71 -0
- package/dist/components/IO/GPIOAttachments/GPIOAttachment.js.map +1 -0
- package/dist/components/IO/GPIOAttachments/GPIOJoystickAttachment.js +90 -0
- package/dist/components/IO/GPIOAttachments/GPIOJoystickAttachment.js.map +1 -0
- package/dist/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.js +489 -0
- package/dist/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.js.map +1 -0
- package/dist/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.js +274 -0
- package/dist/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.js.map +1 -0
- package/dist/components/IO/GPIOCard.js +597 -0
- package/dist/components/IO/GPIOCard.js.map +1 -0
- package/dist/components/IO/InputBoard.js +19 -0
- package/dist/components/IO/InputBoard.js.map +1 -0
- package/dist/components/IO/LCDCard.js +19 -0
- package/dist/components/IO/LCDCard.js.map +1 -0
- package/dist/components/IO/RAMCard.js +63 -0
- package/dist/components/IO/RAMCard.js.map +1 -0
- package/dist/components/IO/RTCCard.js +483 -0
- package/dist/components/IO/RTCCard.js.map +1 -0
- package/dist/components/IO/SerialCard.js +282 -0
- package/dist/components/IO/SerialCard.js.map +1 -0
- package/dist/components/IO/SoundCard.js +620 -0
- package/dist/components/IO/SoundCard.js.map +1 -0
- package/dist/components/IO/StorageCard.js +428 -0
- package/dist/components/IO/StorageCard.js.map +1 -0
- package/dist/components/IO/VGACard.js +9 -0
- package/dist/components/IO/VGACard.js.map +1 -0
- package/dist/components/IO/VideoCard.js +623 -0
- package/dist/components/IO/VideoCard.js.map +1 -0
- package/dist/components/IO.js +3 -0
- package/dist/components/IO.js.map +1 -0
- package/dist/components/Machine.js +310 -0
- package/dist/components/Machine.js.map +1 -0
- package/dist/components/RAM.js +24 -0
- package/dist/components/RAM.js.map +1 -0
- package/dist/components/ROM.js +23 -0
- package/dist/components/ROM.js.map +1 -0
- package/dist/index.js +441 -0
- package/dist/index.js.map +1 -0
- package/dist/tests/CPU.test.js +1626 -0
- package/dist/tests/CPU.test.js.map +1 -0
- package/dist/tests/Cart.test.js +119 -0
- package/dist/tests/Cart.test.js.map +1 -0
- package/dist/tests/IO/GPIOAttachments/GPIOAttachment.test.js +339 -0
- package/dist/tests/IO/GPIOAttachments/GPIOAttachment.test.js.map +1 -0
- package/dist/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.js +126 -0
- package/dist/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.js.map +1 -0
- package/dist/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.js +779 -0
- package/dist/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.js.map +1 -0
- package/dist/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.js +355 -0
- package/dist/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.js.map +1 -0
- package/dist/tests/IO/GPIOCard.test.js +503 -0
- package/dist/tests/IO/GPIOCard.test.js.map +1 -0
- package/dist/tests/IO/RAMCard.test.js +229 -0
- package/dist/tests/IO/RAMCard.test.js.map +1 -0
- package/dist/tests/IO/RTCCard.test.js +177 -0
- package/dist/tests/IO/RTCCard.test.js.map +1 -0
- package/dist/tests/IO/SerialCard.test.js +423 -0
- package/dist/tests/IO/SerialCard.test.js.map +1 -0
- package/dist/tests/IO/SoundCard.test.js +528 -0
- package/dist/tests/IO/SoundCard.test.js.map +1 -0
- package/dist/tests/IO/StorageCard.test.js +647 -0
- package/dist/tests/IO/StorageCard.test.js.map +1 -0
- package/dist/tests/IO/VideoCard.test.js +549 -0
- package/dist/tests/IO/VideoCard.test.js.map +1 -0
- package/dist/tests/Machine.test.js +383 -0
- package/dist/tests/Machine.test.js.map +1 -0
- package/dist/tests/RAM.test.js +160 -0
- package/dist/tests/RAM.test.js.map +1 -0
- package/dist/tests/ROM.test.js +123 -0
- package/dist/tests/ROM.test.js.map +1 -0
- package/jest.config.cjs +9 -0
- package/package.json +43 -0
- package/src/components/CPU.ts +1371 -0
- package/src/components/Cart.ts +20 -0
- package/src/components/IO/GPIOAttachments/GPIOAttachment.ts +189 -0
- package/src/components/IO/GPIOAttachments/GPIOJoystickAttachment.ts +99 -0
- package/src/components/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.ts +465 -0
- package/src/components/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.ts +287 -0
- package/src/components/IO/GPIOCard.ts +677 -0
- package/src/components/IO/RAMCard.ts +68 -0
- package/src/components/IO/RTCCard.ts +518 -0
- package/src/components/IO/SerialCard.ts +335 -0
- package/src/components/IO/SoundCard.ts +711 -0
- package/src/components/IO/StorageCard.ts +473 -0
- package/src/components/IO/VideoCard.ts +730 -0
- package/src/components/IO.ts +11 -0
- package/src/components/Machine.ts +364 -0
- package/src/components/RAM.ts +23 -0
- package/src/components/ROM.ts +19 -0
- package/src/index.ts +474 -0
- package/src/tests/CPU.test.ts +2045 -0
- package/src/tests/Cart.test.ts +149 -0
- package/src/tests/IO/GPIOAttachments/GPIOAttachment.test.ts +413 -0
- package/src/tests/IO/GPIOAttachments/GPIOJoystickAttachment.test.ts +147 -0
- package/src/tests/IO/GPIOAttachments/GPIOKeyboardEncoderAttachment.test.ts +961 -0
- package/src/tests/IO/GPIOAttachments/GPIOKeyboardMatrixAttachment.test.ts +449 -0
- package/src/tests/IO/GPIOCard.test.ts +644 -0
- package/src/tests/IO/RAMCard.test.ts +284 -0
- package/src/tests/IO/RTCCard.test.ts +222 -0
- package/src/tests/IO/SerialCard.test.ts +530 -0
- package/src/tests/IO/SoundCard.test.ts +659 -0
- package/src/tests/IO/StorageCard.test.ts +787 -0
- package/src/tests/IO/VideoCard.test.ts +668 -0
- package/src/tests/Machine.test.ts +437 -0
- package/src/tests/RAM.test.ts +196 -0
- package/src/tests/ROM.test.ts +154 -0
- 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
|
+
})
|