Skip to content

Commit eb426cc

Browse files
bfirshclaude
andcommitted
Accept Uint8Array and ArrayBuffer in loadROM()
ROM.load() previously only accepted binary strings, requiring users to do roundabout conversions from typed arrays. Now accepts Uint8Array, ArrayBuffer, Node.js Buffer, and binary strings directly. Fixes #454 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e222989 commit eb426cc

File tree

3 files changed

+66
-7
lines changed

3 files changed

+66
-7
lines changed

src/nes.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export class NES {
2929
zapperFireUp: () => void;
3030
getFPS: () => number;
3131
reloadROM: () => void;
32-
loadROM: (data: string | Buffer) => void;
32+
loadROM: (data: string | Buffer | Uint8Array | ArrayBuffer) => void;
3333
setFramerate: (rate: number) => void;
3434
toJSON: () => EmulatorData;
3535
fromJSON: (data: EmulatorData) => void;

src/rom.js

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,31 @@ class ROM {
6464
load(data) {
6565
let i, j, v;
6666

67-
if (!data.startsWith("NES\x1a")) {
68-
throw new Error("Not a valid NES ROM.");
67+
// Accept Uint8Array, ArrayBuffer, Buffer, or binary string.
68+
if (data instanceof ArrayBuffer) {
69+
data = new Uint8Array(data);
6970
}
71+
const isTypedArray = ArrayBuffer.isView(data);
72+
73+
if (isTypedArray) {
74+
if (
75+
data.length < 4 ||
76+
data[0] !== 0x4e ||
77+
data[1] !== 0x45 ||
78+
data[2] !== 0x53 ||
79+
data[3] !== 0x1a
80+
) {
81+
throw new Error("Not a valid NES ROM.");
82+
}
83+
} else {
84+
if (!data.startsWith("NES\x1a")) {
85+
throw new Error("Not a valid NES ROM.");
86+
}
87+
}
88+
7089
this.header = new Uint8Array(16);
7190
for (i = 0; i < 16; i++) {
72-
this.header[i] = data.charCodeAt(i) & 0xff;
91+
this.header[i] = isTypedArray ? data[i] : data.charCodeAt(i) & 0xff;
7392
}
7493
this.romCount = this.header[4];
7594
this.vromCount = this.header[5] * 2; // Get the number of 4kB banks, not 8kB
@@ -101,7 +120,9 @@ class ROM {
101120
if (offset + j >= data.length) {
102121
break;
103122
}
104-
this.rom[i][j] = data.charCodeAt(offset + j) & 0xff;
123+
this.rom[i][j] = isTypedArray
124+
? data[offset + j]
125+
: data.charCodeAt(offset + j) & 0xff;
105126
}
106127
offset += 16384;
107128
}
@@ -113,7 +134,9 @@ class ROM {
113134
if (offset + j >= data.length) {
114135
break;
115136
}
116-
this.vrom[i][j] = data.charCodeAt(offset + j) & 0xff;
137+
this.vrom[i][j] = isTypedArray
138+
? data[offset + j]
139+
: data.charCodeAt(offset + j) & 0xff;
117140
}
118141
offset += 4096;
119142
}

test/nes.spec.js

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,49 @@ describe("NES", function() {
3636
}
3737
});
3838

39+
it("loads a ROM from a Uint8Array and runs a frame", function() {
40+
let onFrame = mock.fn();
41+
let nes = new NES({ onFrame: onFrame });
42+
let data = fs.readFileSync("roms/croom/croom.nes");
43+
nes.loadROM(new Uint8Array(data));
44+
nes.frame();
45+
assert.strictEqual(onFrame.mock.callCount(), 1);
46+
assert.ok(onFrame.mock.calls[0].arguments[0] instanceof Uint32Array);
47+
assert.strictEqual(onFrame.mock.calls[0].arguments[0].length, 256 * 240);
48+
});
49+
50+
it("produces the same frame buffer from Uint8Array and string", function() {
51+
let stringFrames = [];
52+
let nes1 = new NES({ onFrame: (buf) => stringFrames.push(buf.slice()) });
53+
let data = fs.readFileSync("roms/croom/croom.nes");
54+
nes1.loadROM(data.toString("binary"));
55+
for (let i = 0; i < 6; i++) nes1.frame();
56+
57+
let uint8Frames = [];
58+
let nes2 = new NES({ onFrame: (buf) => uint8Frames.push(buf.slice()) });
59+
nes2.loadROM(new Uint8Array(data));
60+
for (let i = 0; i < 6; i++) nes2.frame();
61+
62+
assert.strictEqual(stringFrames.length, uint8Frames.length);
63+
for (let i = 0; i < stringFrames.length; i++) {
64+
assert.deepStrictEqual(stringFrames[i], uint8Frames[i]);
65+
}
66+
});
67+
3968
describe("#loadROM()", function() {
40-
it("throws an error given an invalid ROM", function() {
69+
it("throws an error given an invalid ROM string", function() {
4170
let nes = new NES();
4271
assert.throws(function() {
4372
nes.loadROM("foo");
4473
}, { message: "Not a valid NES ROM." });
4574
});
75+
76+
it("throws an error given an invalid ROM Uint8Array", function() {
77+
let nes = new NES();
78+
assert.throws(function() {
79+
nes.loadROM(new Uint8Array([0x66, 0x6f, 0x6f]));
80+
}, { message: "Not a valid NES ROM." });
81+
});
4682
});
4783

4884
describe("#frame() with invalid opcode", function() {

0 commit comments

Comments
 (0)