@gba-kit/gba-browser 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bruno Macabeus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # @gba-kit/gba-browser
2
+
3
+ Framework-agnostic browser runtime for GBA emulation.
4
+
5
+ Provides `EmulatorBridge`, a class that connects the GBA emulator to a browser canvas with keyboard input, save states, breakpoints, disassembly, and IndexedDB-backed save state persistence.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @gba-kit/gba-browser
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ### EmulatorBridge
16
+
17
+ The core class that wraps `@gba-kit/gba-emulator` for browser use:
18
+
19
+ ```typescript
20
+ import { EmulatorBridge } from '@gba-kit/gba-browser';
21
+
22
+ const emulator = new EmulatorBridge();
23
+
24
+ // Attach a canvas for rendering
25
+ emulator.attachCanvas(document.querySelector('canvas')!);
26
+
27
+ // Load a ROM and start emulation
28
+ emulator.loadRom(romArrayBuffer);
29
+ emulator.run();
30
+
31
+ // Pause / step
32
+ emulator.pause();
33
+ emulator.stepInstruction();
34
+
35
+ // Keyboard input (wire to your own event listeners)
36
+ window.addEventListener('keydown', (e) => emulator.handleKeyDown(e));
37
+ window.addEventListener('keyup', (e) => emulator.handleKeyUp(e));
38
+
39
+ // Breakpoints
40
+ emulator.addBreakpoint(0x08001234);
41
+
42
+ // Save/load states
43
+ const { snapshot, thumbnail } = await emulator.saveState();
44
+ emulator.loadState(snapshot);
45
+
46
+ // Disassembly
47
+ const instructions = emulator.disassembleAt(0x08000000, 20);
48
+
49
+ // Memory access
50
+ const data = emulator.readMemory(0x03000000, 256);
51
+ ```
52
+
53
+ ### Save State Persistence
54
+
55
+ IndexedDB-backed storage for save states with thumbnail previews:
56
+
57
+ ```typescript
58
+ import { computeRomHash, deleteState, listByRom, loadState, saveState } from '@gba-kit/gba-browser';
59
+
60
+ const hash = await computeRomHash(romArrayBuffer);
61
+
62
+ const id = await saveState(hash, snapshot, thumbnailBlob, 'My Save');
63
+
64
+ const saves = await listByRom(hash);
65
+
66
+ const record = await loadState(id);
67
+ ```
68
+
69
+ ## Exports
70
+
71
+ | Export | Description |
72
+ | ------------------ | ---------------------------------------------------------------------------- |
73
+ | `EmulatorBridge` | Browser GBA emulator wrapper with canvas, keyboard, breakpoints, save states |
74
+ | `computeRomHash()` | SHA-256 hash of ROM header for identity |
75
+ | `saveState()` | Save snapshot + thumbnail to IndexedDB |
76
+ | `loadState()` | Load a save state record by ID |
77
+ | `deleteState()` | Delete a save state by ID |
78
+ | `renameState()` | Rename a save state |
79
+ | `listByRom()` | List save state metadata for a ROM |
80
+
81
+ Types: `EmulatorState`, `EmulatorCallbacks`, `Breakpoint`, `SaveStateRecord`, `SaveStateMeta`
82
+
83
+ ## See Also
84
+
85
+ - [`@gba-kit/gba-react`](../gba-react) — React hooks that wrap this package
86
+ - [`@gba-kit/gba-node`](../gba-node) — Headless Node.js runtime for scripted emulation
@@ -0,0 +1,71 @@
1
+ import { ArmCpu } from '@gba-kit/arm-emulator/arm-cpu';
2
+ import { Gba } from '@gba-kit/gba-emulator';
3
+ import type { GbaSnapshot } from '@gba-kit/gba-emulator/savestate';
4
+ export type EmulatorState = 'idle' | 'running' | 'paused';
5
+ export interface Breakpoint {
6
+ address: number;
7
+ enabled: boolean;
8
+ }
9
+ export interface EmulatorCallbacks {
10
+ onStateChange: (state: EmulatorState) => void;
11
+ onFrame: () => void;
12
+ onBreakpoint: (address: number) => void;
13
+ }
14
+ export declare class EmulatorBridge {
15
+ #private;
16
+ constructor();
17
+ get gba(): Gba;
18
+ get cpu(): ArmCpu;
19
+ get state(): EmulatorState;
20
+ /** Attach callbacks for state updates */
21
+ setCallbacks(callbacks: EmulatorCallbacks): void;
22
+ /** Attach a canvas element for rendering. Immediately shows the last frame. */
23
+ attachCanvas(canvas: HTMLCanvasElement): void;
24
+ /** Detach the current canvas (call when a view unmounts) */
25
+ detachCanvas(): void;
26
+ /** Load a ROM from an ArrayBuffer */
27
+ loadRom(data: ArrayBuffer): void;
28
+ /** Start/resume emulation */
29
+ run(): void;
30
+ /** Pause emulation */
31
+ pause(): void;
32
+ /** Stop emulation and reset */
33
+ stop(): void;
34
+ /** Execute a single CPU instruction */
35
+ stepInstruction(): void;
36
+ /** Run a single emulation frame and render to the attached canvas. */
37
+ runOneFrame(): void;
38
+ /** Step over: run until PC advances past the current instruction */
39
+ stepOver(): void;
40
+ /** Run until a specific address is reached */
41
+ runToAddress(address: number): void;
42
+ addBreakpoint(address: number): void;
43
+ removeBreakpoint(address: number): void;
44
+ toggleBreakpoint(address: number): void;
45
+ getBreakpoints(): Breakpoint[];
46
+ /** Disassemble instructions around the given address */
47
+ disassembleAt(address: number, count: number): Array<{
48
+ address: number;
49
+ mnemonic: string;
50
+ isThumb: boolean;
51
+ }>;
52
+ handleKeyDown(e: KeyboardEvent): void;
53
+ handleKeyUp(e: KeyboardEvent): void;
54
+ /** Whether audio output is currently enabled */
55
+ get audioEnabled(): boolean;
56
+ /** Enable audio output. Creates an AudioContext on first call (requires user gesture). */
57
+ enableAudio(): void;
58
+ /** Disable audio output (mute). The AudioContext is kept alive for quick re-enable. */
59
+ disableAudio(): void;
60
+ /** Toggle audio on/off. Returns the new state. */
61
+ toggleAudio(): boolean;
62
+ readMemory(address: number, size: number): Uint8Array;
63
+ /** Create a snapshot of the current emulator state plus a thumbnail. */
64
+ saveState(): Promise<{
65
+ snapshot: GbaSnapshot;
66
+ thumbnail: Blob;
67
+ }>;
68
+ /** Load a snapshot, restoring the full emulator state. */
69
+ loadState(snapshot: GbaSnapshot): void;
70
+ }
71
+ //# sourceMappingURL=emulator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emulator.d.ts","sourceRoot":"","sources":["../src/emulator.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAEvD,OAAO,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAgBnE,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE1D,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC9C,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,qBAAa,cAAc;;;IAoBzB,IAAI,GAAG,IAAI,GAAG,CAEb;IAED,IAAI,GAAG,IAAI,MAAM,CAEhB;IAED,IAAI,KAAK,IAAI,aAAa,CAEzB;IAED,yCAAyC;IACzC,YAAY,CAAC,SAAS,EAAE,iBAAiB,GAAG,IAAI;IAIhD,+EAA+E;IAC/E,YAAY,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI;IAQ7C,4DAA4D;IAC5D,YAAY,IAAI,IAAI;IAIpB,qCAAqC;IACrC,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;IAuChC,6BAA6B;IAC7B,GAAG,IAAI,IAAI;IAaX,sBAAsB;IACtB,KAAK,IAAI,IAAI;IAQb,+BAA+B;IAC/B,IAAI,IAAI,IAAI;IAMZ,uCAAuC;IACvC,eAAe,IAAI,IAAI;IAWvB,sEAAsE;IACtE,WAAW,IAAI,IAAI;IAMnB,oEAAoE;IACpE,QAAQ,IAAI,IAAI;IA2BhB,8CAA8C;IAC9C,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAoBnC,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKpC,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKvC,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAQvC,cAAc,IAAI,UAAU,EAAE;IAM9B,wDAAwD;IACxD,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAsB7G,aAAa,CAAC,CAAC,EAAE,aAAa,GAAG,IAAI;IAQrC,WAAW,CAAC,CAAC,EAAE,aAAa,GAAG,IAAI;IAUnC,gDAAgD;IAChD,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED,0FAA0F;IAC1F,WAAW,IAAI,IAAI;IA4BnB,uFAAuF;IACvF,YAAY,IAAI,IAAI;IAQpB,kDAAkD;IAClD,WAAW,IAAI,OAAO;IAWtB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU;IAUrD,wEAAwE;IAClE,SAAS,IAAI,OAAO,CAAC;QAAE,QAAQ,EAAE,WAAW,CAAC;QAAC,SAAS,EAAE,IAAI,CAAA;KAAE,CAAC;IAkCtE,0DAA0D;IAC1D,SAAS,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI;CA6EvC"}
@@ -0,0 +1,377 @@
1
+ import { disassembleArm, disassembleThumb } from '@gba-kit/arm-emulator/disassembler';
2
+ import { Gba } from '@gba-kit/gba-emulator';
3
+ /** Keyboard mapping: key → GBA button bit */
4
+ const KEY_MAP = {
5
+ ArrowRight: 4,
6
+ ArrowLeft: 5,
7
+ ArrowUp: 6,
8
+ ArrowDown: 7,
9
+ z: 0, // A
10
+ x: 1, // B
11
+ Backspace: 2, // Select
12
+ Enter: 3, // Start
13
+ a: 8, // R
14
+ s: 9, // L
15
+ };
16
+ export class EmulatorBridge {
17
+ #gba = new Gba();
18
+ #ctx = null;
19
+ #state = 'idle';
20
+ #animFrameId = 0;
21
+ #callbacks = null;
22
+ #breakpoints = new Map();
23
+ #hitBreakpoint = false;
24
+ /** Persistent framebuffer image, always up-to-date with the last rendered frame.
25
+ * Decoupled from any DOM canvas so mode switches never lose the current frame. */
26
+ #framebufferImage = new ImageData(240, 160);
27
+ // Audio output
28
+ #audioCtx = null;
29
+ #audioNode = null;
30
+ #audioEnabled = false;
31
+ constructor() { }
32
+ get gba() {
33
+ return this.#gba;
34
+ }
35
+ get cpu() {
36
+ return this.#gba.armCpu;
37
+ }
38
+ get state() {
39
+ return this.#state;
40
+ }
41
+ /** Attach callbacks for state updates */
42
+ setCallbacks(callbacks) {
43
+ this.#callbacks = callbacks;
44
+ }
45
+ /** Attach a canvas element for rendering. Immediately shows the last frame. */
46
+ attachCanvas(canvas) {
47
+ canvas.width = 240;
48
+ canvas.height = 160;
49
+ this.#ctx = canvas.getContext('2d');
50
+ // Immediately blit the persistent framebuffer so the canvas is never blank
51
+ this.#ctx.putImageData(this.#framebufferImage, 0, 0);
52
+ }
53
+ /** Detach the current canvas (call when a view unmounts) */
54
+ detachCanvas() {
55
+ this.#ctx = null;
56
+ }
57
+ /** Load a ROM from an ArrayBuffer */
58
+ loadRom(data) {
59
+ this.stop();
60
+ this.#gba.reset();
61
+ this.#gba.loadRom(new Uint8Array(data));
62
+ const cpu = this.#gba.armCpu;
63
+ // Set up initial CPU state matching post-BIOS boot:
64
+ // The GBA BIOS normally initializes stacks and then jumps to ROM.
65
+ // Since we skip the BIOS, we replicate the post-boot state.
66
+ // Set up IRQ mode stack pointer
67
+ cpu.switchMode(0x12); // MODE_IRQ
68
+ cpu.registers[13] = 0x03007fa0;
69
+ // Set up SVC mode stack pointer
70
+ cpu.switchMode(0x13); // MODE_SVC
71
+ cpu.registers[13] = 0x03007fe0;
72
+ // Switch to System mode (privileged, but uses USR registers)
73
+ cpu.switchMode(0x1f); // MODE_SYS
74
+ // Set USR/SYS stack pointer
75
+ cpu.registers[13] = 0x03007f00;
76
+ // Enable IRQs (clear I bit), keep FIQ disabled, System mode
77
+ // CPSR: 0x1F (SYS mode) with I=0, F=0, T=0 (ARM state initially)
78
+ cpu.cpsr = 0x1f; // SYS mode, IRQs enabled, ARM state
79
+ // PC to ROM entry point
80
+ cpu.registers[15] = 0x08000000;
81
+ // Read the ROM header to determine entry mode
82
+ // GBA ROMs start with an ARM branch instruction at 0x08000000
83
+ // The branch usually jumps to Thumb code, but the entry is always ARM
84
+ this.#setState('paused');
85
+ }
86
+ /** Start/resume emulation */
87
+ run() {
88
+ if (this.#state === 'running') {
89
+ return;
90
+ }
91
+ this.#setState('running');
92
+ this.#hitBreakpoint = false;
93
+ // Install breakpoint hooks if any breakpoints are set
94
+ this.#updateDebugHooks();
95
+ this.#emulationLoop();
96
+ }
97
+ /** Pause emulation */
98
+ pause() {
99
+ if (this.#state !== 'running') {
100
+ return;
101
+ }
102
+ cancelAnimationFrame(this.#animFrameId);
103
+ this.#setState('paused');
104
+ }
105
+ /** Stop emulation and reset */
106
+ stop() {
107
+ cancelAnimationFrame(this.#animFrameId);
108
+ this.disableAudio();
109
+ this.#setState('idle');
110
+ }
111
+ /** Execute a single CPU instruction */
112
+ stepInstruction() {
113
+ if (this.#state !== 'paused') {
114
+ return;
115
+ }
116
+ this.#gba.armCpu.step();
117
+ this.#gba.scheduler.tick(1);
118
+ this.#gba.apu.tick(1);
119
+ this.#renderFrame();
120
+ this.#callbacks?.onFrame();
121
+ }
122
+ /** Run a single emulation frame and render to the attached canvas. */
123
+ runOneFrame() {
124
+ this.#gba.runFrame();
125
+ this.#renderFrame();
126
+ this.#callbacks?.onFrame();
127
+ }
128
+ /** Step over: run until PC advances past the current instruction */
129
+ stepOver() {
130
+ if (this.#state !== 'paused') {
131
+ return;
132
+ }
133
+ const currentPC = this.#gba.armCpu.registers[15];
134
+ const instrSize = this.#gba.armCpu.getT() ? 2 : 4;
135
+ const targetPC = currentPC + instrSize;
136
+ // Run until we reach the next instruction or hit a breakpoint
137
+ let cyclesRun = 0;
138
+ for (let i = 0; i < 100_000; i++) {
139
+ this.#gba.armCpu.step();
140
+ this.#gba.scheduler.tick(1);
141
+ cyclesRun++;
142
+ if (this.#gba.armCpu.registers[15] === targetPC) {
143
+ break;
144
+ }
145
+ if (this.#breakpoints.has(this.#gba.armCpu.registers[15])) {
146
+ break;
147
+ }
148
+ }
149
+ this.#gba.apu.tick(cyclesRun);
150
+ this.#renderFrame();
151
+ this.#callbacks?.onFrame();
152
+ }
153
+ /** Run until a specific address is reached */
154
+ runToAddress(address) {
155
+ if (this.#state !== 'paused') {
156
+ return;
157
+ }
158
+ let cyclesRun = 0;
159
+ for (let i = 0; i < 10_000_000; i++) {
160
+ this.#gba.armCpu.step();
161
+ this.#gba.scheduler.tick(1);
162
+ cyclesRun++;
163
+ if (this.#gba.armCpu.registers[15] === address) {
164
+ break;
165
+ }
166
+ }
167
+ this.#gba.apu.tick(cyclesRun);
168
+ this.#renderFrame();
169
+ this.#callbacks?.onFrame();
170
+ }
171
+ // ─── Breakpoints ──────────────────────────────────────────────────
172
+ addBreakpoint(address) {
173
+ this.#breakpoints.set(address, { address, enabled: true });
174
+ this.#updateDebugHooks();
175
+ }
176
+ removeBreakpoint(address) {
177
+ this.#breakpoints.delete(address);
178
+ this.#updateDebugHooks();
179
+ }
180
+ toggleBreakpoint(address) {
181
+ const bp = this.#breakpoints.get(address);
182
+ if (bp) {
183
+ bp.enabled = !bp.enabled;
184
+ }
185
+ this.#updateDebugHooks();
186
+ }
187
+ getBreakpoints() {
188
+ return Array.from(this.#breakpoints.values());
189
+ }
190
+ // ─── Disassembly ──────────────────────────────────────────────────
191
+ /** Disassemble instructions around the given address */
192
+ disassembleAt(address, count) {
193
+ const result = [];
194
+ const isThumb = this.#gba.armCpu.getT();
195
+ let addr = address;
196
+ for (let i = 0; i < count; i++) {
197
+ if (isThumb) {
198
+ const instr = this.#gba.bus.read16(addr);
199
+ result.push({ address: addr, mnemonic: disassembleThumb(instr, addr), isThumb: true });
200
+ addr += 2;
201
+ }
202
+ else {
203
+ const instr = this.#gba.bus.read32(addr);
204
+ result.push({ address: addr, mnemonic: disassembleArm(instr, addr), isThumb: false });
205
+ addr += 4;
206
+ }
207
+ }
208
+ return result;
209
+ }
210
+ // ─── Keyboard Input ───────────────────────────────────────────────
211
+ handleKeyDown(e) {
212
+ const button = KEY_MAP[e.key];
213
+ if (button !== undefined) {
214
+ e.preventDefault();
215
+ this.#gba.input.press(button);
216
+ }
217
+ }
218
+ handleKeyUp(e) {
219
+ const button = KEY_MAP[e.key];
220
+ if (button !== undefined) {
221
+ e.preventDefault();
222
+ this.#gba.input.release(button);
223
+ }
224
+ }
225
+ // ─── Audio ──────────────────────────────────────────────────────────
226
+ /** Whether audio output is currently enabled */
227
+ get audioEnabled() {
228
+ return this.#audioEnabled;
229
+ }
230
+ /** Enable audio output. Creates an AudioContext on first call (requires user gesture). */
231
+ enableAudio() {
232
+ if (this.#audioEnabled) {
233
+ return;
234
+ }
235
+ if (!this.#audioCtx) {
236
+ this.#audioCtx = new AudioContext({ sampleRate: 32768 });
237
+ // ScriptProcessorNode: pulls samples from the APU ring buffer
238
+ this.#audioNode = this.#audioCtx.createScriptProcessor(2048, 0, 2);
239
+ this.#audioNode.onaudioprocess = (e) => {
240
+ const left = e.outputBuffer.getChannelData(0);
241
+ const right = e.outputBuffer.getChannelData(1);
242
+ const interleaved = new Float32Array(left.length * 2);
243
+ this.#gba.apu.readSamples(interleaved);
244
+ for (let i = 0; i < left.length; i++) {
245
+ left[i] = interleaved[i * 2];
246
+ right[i] = interleaved[i * 2 + 1];
247
+ }
248
+ };
249
+ }
250
+ this.#audioNode.connect(this.#audioCtx.destination);
251
+ if (this.#audioCtx.state === 'suspended') {
252
+ this.#audioCtx.resume();
253
+ }
254
+ this.#audioEnabled = true;
255
+ }
256
+ /** Disable audio output (mute). The AudioContext is kept alive for quick re-enable. */
257
+ disableAudio() {
258
+ if (!this.#audioEnabled) {
259
+ return;
260
+ }
261
+ this.#audioNode?.disconnect();
262
+ this.#audioEnabled = false;
263
+ }
264
+ /** Toggle audio on/off. Returns the new state. */
265
+ toggleAudio() {
266
+ if (this.#audioEnabled) {
267
+ this.disableAudio();
268
+ }
269
+ else {
270
+ this.enableAudio();
271
+ }
272
+ return this.#audioEnabled;
273
+ }
274
+ // ─── Memory Access ────────────────────────────────────────────────
275
+ readMemory(address, size) {
276
+ const data = new Uint8Array(size);
277
+ for (let i = 0; i < size; i++) {
278
+ data[i] = this.#gba.bus.read8(address + i);
279
+ }
280
+ return data;
281
+ }
282
+ // ─── Save States ────────────────────────────────────────────────
283
+ /** Create a snapshot of the current emulator state plus a thumbnail. */
284
+ async saveState() {
285
+ const wasRunning = this.#state === 'running';
286
+ if (wasRunning) {
287
+ this.pause();
288
+ }
289
+ // Serialize GBA state
290
+ const snapshot = this.#gba.serialize();
291
+ // Serialize CPU into the snapshot
292
+ snapshot.cpu = this.#gba.armCpu.serialize();
293
+ // Generate thumbnail (60x40)
294
+ const thumbCanvas = document.createElement('canvas');
295
+ thumbCanvas.width = 60;
296
+ thumbCanvas.height = 40;
297
+ const thumbCtx = thumbCanvas.getContext('2d');
298
+ // Create a temporary canvas with the full framebuffer to scale from
299
+ const srcCanvas = document.createElement('canvas');
300
+ srcCanvas.width = 240;
301
+ srcCanvas.height = 160;
302
+ const srcCtx = srcCanvas.getContext('2d');
303
+ srcCtx.putImageData(this.#framebufferImage, 0, 0);
304
+ thumbCtx.imageSmoothingEnabled = false;
305
+ thumbCtx.drawImage(srcCanvas, 0, 0, 60, 40);
306
+ const thumbnail = await new Promise((resolve) => {
307
+ thumbCanvas.toBlob((blob) => resolve(blob), 'image/png');
308
+ });
309
+ return { snapshot, thumbnail };
310
+ }
311
+ /** Load a snapshot, restoring the full emulator state. */
312
+ loadState(snapshot) {
313
+ cancelAnimationFrame(this.#animFrameId);
314
+ // Restore GBA subsystems
315
+ this.#gba.deserialize(snapshot);
316
+ // Restore CPU
317
+ this.#gba.armCpu.deserialize(snapshot.cpu);
318
+ // Re-render frame from restored framebuffer
319
+ this.#renderFrame();
320
+ this.#callbacks?.onFrame();
321
+ this.#setState('paused');
322
+ }
323
+ // ─── Internal ─────────────────────────────────────────────────────
324
+ #emulationLoop = () => {
325
+ if (this.#state !== 'running') {
326
+ return;
327
+ }
328
+ this.#gba.runFrame();
329
+ this.#renderFrame();
330
+ this.#callbacks?.onFrame();
331
+ if (this.#hitBreakpoint) {
332
+ this.#setState('paused');
333
+ this.#callbacks?.onBreakpoint(this.#gba.armCpu.registers[15]);
334
+ return;
335
+ }
336
+ this.#animFrameId = requestAnimationFrame(this.#emulationLoop);
337
+ };
338
+ #renderFrame() {
339
+ const framebuffer = this.#gba.ppu.getFramebuffer();
340
+ const pixels = new Uint8ClampedArray(this.#framebufferImage.data.buffer);
341
+ // Copy ABGR framebuffer to RGBA ImageData (always update the persistent buffer)
342
+ for (let i = 0; i < 240 * 160; i++) {
343
+ const abgr = framebuffer[i];
344
+ const offset = i * 4;
345
+ pixels[offset] = abgr & 0xff; // R
346
+ pixels[offset + 1] = (abgr >> 8) & 0xff; // G
347
+ pixels[offset + 2] = (abgr >> 16) & 0xff; // B
348
+ pixels[offset + 3] = (abgr >> 24) & 0xff; // A
349
+ }
350
+ // Blit to the attached canvas if one exists
351
+ this.#ctx?.putImageData(this.#framebufferImage, 0, 0);
352
+ }
353
+ #setState(state) {
354
+ this.#state = state;
355
+ this.#callbacks?.onStateChange(state);
356
+ }
357
+ #updateDebugHooks() {
358
+ const hasBreakpoints = Array.from(this.#breakpoints.values()).some((bp) => bp.enabled);
359
+ if (hasBreakpoints) {
360
+ const hooks = {
361
+ onInstructionPre: (address) => {
362
+ const bp = this.#breakpoints.get(address);
363
+ if (bp?.enabled) {
364
+ this.#hitBreakpoint = true;
365
+ return 'break';
366
+ }
367
+ return 'continue';
368
+ },
369
+ };
370
+ this.#gba.armCpu.setDebugHooks(hooks);
371
+ }
372
+ else {
373
+ this.#gba.armCpu.setDebugHooks(undefined);
374
+ }
375
+ }
376
+ }
377
+ //# sourceMappingURL=emulator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emulator.js","sourceRoot":"","sources":["../src/emulator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { EmulatorBridge } from './emulator.js';
2
+ export type { EmulatorState, EmulatorCallbacks, Breakpoint } from './emulator.js';
3
+ export { computeRomHash, saveState, loadState, deleteState, renameState, listByRom } from './savestate-db.js';
4
+ export type { SaveStateRecord, SaveStateMeta } from './savestate-db.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGlF,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9G,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ // Core
2
+ export { EmulatorBridge } from './emulator.js';
3
+ // Save state persistence
4
+ export { computeRomHash, saveState, loadState, deleteState, renameState, listByRom } from './savestate-db.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO;AACP,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,yBAAyB;AACzB,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Save State IndexedDB Storage
3
+ *
4
+ * Stores GBA save states with thumbnail previews in IndexedDB.
5
+ * Uses structured clone for efficient storage of typed arrays.
6
+ */
7
+ import type { GbaSnapshot } from '@gba-kit/gba-emulator/savestate';
8
+ export interface SaveStateRecord {
9
+ id: number;
10
+ label: string;
11
+ timestamp: number;
12
+ romHash: string;
13
+ thumbnail: Blob;
14
+ snapshot: GbaSnapshot;
15
+ }
16
+ export type SaveStateMeta = Omit<SaveStateRecord, 'snapshot'>;
17
+ /** Compute a ROM hash from the first 192 bytes of the ROM. */
18
+ export declare function computeRomHash(rom: ArrayBuffer): Promise<string>;
19
+ /** Save a snapshot to IndexedDB. Returns the auto-generated ID. */
20
+ export declare function saveState(romHash: string, snapshot: GbaSnapshot, thumbnail: Blob, label?: string): Promise<number>;
21
+ /** Load a full save state record by ID. */
22
+ export declare function loadState(id: number): Promise<SaveStateRecord | undefined>;
23
+ /** Delete a save state by ID. */
24
+ export declare function deleteState(id: number): Promise<void>;
25
+ /** Rename a save state. */
26
+ export declare function renameState(id: number, label: string): Promise<void>;
27
+ /** List save state metadata (no snapshot data) for a specific ROM. */
28
+ export declare function listByRom(romHash: string): Promise<SaveStateMeta[]>;
29
+ //# sourceMappingURL=savestate-db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"savestate-db.d.ts","sourceRoot":"","sources":["../src/savestate-db.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAEnE,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,WAAW,CAAC;CACvB;AAED,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;AA6B9D,8DAA8D;AAC9D,wBAAsB,cAAc,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAMtE;AAED,mEAAmE;AACnE,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,WAAW,EACrB,SAAS,EAAE,IAAI,EACf,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAED,2CAA2C;AAC3C,wBAAsB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAShF;AAED,iCAAiC;AACjC,wBAAsB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAS3D;AAED,2BAA2B;AAC3B,wBAAsB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB1E;AAED,sEAAsE;AACtE,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAsBzE"}
@@ -0,0 +1,118 @@
1
+ const DB_NAME = 'gba-kit-savestates';
2
+ const DB_VERSION = 1;
3
+ const STORE_NAME = 'slots';
4
+ let dbPromise = null;
5
+ function openDb() {
6
+ if (dbPromise) {
7
+ return dbPromise;
8
+ }
9
+ dbPromise = new Promise((resolve, reject) => {
10
+ const request = indexedDB.open(DB_NAME, DB_VERSION);
11
+ request.onupgradeneeded = () => {
12
+ const db = request.result;
13
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
14
+ db.createObjectStore(STORE_NAME, { keyPath: 'id', autoIncrement: true });
15
+ }
16
+ };
17
+ request.onsuccess = () => resolve(request.result);
18
+ request.onerror = () => {
19
+ dbPromise = null;
20
+ reject(request.error);
21
+ };
22
+ });
23
+ return dbPromise;
24
+ }
25
+ /** Compute a ROM hash from the first 192 bytes of the ROM. */
26
+ export async function computeRomHash(rom) {
27
+ const header = rom.slice(0, 192);
28
+ const hash = await crypto.subtle.digest('SHA-256', header);
29
+ return Array.from(new Uint8Array(hash))
30
+ .map((b) => b.toString(16).padStart(2, '0'))
31
+ .join('');
32
+ }
33
+ /** Save a snapshot to IndexedDB. Returns the auto-generated ID. */
34
+ export async function saveState(romHash, snapshot, thumbnail, label) {
35
+ const db = await openDb();
36
+ const record = {
37
+ label: label ?? `Save #${Date.now()}`,
38
+ timestamp: Date.now(),
39
+ romHash,
40
+ thumbnail,
41
+ snapshot,
42
+ };
43
+ return new Promise((resolve, reject) => {
44
+ const tx = db.transaction(STORE_NAME, 'readwrite');
45
+ const store = tx.objectStore(STORE_NAME);
46
+ const request = store.add(record);
47
+ request.onsuccess = () => resolve(request.result);
48
+ request.onerror = () => reject(request.error);
49
+ });
50
+ }
51
+ /** Load a full save state record by ID. */
52
+ export async function loadState(id) {
53
+ const db = await openDb();
54
+ return new Promise((resolve, reject) => {
55
+ const tx = db.transaction(STORE_NAME, 'readonly');
56
+ const store = tx.objectStore(STORE_NAME);
57
+ const request = store.get(id);
58
+ request.onsuccess = () => resolve(request.result);
59
+ request.onerror = () => reject(request.error);
60
+ });
61
+ }
62
+ /** Delete a save state by ID. */
63
+ export async function deleteState(id) {
64
+ const db = await openDb();
65
+ return new Promise((resolve, reject) => {
66
+ const tx = db.transaction(STORE_NAME, 'readwrite');
67
+ const store = tx.objectStore(STORE_NAME);
68
+ const request = store.delete(id);
69
+ request.onsuccess = () => resolve();
70
+ request.onerror = () => reject(request.error);
71
+ });
72
+ }
73
+ /** Rename a save state. */
74
+ export async function renameState(id, label) {
75
+ const db = await openDb();
76
+ return new Promise((resolve, reject) => {
77
+ const tx = db.transaction(STORE_NAME, 'readwrite');
78
+ const store = tx.objectStore(STORE_NAME);
79
+ const getReq = store.get(id);
80
+ getReq.onsuccess = () => {
81
+ const record = getReq.result;
82
+ if (!record) {
83
+ reject(new Error(`Save state ${id} not found`));
84
+ return;
85
+ }
86
+ record.label = label;
87
+ const putReq = store.put(record);
88
+ putReq.onsuccess = () => resolve();
89
+ putReq.onerror = () => reject(putReq.error);
90
+ };
91
+ getReq.onerror = () => reject(getReq.error);
92
+ });
93
+ }
94
+ /** List save state metadata (no snapshot data) for a specific ROM. */
95
+ export async function listByRom(romHash) {
96
+ const db = await openDb();
97
+ return new Promise((resolve, reject) => {
98
+ const tx = db.transaction(STORE_NAME, 'readonly');
99
+ const store = tx.objectStore(STORE_NAME);
100
+ const request = store.getAll();
101
+ request.onsuccess = () => {
102
+ const all = request.result;
103
+ const filtered = all
104
+ .filter((r) => r.romHash === romHash)
105
+ .map(({ id, label, timestamp, romHash, thumbnail }) => ({
106
+ id,
107
+ label,
108
+ timestamp,
109
+ romHash,
110
+ thumbnail,
111
+ }))
112
+ .sort((a, b) => b.timestamp - a.timestamp);
113
+ resolve(filtered);
114
+ };
115
+ request.onerror = () => reject(request.error);
116
+ });
117
+ }
118
+ //# sourceMappingURL=savestate-db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"savestate-db.js","sourceRoot":"","sources":["../src/savestate-db.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@gba-kit/gba-browser",
3
+ "version": "0.1.0",
4
+ "description": "Browser runtime for GBA emulation",
5
+ "author": "macabeus",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/macabeus/gba-kit",
19
+ "directory": "packages/gba-browser"
20
+ },
21
+ "homepage": "https://github.com/macabeus/gba-kit",
22
+ "bugs": "https://github.com/macabeus/gba-kit/issues",
23
+ "keywords": [
24
+ "gba",
25
+ "gameboy-advance",
26
+ "emulator",
27
+ "arm7tdmi",
28
+ "typescript"
29
+ ],
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "dependencies": {
34
+ "@gba-kit/arm-emulator": "0.1.0",
35
+ "@gba-kit/gba-emulator": "0.1.0"
36
+ },
37
+ "devDependencies": {
38
+ "typescript": "^6.0.2"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc",
42
+ "check-types": "tsc --noEmit",
43
+ "lint": "eslint src/"
44
+ }
45
+ }