ac6502 1.11.0 → 1.12.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 +12 -97
- 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 +14 -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 +12 -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.12.0'
|
|
15
15
|
const WIDTH = 320
|
|
16
16
|
const HEIGHT = 240
|
|
17
17
|
|
|
@@ -45,7 +45,6 @@ interface EmulatorOptions {
|
|
|
45
45
|
stopbits?: string
|
|
46
46
|
port?: string
|
|
47
47
|
storage?: string
|
|
48
|
-
target?: string
|
|
49
48
|
encoder?: string
|
|
50
49
|
}
|
|
51
50
|
|
|
@@ -61,7 +60,7 @@ class Emulator {
|
|
|
61
60
|
|
|
62
61
|
constructor(options: EmulatorOptions) {
|
|
63
62
|
this.options = options
|
|
64
|
-
this.machine = new Machine(
|
|
63
|
+
this.machine = new Machine()
|
|
65
64
|
this.controllers = new Map()
|
|
66
65
|
this.joystickButtonStateA = 0x00
|
|
67
66
|
this.joystickButtonStateB = 0x00
|
|
@@ -111,7 +110,7 @@ class Emulator {
|
|
|
111
110
|
console.log('Loaded Cart: NONE')
|
|
112
111
|
}
|
|
113
112
|
|
|
114
|
-
if (this.options.storage
|
|
113
|
+
if (this.options.storage) {
|
|
115
114
|
if (existsSync(this.options.storage)) {
|
|
116
115
|
const storageData = await readFile(this.options.storage)
|
|
117
116
|
;(this.machine.io4 as Storage).loadData(new Uint8Array(storageData))
|
|
@@ -217,7 +216,6 @@ class Emulator {
|
|
|
217
216
|
}
|
|
218
217
|
|
|
219
218
|
private setupAudio(): void {
|
|
220
|
-
if (this.options.target === 'kim') return
|
|
221
219
|
try {
|
|
222
220
|
this.audioDevice = sdl.audio.openDevice({ type: 'playback' }, {
|
|
223
221
|
channels: AUDIO_CHANNELS as 1,
|
|
@@ -226,10 +224,7 @@ class Emulator {
|
|
|
226
224
|
buffered: AUDIO_BUFFERED,
|
|
227
225
|
})
|
|
228
226
|
|
|
229
|
-
|
|
230
|
-
if (this.options.target !== 'dev') {
|
|
231
|
-
;(this.machine.io7 as Sound).sampleRate = this.audioDevice.frequency
|
|
232
|
-
}
|
|
227
|
+
;(this.machine.io7 as Sound).sampleRate = this.audioDevice.frequency
|
|
233
228
|
|
|
234
229
|
// Connect the Machine's audio callback to the SDL audio device
|
|
235
230
|
this.machine.play = (samples: Float32Array) => {
|
|
@@ -254,29 +249,10 @@ class Emulator {
|
|
|
254
249
|
}
|
|
255
250
|
|
|
256
251
|
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
252
|
this.window = sdl.video.createWindow({
|
|
277
|
-
title:
|
|
278
|
-
width:
|
|
279
|
-
height:
|
|
253
|
+
title: '6502 Emulator (COB)',
|
|
254
|
+
width: WIDTH * this.machine.scale,
|
|
255
|
+
height: HEIGHT * this.machine.scale,
|
|
280
256
|
accelerated: true,
|
|
281
257
|
vsync: true
|
|
282
258
|
})
|
|
@@ -291,83 +267,10 @@ class Emulator {
|
|
|
291
267
|
this.machine.onKeyUp(event.scancode)
|
|
292
268
|
})
|
|
293
269
|
|
|
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
|
-
}
|
|
270
|
+
const video = this.machine.io8 as Video
|
|
271
|
+
this.machine.render = () => {
|
|
272
|
+
if (!this.window) { return }
|
|
273
|
+
this.window.render(WIDTH, HEIGHT, WIDTH * 4, 'rgba32', video.buffer)
|
|
371
274
|
}
|
|
372
275
|
|
|
373
276
|
this.window.on('close', () => this.shutdown())
|
|
@@ -379,7 +282,6 @@ class Emulator {
|
|
|
379
282
|
}
|
|
380
283
|
|
|
381
284
|
private setupControllers(): void {
|
|
382
|
-
if (this.options.target === 'kim') return
|
|
383
285
|
// Controller device add/remove handlers
|
|
384
286
|
(sdl.controller as any).on('deviceAdd', (device: any) => {
|
|
385
287
|
console.log(`Controller added: ${device.name || device.id}`)
|
|
@@ -557,7 +459,7 @@ class Emulator {
|
|
|
557
459
|
})
|
|
558
460
|
|
|
559
461
|
// Save storage data if path was provided
|
|
560
|
-
if (this.options.storage
|
|
462
|
+
if (this.options.storage) {
|
|
561
463
|
const storageData = (this.machine.io4 as Storage).getData()
|
|
562
464
|
writeFile(this.options.storage, storageData).then(() => {
|
|
563
465
|
console.log(`Storage saved to: ${this.options.storage}`)
|
|
@@ -593,7 +495,6 @@ program
|
|
|
593
495
|
.addOption(new Option('-s, --scale <scale>', 'Set the emulator scale').default('2'))
|
|
594
496
|
.addOption(new Option('-S, --storage <path>', 'Path to storage data file for Compact Flash card persistence'))
|
|
595
497
|
.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
498
|
.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
499
|
.addHelpText('beforeAll', figlet.textSync('6502 Emulator', { font: 'cricket' }) + '\n' + `Version: ${VERSION} | A.C. Wright Design\n`)
|
|
599
500
|
.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'
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { AttachmentBase } from './Attachment'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* USB HID keycode to keypad value mapping
|
|
5
|
-
* Maps USB HID usage IDs to the 5-bit keypad codes ($00-$17) per the 6502 Keypad Mapping table
|
|
6
|
-
*
|
|
7
|
-
* Keypad layout (4 columns × 6 rows = 24 keys):
|
|
8
|
-
* $00 = ◄ $01=1 $02=2 $03=3
|
|
9
|
-
* $04 = 4 $05=5 $06=6 $07=7
|
|
10
|
-
* $08 = 8 $09=9 $0A=0 $0B=►
|
|
11
|
-
* $0C = F $0D=E $0E=D $0F=C
|
|
12
|
-
* $10 = ESC $11=INS $12=PGUP $13=A
|
|
13
|
-
* $14 = ▲/Enter $15=DEL $16=PGDN $17=B
|
|
14
|
-
*/
|
|
15
|
-
const USB_HID_TO_KEYPAD: { [key: number]: number } = {
|
|
16
|
-
0x50: 0x00, // Left Arrow → ◄
|
|
17
|
-
0x1E: 0x01, // 1
|
|
18
|
-
0x1F: 0x02, // 2
|
|
19
|
-
0x20: 0x03, // 3
|
|
20
|
-
0x21: 0x04, // 4
|
|
21
|
-
0x22: 0x05, // 5
|
|
22
|
-
0x23: 0x06, // 6
|
|
23
|
-
0x24: 0x07, // 7
|
|
24
|
-
0x25: 0x08, // 8
|
|
25
|
-
0x26: 0x09, // 9
|
|
26
|
-
0x27: 0x0A, // 0
|
|
27
|
-
0x4F: 0x0B, // Right Arrow → ►
|
|
28
|
-
0x09: 0x0C, // f → F
|
|
29
|
-
0x08: 0x0D, // e → E
|
|
30
|
-
0x07: 0x0E, // d → D
|
|
31
|
-
0x06: 0x0F, // c → C
|
|
32
|
-
0x29: 0x10, // Escape → ESC
|
|
33
|
-
0x49: 0x11, // Insert → INS
|
|
34
|
-
0x4B: 0x12, // Page Up → PGUP
|
|
35
|
-
0x04: 0x13, // a → A
|
|
36
|
-
0x52: 0x14, // Up Arrow → ▲
|
|
37
|
-
0x28: 0x14, // Enter → ▲
|
|
38
|
-
0x4C: 0x15, // Delete → DEL
|
|
39
|
-
0x4E: 0x16, // Page Down → PGDN
|
|
40
|
-
0x05: 0x17, // b → B
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* KeypadAttachment - Emulates a 4×6 matrix keypad with a built-in hardware encoder
|
|
45
|
-
*
|
|
46
|
-
* The encoder converts a key press into a 5-bit code (PA0–PA4) that appears on the GPIO
|
|
47
|
-
* port. Bits 5–7 are never driven by the keypad and always read as 0 when data is present.
|
|
48
|
-
*
|
|
49
|
-
* Behaviour mirrors a typical 74C922-style encoder:
|
|
50
|
-
* - On key press → the 5-bit keypad code is latched and a CA1/CB1 interrupt is asserted
|
|
51
|
-
* - On port read → the latched code is returned on bits 0–4 (bits 5–7 = 0)
|
|
52
|
-
* - clearInterrupts → clears the interrupt and the data-ready latch
|
|
53
|
-
* - Key releases → ignored (encoder only reports on the falling edge of a keypress)
|
|
54
|
-
*
|
|
55
|
-
* The attachment may be wired to either Port A or Port B via the constructor parameter.
|
|
56
|
-
* CA1/CB1 is the DA (Data Available) interrupt line from the 74C922.
|
|
57
|
-
* CA2/CB2 is connected to the 74C922 OE (Output Enable) pin; data is only driven onto the
|
|
58
|
-
* bus when OE is asserted LOW by the 6522.
|
|
59
|
-
*/
|
|
60
|
-
export class KeypadAttachment extends AttachmentBase {
|
|
61
|
-
private keypadValue: number = 0x00
|
|
62
|
-
private dataReady: boolean = false
|
|
63
|
-
private interruptPending: boolean = false
|
|
64
|
-
private readonly attachedToPortA: boolean
|
|
65
|
-
|
|
66
|
-
// OE state: CA2 for Port A, CB2 for Port B. HIGH = output disabled (default).
|
|
67
|
-
private oeState: boolean = true
|
|
68
|
-
|
|
69
|
-
constructor(attachToPortA: boolean = true, priority: number = 0) {
|
|
70
|
-
super(priority, false, false, false, false)
|
|
71
|
-
this.attachedToPortA = attachToPortA
|
|
72
|
-
this.reset()
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
reset(): void {
|
|
76
|
-
super.reset()
|
|
77
|
-
this.keypadValue = 0x00
|
|
78
|
-
this.dataReady = false
|
|
79
|
-
this.interruptPending = false
|
|
80
|
-
this.oeState = true // OE disabled until explicitly asserted by the 6522
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
updateControlLines(ca1: boolean, ca2: boolean, cb1: boolean, cb2: boolean): void {
|
|
84
|
-
// CA2 controls OE for Port A; CB2 controls OE for Port B.
|
|
85
|
-
// 74C922 OE is active-LOW, so a LOW signal enables the output.
|
|
86
|
-
this.oeState = this.attachedToPortA ? ca2 : cb2
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
readPortA(ddr: number, or: number): number {
|
|
90
|
-
// Only drive the bus when attached to Port A, OE is asserted (LOW), and data is latched
|
|
91
|
-
if (this.attachedToPortA && !this.oeState && this.dataReady) {
|
|
92
|
-
return this.keypadValue & 0x1F // bits 0–4 only; bits 5–7 = 0
|
|
93
|
-
}
|
|
94
|
-
return 0xFF // not driving the bus
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
readPortB(ddr: number, or: number): number {
|
|
98
|
-
// Only drive the bus when attached to Port B, OE is asserted (LOW), and data is latched
|
|
99
|
-
if (!this.attachedToPortA && !this.oeState && this.dataReady) {
|
|
100
|
-
return this.keypadValue & 0x1F // bits 0–4 only; bits 5–7 = 0
|
|
101
|
-
}
|
|
102
|
-
return 0xFF // not driving the bus
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
hasCA1Interrupt(): boolean {
|
|
106
|
-
return this.attachedToPortA && this.interruptPending
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
hasCB1Interrupt(): boolean {
|
|
110
|
-
return !this.attachedToPortA && this.interruptPending
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
clearInterrupts(ca1: boolean, ca2: boolean, cb1: boolean, cb2: boolean): void {
|
|
114
|
-
if ((this.attachedToPortA && ca1) || (!this.attachedToPortA && cb1)) {
|
|
115
|
-
this.interruptPending = false
|
|
116
|
-
this.dataReady = false
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Notify the attachment of a USB HID key event.
|
|
122
|
-
* Key releases are ignored; only presses generate output on the GPIO port.
|
|
123
|
-
*
|
|
124
|
-
* @param usbHidKeycode - USB HID usage ID for the key
|
|
125
|
-
* @param pressed - true for key-down, false for key-up
|
|
126
|
-
*/
|
|
127
|
-
updateKey(usbHidKeycode: number, pressed: boolean): void {
|
|
128
|
-
if (!pressed) {
|
|
129
|
-
return
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const keypadCode = USB_HID_TO_KEYPAD[usbHidKeycode]
|
|
133
|
-
if (keypadCode === undefined) {
|
|
134
|
-
return // key is not present on this keypad
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
this.keypadValue = keypadCode
|
|
138
|
-
this.dataReady = true
|
|
139
|
-
this.interruptPending = true
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Returns the current latched keypad code (bits 0–4) or 0xFF if no data is ready.
|
|
144
|
-
*/
|
|
145
|
-
getCurrentKey(): number {
|
|
146
|
-
return this.dataReady ? (this.keypadValue & 0x1F) : 0xFF
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/** Returns true when a key has been pressed and the latch has not yet been cleared. */
|
|
150
|
-
hasDataReady(): boolean {
|
|
151
|
-
return this.dataReady
|
|
152
|
-
}
|
|
153
|
-
}
|