This commit is contained in:
1 parent
2127f5f459
commit
8b80d05ca2
56 files changed
+6910
-1
No files matched your search
@@ -0,0 +1,106 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { existsSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
const romPath = resolve(
|
||||
process.env.CRYSTAL_ROM_PATH ??
|
||||
"Pokemon - Crystal Version (UE) (V1.1) [C][!].gbc",
|
||||
);
|
||||
|
||||
test("native decompression matches original GB assembly executing in the reference emulator", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip(!existsSync(romPath), "Requires the local user ROM.");
|
||||
await page.goto("/reference.html");
|
||||
// File chooser supplies bytes to an isolated test input, without an HTTP ROM endpoint.
|
||||
await page.evaluate(() => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.id = "reference-fixture";
|
||||
document.body.append(input);
|
||||
});
|
||||
await page.locator("#reference-fixture").setInputFiles(romPath);
|
||||
const result = await page.evaluate(async () => {
|
||||
const { BinjgbEmulator } = await import("/src/emulator/binjgb.ts");
|
||||
const { CrystalAssets } = await import("/src/game/rom/assets.ts");
|
||||
const { decompress } = await import("/src/game/rom/lz.ts");
|
||||
const { SYMBOLS, NATIVE_MAPS } = await import("/src/game/rom/profile.ts");
|
||||
const file = (
|
||||
document.querySelector("#reference-fixture") as HTMLInputElement
|
||||
).files![0];
|
||||
const bytes = new Uint8Array(await file.arrayBuffer());
|
||||
const canvas = document.createElement("canvas");
|
||||
const reference = new BinjgbEmulator();
|
||||
const assets = new CrystalAssets(bytes);
|
||||
await reference.load(bytes, canvas);
|
||||
const comparisons: {
|
||||
map: string;
|
||||
bytes: number;
|
||||
equal: boolean;
|
||||
returned: boolean;
|
||||
}[] = [];
|
||||
for (const id of Object.keys(NATIVE_MAPS)) {
|
||||
const map = assets.map(id);
|
||||
const pointer = assets.reader.far(SYMBOLS.tilesets + map.tileset * 15);
|
||||
const bank = Math.floor(pointer / 0x4000),
|
||||
address = 0x4000 + (pointer % 0x4000);
|
||||
const native = decompress(assets.reader, pointer);
|
||||
// Original test trampoline in WRAM: DI; SP=$c7fe; A=bank;
|
||||
// HL=compressed ROM address; DE=$d000; CALL FarDecompress ($0b40);
|
||||
// write a sentinel and loop. The referenced routine is the user's ROM.
|
||||
const program = [
|
||||
0xf3,
|
||||
0x31,
|
||||
0xfe,
|
||||
0xc7,
|
||||
0x3e,
|
||||
bank,
|
||||
0x21,
|
||||
address & 255,
|
||||
address >> 8,
|
||||
0x11,
|
||||
0,
|
||||
0xd0,
|
||||
0xcd,
|
||||
0x40,
|
||||
0x0b,
|
||||
0x3e,
|
||||
0x42,
|
||||
0xea,
|
||||
0xff,
|
||||
0xc6,
|
||||
0x18,
|
||||
0xfe,
|
||||
];
|
||||
reference.writeMemory(0xff70, 6);
|
||||
reference.writeMemory(0xff9d, 1);
|
||||
reference.writeMemory(0xc6ff, 0);
|
||||
program.forEach((value, index) =>
|
||||
reference.writeMemory(0xc000 + index, value),
|
||||
);
|
||||
reference.setProgramCounter(0xc000);
|
||||
reference.advanceFrames(30);
|
||||
comparisons.push({
|
||||
map: id,
|
||||
bytes: native.length,
|
||||
returned: reference.readMemory(0xc6ff) === 0x42,
|
||||
equal: native.every(
|
||||
(value, index) => reference.readMemory(0xd000 + index) === value,
|
||||
),
|
||||
});
|
||||
}
|
||||
reference.dispose();
|
||||
return comparisons;
|
||||
});
|
||||
expect(result.length).toBe(6);
|
||||
for (const comparison of result) {
|
||||
expect(
|
||||
comparison.returned,
|
||||
`${comparison.map}: original routine returned`,
|
||||
).toBe(true);
|
||||
expect(
|
||||
comparison.equal,
|
||||
`${comparison.map}: ${comparison.bytes} decompressed bytes match`,
|
||||
).toBe(true);
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user