ac6502 1.11.0 → 1.13.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/README.md +16 -94
- package/dist/components/Machine.d.ts +2 -7
- package/dist/components/Machine.js +51 -185
- package/dist/components/Machine.js.map +1 -1
- package/dist/index.js +37 -118
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +0 -2
- package/dist/lib.js +1 -5
- package/dist/lib.js.map +1 -1
- package/dist/tests/Machine.test.js +1 -1
- package/dist/tests/Machine.test.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Machine.ts +59 -210
- package/src/index.ts +34 -111
- package/src/lib.ts +0 -2
- package/src/tests/Machine.test.ts +1 -1
- package/src/components/IO/Attachments/KeypadAttachment.ts +0 -153
- package/src/components/IO/Attachments/LCDAttachment.ts +0 -791
- package/src/tests/IO/Attachments/KeypadAttachment.test.ts +0 -389
- package/src/tests/IO/Attachments/LCDAttachment.test.ts +0 -795
|
@@ -12,9 +12,6 @@ import { Video } from './IO/Video'
|
|
|
12
12
|
import { KeyboardMatrixAttachment } from './IO/Attachments/KeyboardMatrixAttachment'
|
|
13
13
|
import { KeyboardEncoderAttachment } from './IO/Attachments/KeyboardEncoderAttachment'
|
|
14
14
|
import { JoystickAttachment } from './IO/Attachments/JoystickAttachment'
|
|
15
|
-
import { LCDAttachment } from './IO/Attachments/LCDAttachment'
|
|
16
|
-
import { KeypadAttachment } from './IO/Attachments/KeypadAttachment'
|
|
17
|
-
import { Empty } from './IO/Empty'
|
|
18
15
|
import { IO } from './IO'
|
|
19
16
|
|
|
20
17
|
export class Machine {
|
|
@@ -29,14 +26,14 @@ export class Machine {
|
|
|
29
26
|
rom: ROM
|
|
30
27
|
cart?: Cart
|
|
31
28
|
|
|
32
|
-
io1
|
|
33
|
-
io2
|
|
34
|
-
io3
|
|
35
|
-
io4
|
|
36
|
-
io5
|
|
37
|
-
io6
|
|
38
|
-
io7
|
|
39
|
-
io8
|
|
29
|
+
io1!: IO
|
|
30
|
+
io2!: IO
|
|
31
|
+
io3!: IO
|
|
32
|
+
io4!: IO
|
|
33
|
+
io5!: IO
|
|
34
|
+
io6!: IO
|
|
35
|
+
io7!: IO
|
|
36
|
+
io8!: IO
|
|
40
37
|
|
|
41
38
|
// VIA Attachments
|
|
42
39
|
keyboardMatrixAttachment?: KeyboardMatrixAttachment
|
|
@@ -44,11 +41,6 @@ export class Machine {
|
|
|
44
41
|
joystickAttachmentA?: JoystickAttachment
|
|
45
42
|
joystickAttachmentB?: JoystickAttachment
|
|
46
43
|
|
|
47
|
-
// KIM mode attachments
|
|
48
|
-
lcdAttachment?: LCDAttachment
|
|
49
|
-
keypadAttachment?: KeypadAttachment
|
|
50
|
-
|
|
51
|
-
target: string
|
|
52
44
|
isRunning: boolean = false
|
|
53
45
|
frequency: number = 1000000 // 1 MHz
|
|
54
46
|
scale: number = 2
|
|
@@ -64,194 +56,62 @@ export class Machine {
|
|
|
64
56
|
// Initialization
|
|
65
57
|
//
|
|
66
58
|
|
|
67
|
-
constructor(
|
|
68
|
-
this.target = target
|
|
59
|
+
constructor() {
|
|
69
60
|
this.cpu = new CPU(this.read.bind(this), this.write.bind(this))
|
|
70
61
|
this.ram = new RAM()
|
|
71
62
|
this.rom = new ROM()
|
|
72
63
|
|
|
73
|
-
this.
|
|
74
|
-
this.io2 = new Empty()
|
|
75
|
-
this.io3 = new Empty()
|
|
76
|
-
this.io4 = new Empty()
|
|
77
|
-
this.io5 = new Empty()
|
|
78
|
-
this.io6 = new Empty()
|
|
79
|
-
this.io7 = new Empty()
|
|
80
|
-
this.io8 = new Empty()
|
|
81
|
-
|
|
82
|
-
this.configureTarget(target)
|
|
64
|
+
this.configure()
|
|
83
65
|
|
|
84
66
|
this.startTime = Date.now()
|
|
85
67
|
this.cpu.reset()
|
|
86
68
|
}
|
|
87
69
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
this.io5 = acia
|
|
92
|
-
|
|
93
|
-
// Connect ACIA transmit callback
|
|
94
|
-
acia.transmit = (data: number) => {
|
|
95
|
-
if (this.transmit) {
|
|
96
|
-
this.transmit(data)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
this.io1 = new Empty()
|
|
101
|
-
this.io2 = new Empty()
|
|
102
|
-
this.io3 = new Empty()
|
|
103
|
-
this.io4 = new Empty()
|
|
104
|
-
this.io6 = new Empty()
|
|
105
|
-
this.io7 = new Empty()
|
|
106
|
-
|
|
107
|
-
const via = new VIA()
|
|
108
|
-
this.io8 = via
|
|
109
|
-
|
|
110
|
-
// Create KIM GPIO Attachments
|
|
111
|
-
this.lcdAttachment = new LCDAttachment(16, 2, 10)
|
|
112
|
-
this.keypadAttachment = new KeypadAttachment(true, 20)
|
|
113
|
-
|
|
114
|
-
// Attach LCD to Port A (control: RS/RW/E on bits 5-7) and Port B (data bus)
|
|
115
|
-
via.attachToPortA(this.lcdAttachment)
|
|
116
|
-
via.attachToPortB(this.lcdAttachment)
|
|
117
|
-
|
|
118
|
-
// Attach keypad to Port A (bits 0-4)
|
|
119
|
-
via.attachToPortA(this.keypadAttachment)
|
|
120
|
-
} else if (target === 'dev') {
|
|
121
|
-
const acia = new ACIA()
|
|
122
|
-
this.io5 = acia
|
|
123
|
-
|
|
124
|
-
// Connect ACIA transmit callback
|
|
125
|
-
acia.transmit = (data: number) => {
|
|
126
|
-
if (this.transmit) {
|
|
127
|
-
this.transmit(data)
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const rtc = new RTC()
|
|
132
|
-
const storage = new Storage()
|
|
133
|
-
const via = new VIA()
|
|
134
|
-
const sound = new Sound()
|
|
135
|
-
const video = new Video()
|
|
136
|
-
|
|
137
|
-
this.io1 = new RAMBank()
|
|
138
|
-
this.io2 = new RAMBank()
|
|
139
|
-
this.io3 = rtc
|
|
140
|
-
this.io4 = storage
|
|
141
|
-
this.io6 = via
|
|
142
|
-
this.io7 = sound
|
|
143
|
-
this.io8 = video
|
|
144
|
-
|
|
145
|
-
// Connect Sound pushSamples callback
|
|
146
|
-
sound.pushSamples = (samples: Float32Array) => {
|
|
147
|
-
if (this.play) {
|
|
148
|
-
this.play(samples)
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Create standard GPIO attachments
|
|
153
|
-
this.keyboardMatrixAttachment = new KeyboardMatrixAttachment(10)
|
|
154
|
-
this.keyboardEncoderAttachment = new KeyboardEncoderAttachment(20)
|
|
155
|
-
this.joystickAttachmentA = new JoystickAttachment(false, 100)
|
|
156
|
-
this.joystickAttachmentB = new JoystickAttachment(false, 100)
|
|
157
|
-
|
|
158
|
-
// Attach peripherals to GPIO Card
|
|
159
|
-
via.attachToPortA(this.keyboardMatrixAttachment)
|
|
160
|
-
via.attachToPortB(this.keyboardMatrixAttachment)
|
|
161
|
-
via.attachToPortA(this.keyboardEncoderAttachment)
|
|
162
|
-
via.attachToPortB(this.keyboardEncoderAttachment)
|
|
163
|
-
via.attachToPortA(this.joystickAttachmentA)
|
|
164
|
-
via.attachToPortB(this.joystickAttachmentB)
|
|
165
|
-
} else if (target === 'vcs') {
|
|
166
|
-
this.io5 = new Empty()
|
|
167
|
-
|
|
168
|
-
const via = new VIA()
|
|
169
|
-
const sound = new Sound()
|
|
170
|
-
const video = new Video()
|
|
171
|
-
|
|
172
|
-
this.io1 = new Empty()
|
|
173
|
-
this.io2 = new Empty()
|
|
174
|
-
this.io3 = new Empty()
|
|
175
|
-
this.io4 = new Empty()
|
|
176
|
-
this.io6 = via
|
|
177
|
-
this.io7 = sound
|
|
178
|
-
this.io8 = video
|
|
179
|
-
|
|
180
|
-
// Connect Sound pushSamples callback
|
|
181
|
-
sound.pushSamples = (samples: Float32Array) => {
|
|
182
|
-
if (this.play) {
|
|
183
|
-
this.play(samples)
|
|
184
|
-
}
|
|
185
|
-
}
|
|
70
|
+
private configure(): void {
|
|
71
|
+
const acia = new ACIA()
|
|
72
|
+
this.io5 = acia
|
|
186
73
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
this.
|
|
190
|
-
|
|
191
|
-
this.joystickAttachmentB = new JoystickAttachment(false, 100)
|
|
192
|
-
|
|
193
|
-
// Attach peripherals to GPIO Card
|
|
194
|
-
via.attachToPortA(this.keyboardMatrixAttachment)
|
|
195
|
-
via.attachToPortB(this.keyboardMatrixAttachment)
|
|
196
|
-
via.attachToPortA(this.keyboardEncoderAttachment)
|
|
197
|
-
via.attachToPortB(this.keyboardEncoderAttachment)
|
|
198
|
-
via.attachToPortA(this.joystickAttachmentA)
|
|
199
|
-
via.attachToPortB(this.joystickAttachmentB)
|
|
200
|
-
} else if (target === 'cob') {
|
|
201
|
-
const acia = new ACIA()
|
|
202
|
-
this.io5 = acia
|
|
203
|
-
|
|
204
|
-
// Connect ACIA transmit callback
|
|
205
|
-
acia.transmit = (data: number) => {
|
|
206
|
-
if (this.transmit) {
|
|
207
|
-
this.transmit(data)
|
|
208
|
-
}
|
|
74
|
+
// Connect ACIA transmit callback
|
|
75
|
+
acia.transmit = (data: number) => {
|
|
76
|
+
if (this.transmit) {
|
|
77
|
+
this.transmit(data)
|
|
209
78
|
}
|
|
79
|
+
}
|
|
210
80
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
81
|
+
const rtc = new RTC()
|
|
82
|
+
const storage = new Storage()
|
|
83
|
+
const via = new VIA()
|
|
84
|
+
const sound = new Sound()
|
|
85
|
+
const video = new Video()
|
|
86
|
+
|
|
87
|
+
this.io1 = new RAMBank()
|
|
88
|
+
this.io2 = new RAMBank()
|
|
89
|
+
this.io3 = rtc
|
|
90
|
+
this.io4 = storage
|
|
91
|
+
this.io6 = via
|
|
92
|
+
this.io7 = sound
|
|
93
|
+
this.io8 = video
|
|
94
|
+
|
|
95
|
+
// Connect Sound pushSamples callback
|
|
96
|
+
sound.pushSamples = (samples: Float32Array) => {
|
|
97
|
+
if (this.play) {
|
|
98
|
+
this.play(samples)
|
|
230
99
|
}
|
|
231
|
-
|
|
232
|
-
// Create standard GPIO attachments
|
|
233
|
-
this.keyboardMatrixAttachment = new KeyboardMatrixAttachment(10)
|
|
234
|
-
this.keyboardEncoderAttachment = new KeyboardEncoderAttachment(20)
|
|
235
|
-
this.joystickAttachmentA = new JoystickAttachment(false, 100)
|
|
236
|
-
this.joystickAttachmentB = new JoystickAttachment(false, 100)
|
|
237
|
-
|
|
238
|
-
// Attach peripherals to GPIO Card
|
|
239
|
-
via.attachToPortA(this.keyboardMatrixAttachment)
|
|
240
|
-
via.attachToPortB(this.keyboardMatrixAttachment)
|
|
241
|
-
via.attachToPortA(this.keyboardEncoderAttachment)
|
|
242
|
-
via.attachToPortB(this.keyboardEncoderAttachment)
|
|
243
|
-
via.attachToPortA(this.joystickAttachmentA)
|
|
244
|
-
via.attachToPortB(this.joystickAttachmentB)
|
|
245
|
-
} else {
|
|
246
|
-
this.io1 = new Empty()
|
|
247
|
-
this.io2 = new Empty()
|
|
248
|
-
this.io3 = new Empty()
|
|
249
|
-
this.io4 = new Empty()
|
|
250
|
-
this.io5 = new Empty()
|
|
251
|
-
this.io6 = new Empty()
|
|
252
|
-
this.io7 = new Empty()
|
|
253
|
-
this.io8 = new Empty()
|
|
254
100
|
}
|
|
101
|
+
|
|
102
|
+
// Create standard GPIO attachments
|
|
103
|
+
this.keyboardMatrixAttachment = new KeyboardMatrixAttachment(10)
|
|
104
|
+
this.keyboardEncoderAttachment = new KeyboardEncoderAttachment(20)
|
|
105
|
+
this.joystickAttachmentA = new JoystickAttachment(false, 100)
|
|
106
|
+
this.joystickAttachmentB = new JoystickAttachment(false, 100)
|
|
107
|
+
|
|
108
|
+
// Attach peripherals to GPIO Card
|
|
109
|
+
via.attachToPortA(this.keyboardMatrixAttachment)
|
|
110
|
+
via.attachToPortB(this.keyboardMatrixAttachment)
|
|
111
|
+
via.attachToPortA(this.keyboardEncoderAttachment)
|
|
112
|
+
via.attachToPortB(this.keyboardEncoderAttachment)
|
|
113
|
+
via.attachToPortA(this.joystickAttachmentA)
|
|
114
|
+
via.attachToPortB(this.joystickAttachmentB)
|
|
255
115
|
}
|
|
256
116
|
|
|
257
117
|
//
|
|
@@ -350,25 +210,17 @@ export class Machine {
|
|
|
350
210
|
}
|
|
351
211
|
|
|
352
212
|
onReceive(data: number): void {
|
|
353
|
-
|
|
354
|
-
(this.io5 as ACIA).onData(data) // Pass data to Serial card
|
|
355
|
-
}
|
|
213
|
+
(this.io5 as ACIA).onData(data) // Pass data to Serial card
|
|
356
214
|
}
|
|
357
215
|
|
|
358
216
|
onKeyDown(scancode: number): void {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
} else {
|
|
362
|
-
this.keyboardMatrixAttachment?.updateKey(scancode, true)
|
|
363
|
-
this.keyboardEncoderAttachment?.updateKey(scancode, true)
|
|
364
|
-
}
|
|
217
|
+
this.keyboardMatrixAttachment?.updateKey(scancode, true)
|
|
218
|
+
this.keyboardEncoderAttachment?.updateKey(scancode, true)
|
|
365
219
|
}
|
|
366
220
|
|
|
367
221
|
onKeyUp(scancode: number): void {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
this.keyboardEncoderAttachment?.updateKey(scancode, false)
|
|
371
|
-
}
|
|
222
|
+
this.keyboardMatrixAttachment?.updateKey(scancode, false)
|
|
223
|
+
this.keyboardEncoderAttachment?.updateKey(scancode, false)
|
|
372
224
|
}
|
|
373
225
|
|
|
374
226
|
onJoystickA(buttons: number): void {
|
|
@@ -408,13 +260,10 @@ export class Machine {
|
|
|
408
260
|
(this as any)._accumulatorMs = accumulator
|
|
409
261
|
}
|
|
410
262
|
|
|
411
|
-
if (this.render
|
|
412
|
-
this.
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const Video = this.io8 as Video
|
|
416
|
-
if (Video.frameReady) {
|
|
417
|
-
Video.frameReady = false
|
|
263
|
+
if (this.render) {
|
|
264
|
+
const video = this.io8 as Video
|
|
265
|
+
if (video.frameReady) {
|
|
266
|
+
video.frameReady = false
|
|
418
267
|
this.render()
|
|
419
268
|
this.frames += 1
|
|
420
269
|
}
|
package/src/index.ts
CHANGED
|
@@ -11,7 +11,7 @@ import sdl from '@kmamal/sdl'
|
|
|
11
11
|
import { readFile, writeFile } from 'fs/promises'
|
|
12
12
|
import { existsSync } from 'fs'
|
|
13
13
|
|
|
14
|
-
const VERSION = '1.
|
|
14
|
+
const VERSION = '1.13.0'
|
|
15
15
|
const WIDTH = 320
|
|
16
16
|
const HEIGHT = 240
|
|
17
17
|
|
|
@@ -37,6 +37,7 @@ const AXIS_THRESHOLD = 0.5
|
|
|
37
37
|
interface EmulatorOptions {
|
|
38
38
|
cart?: string
|
|
39
39
|
freq?: string
|
|
40
|
+
program?: string
|
|
40
41
|
rom?: string
|
|
41
42
|
scale?: string
|
|
42
43
|
baudrate?: string
|
|
@@ -45,7 +46,6 @@ interface EmulatorOptions {
|
|
|
45
46
|
stopbits?: string
|
|
46
47
|
port?: string
|
|
47
48
|
storage?: string
|
|
48
|
-
target?: string
|
|
49
49
|
encoder?: string
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -61,7 +61,7 @@ class Emulator {
|
|
|
61
61
|
|
|
62
62
|
constructor(options: EmulatorOptions) {
|
|
63
63
|
this.options = options
|
|
64
|
-
this.machine = new Machine(
|
|
64
|
+
this.machine = new Machine()
|
|
65
65
|
this.controllers = new Map()
|
|
66
66
|
this.joystickButtonStateA = 0x00
|
|
67
67
|
this.joystickButtonStateB = 0x00
|
|
@@ -78,6 +78,26 @@ class Emulator {
|
|
|
78
78
|
this.setupWindow()
|
|
79
79
|
this.setupControllers()
|
|
80
80
|
this.machine.reset(true)
|
|
81
|
+
await this.loadProgram()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private async loadProgram(): Promise<void> {
|
|
85
|
+
if (this.options.program) {
|
|
86
|
+
const programData = await readFile(this.options.program)
|
|
87
|
+
const programStart = 0x0800
|
|
88
|
+
const programEnd = 0x7FFF
|
|
89
|
+
const maxSize = programEnd - programStart + 1
|
|
90
|
+
if (programData.length > maxSize) {
|
|
91
|
+
console.log(`Error: Program file too large (${programData.length} bytes, max ${maxSize} bytes)`)
|
|
92
|
+
process.exit(1)
|
|
93
|
+
}
|
|
94
|
+
for (let i = 0; i < programData.length; i++) {
|
|
95
|
+
this.machine.ram.write(programStart + i, programData[i])
|
|
96
|
+
}
|
|
97
|
+
console.log(`Loaded Program: ${this.options.program} (${programData.length} bytes at $${programStart.toString(16).toUpperCase().padStart(4, '0')})`)
|
|
98
|
+
} else {
|
|
99
|
+
console.log('Loaded Program: NONE')
|
|
100
|
+
}
|
|
81
101
|
}
|
|
82
102
|
|
|
83
103
|
private validateOptions(): void {
|
|
@@ -111,7 +131,7 @@ class Emulator {
|
|
|
111
131
|
console.log('Loaded Cart: NONE')
|
|
112
132
|
}
|
|
113
133
|
|
|
114
|
-
if (this.options.storage
|
|
134
|
+
if (this.options.storage) {
|
|
115
135
|
if (existsSync(this.options.storage)) {
|
|
116
136
|
const storageData = await readFile(this.options.storage)
|
|
117
137
|
;(this.machine.io4 as Storage).loadData(new Uint8Array(storageData))
|
|
@@ -217,7 +237,6 @@ class Emulator {
|
|
|
217
237
|
}
|
|
218
238
|
|
|
219
239
|
private setupAudio(): void {
|
|
220
|
-
if (this.options.target === 'kim') return
|
|
221
240
|
try {
|
|
222
241
|
this.audioDevice = sdl.audio.openDevice({ type: 'playback' }, {
|
|
223
242
|
channels: AUDIO_CHANNELS as 1,
|
|
@@ -226,10 +245,7 @@ class Emulator {
|
|
|
226
245
|
buffered: AUDIO_BUFFERED,
|
|
227
246
|
})
|
|
228
247
|
|
|
229
|
-
|
|
230
|
-
if (this.options.target !== 'dev') {
|
|
231
|
-
;(this.machine.io7 as Sound).sampleRate = this.audioDevice.frequency
|
|
232
|
-
}
|
|
248
|
+
;(this.machine.io7 as Sound).sampleRate = this.audioDevice.frequency
|
|
233
249
|
|
|
234
250
|
// Connect the Machine's audio callback to the SDL audio device
|
|
235
251
|
this.machine.play = (samples: Float32Array) => {
|
|
@@ -254,29 +270,10 @@ class Emulator {
|
|
|
254
270
|
}
|
|
255
271
|
|
|
256
272
|
private setupWindow(): void {
|
|
257
|
-
const isKIM = this.options.target === 'kim'
|
|
258
|
-
const lcd = this.machine.lcdAttachment
|
|
259
|
-
|
|
260
|
-
// LCD dot-matrix rendering constants
|
|
261
|
-
const DOT_SIZE = 2 // Each LCD dot rendered as DOT_SIZE x DOT_SIZE pixels
|
|
262
|
-
const DOT_GAP = 1 // Gap between dots
|
|
263
|
-
const LCD_PADDING = 8 // Green border padding around the display
|
|
264
|
-
const CELL = DOT_SIZE + DOT_GAP
|
|
265
|
-
|
|
266
|
-
let windowWidth: number
|
|
267
|
-
let windowHeight: number
|
|
268
|
-
if (isKIM && lcd) {
|
|
269
|
-
windowWidth = LCD_PADDING * 2 + lcd.pixelsWidth * CELL
|
|
270
|
-
windowHeight = LCD_PADDING * 2 + lcd.pixelsHeight * CELL
|
|
271
|
-
} else {
|
|
272
|
-
windowWidth = WIDTH
|
|
273
|
-
windowHeight = HEIGHT
|
|
274
|
-
}
|
|
275
|
-
|
|
276
273
|
this.window = sdl.video.createWindow({
|
|
277
|
-
title:
|
|
278
|
-
width:
|
|
279
|
-
height:
|
|
274
|
+
title: '6502 Emulator (COB)',
|
|
275
|
+
width: WIDTH * this.machine.scale,
|
|
276
|
+
height: HEIGHT * this.machine.scale,
|
|
280
277
|
accelerated: true,
|
|
281
278
|
vsync: true
|
|
282
279
|
})
|
|
@@ -291,83 +288,10 @@ class Emulator {
|
|
|
291
288
|
this.machine.onKeyUp(event.scancode)
|
|
292
289
|
})
|
|
293
290
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
const renderHeight = windowHeight
|
|
299
|
-
const rgbaBuffer = Buffer.alloc(renderWidth * renderHeight * 4)
|
|
300
|
-
|
|
301
|
-
// Pre-fill with background color (LCD green for padding + gaps)
|
|
302
|
-
for (let i = 0; i < renderWidth * renderHeight; i++) {
|
|
303
|
-
const off = i * 4
|
|
304
|
-
rgbaBuffer[off] = 0x50
|
|
305
|
-
rgbaBuffer[off + 1] = 0x88
|
|
306
|
-
rgbaBuffer[off + 2] = 0x38
|
|
307
|
-
rgbaBuffer[off + 3] = 0xFF
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
this.machine.render = () => {
|
|
311
|
-
if (!this.window) { return }
|
|
312
|
-
const buf = lcd.buffer
|
|
313
|
-
|
|
314
|
-
// Reset buffer to background color
|
|
315
|
-
for (let i = 0; i < renderWidth * renderHeight; i++) {
|
|
316
|
-
const off = i * 4
|
|
317
|
-
rgbaBuffer[off] = 0x50
|
|
318
|
-
rgbaBuffer[off + 1] = 0x88
|
|
319
|
-
rgbaBuffer[off + 2] = 0x38
|
|
320
|
-
rgbaBuffer[off + 3] = 0xFF
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Render each buffer pixel as a dot block
|
|
324
|
-
for (let by = 0; by < lcdHeight; by++) {
|
|
325
|
-
for (let bx = 0; bx < lcdWidth; bx++) {
|
|
326
|
-
const val = buf[by * lcdWidth + bx]
|
|
327
|
-
|
|
328
|
-
if (val < 0) {
|
|
329
|
-
// Gap pixel - skip, shows background color
|
|
330
|
-
continue
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
let r: number, g: number, b: number
|
|
334
|
-
if (val === 0) {
|
|
335
|
-
// Pixel off - slightly brighter than background for visible dot grid
|
|
336
|
-
r = 0x60; g = 0xA0; b = 0x40
|
|
337
|
-
} else {
|
|
338
|
-
// Pixel on - dark
|
|
339
|
-
r = 0x10; g = 0x20; b = 0x10
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Draw DOT_SIZE x DOT_SIZE block
|
|
343
|
-
const screenX = LCD_PADDING + bx * CELL
|
|
344
|
-
const screenY = LCD_PADDING + by * CELL
|
|
345
|
-
for (let dy = 0; dy < DOT_SIZE; dy++) {
|
|
346
|
-
for (let dx = 0; dx < DOT_SIZE; dx++) {
|
|
347
|
-
const off = ((screenY + dy) * renderWidth + (screenX + dx)) * 4
|
|
348
|
-
rgbaBuffer[off] = r
|
|
349
|
-
rgbaBuffer[off + 1] = g
|
|
350
|
-
rgbaBuffer[off + 2] = b
|
|
351
|
-
rgbaBuffer[off + 3] = 0xFF
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
this.window.render(renderWidth, renderHeight, renderWidth * 4, 'rgba32', rgbaBuffer)
|
|
358
|
-
}
|
|
359
|
-
} else if (this.options.target === 'cob' || this.options.target === 'vcs') {
|
|
360
|
-
const Video = this.machine.io8 as Video
|
|
361
|
-
this.machine.render = () => {
|
|
362
|
-
if (!this.window) { return }
|
|
363
|
-
this.window.render(WIDTH, HEIGHT, WIDTH * 4, 'rgba32', Video.buffer)
|
|
364
|
-
}
|
|
365
|
-
} else if (this.options.target === 'dev') {
|
|
366
|
-
const video = this.machine.io8 as Video
|
|
367
|
-
this.machine.render = () => {
|
|
368
|
-
if (!this.window) { return }
|
|
369
|
-
this.window.render(WIDTH, HEIGHT, WIDTH * 4, 'rgba32', video.buffer)
|
|
370
|
-
}
|
|
291
|
+
const video = this.machine.io8 as Video
|
|
292
|
+
this.machine.render = () => {
|
|
293
|
+
if (!this.window) { return }
|
|
294
|
+
this.window.render(WIDTH, HEIGHT, WIDTH * 4, 'rgba32', video.buffer)
|
|
371
295
|
}
|
|
372
296
|
|
|
373
297
|
this.window.on('close', () => this.shutdown())
|
|
@@ -379,7 +303,6 @@ class Emulator {
|
|
|
379
303
|
}
|
|
380
304
|
|
|
381
305
|
private setupControllers(): void {
|
|
382
|
-
if (this.options.target === 'kim') return
|
|
383
306
|
// Controller device add/remove handlers
|
|
384
307
|
(sdl.controller as any).on('deviceAdd', (device: any) => {
|
|
385
308
|
console.log(`Controller added: ${device.name || device.id}`)
|
|
@@ -557,7 +480,7 @@ class Emulator {
|
|
|
557
480
|
})
|
|
558
481
|
|
|
559
482
|
// Save storage data if path was provided
|
|
560
|
-
if (this.options.storage
|
|
483
|
+
if (this.options.storage) {
|
|
561
484
|
const storageData = (this.machine.io4 as Storage).getData()
|
|
562
485
|
writeFile(this.options.storage, storageData).then(() => {
|
|
563
486
|
console.log(`Storage saved to: ${this.options.storage}`)
|
|
@@ -588,12 +511,12 @@ program
|
|
|
588
511
|
.addOption(new Option('-c, --cart <path>', 'Path to 32K Cart binary file'))
|
|
589
512
|
.addOption(new Option('-d, --databits <databits>', 'Data Bits (5 | 6 | 7 | 8)').default('8'))
|
|
590
513
|
.addOption(new Option('-f, --freq <freq>', 'Set the clock frequency in Hz').default('1000000'))
|
|
514
|
+
.addOption(new Option('-g, --program <path>', 'Path to program binary file (loaded into RAM at $0800-$7FFF)'))
|
|
591
515
|
.addOption(new Option('-p, --port <port>', 'Path to the serial port (e.g., /dev/ttyUSB0)'))
|
|
592
516
|
.addOption(new Option('-r, --rom <path>', 'Path to 32K ROM binary file'))
|
|
593
517
|
.addOption(new Option('-s, --scale <scale>', 'Set the emulator scale').default('2'))
|
|
594
518
|
.addOption(new Option('-S, --storage <path>', 'Path to storage data file for Compact Flash card persistence'))
|
|
595
519
|
.addOption(new Option('-t, --stopbits <stopbits>', 'Stop Bits (1 | 1.5 | 2)').default('1'))
|
|
596
|
-
.addOption(new Option('-T, --target <target>', 'System target').choices(['cob', 'vcs', 'kim', 'dev']).default('cob'))
|
|
597
520
|
.addOption(new Option('-e, --encoder <mode>', 'Keyboard encoder active port (ps2 = Port A / CA1, matrix = Port B / CB1)').choices(['ps2', 'matrix', 'both']).default('matrix'))
|
|
598
521
|
.addHelpText('beforeAll', figlet.textSync('6502 Emulator', { font: 'cricket' }) + '\n' + `Version: ${VERSION} | A.C. Wright Design\n`)
|
|
599
522
|
.parse(process.argv)
|
package/src/lib.ts
CHANGED
|
@@ -22,5 +22,3 @@ export { AttachmentBase } from './components/IO/Attachments/Attachment'
|
|
|
22
22
|
export { JoystickAttachment } from './components/IO/Attachments/JoystickAttachment'
|
|
23
23
|
export { KeyboardEncoderAttachment } from './components/IO/Attachments/KeyboardEncoderAttachment'
|
|
24
24
|
export { KeyboardMatrixAttachment } from './components/IO/Attachments/KeyboardMatrixAttachment'
|
|
25
|
-
export { KeypadAttachment } from './components/IO/Attachments/KeypadAttachment'
|
|
26
|
-
export { LCDAttachment } from './components/IO/Attachments/LCDAttachment'
|