import type { RomReader } from "./reader"; import { WRAM } from "./profile"; /** One step of an instruction pattern: an opcode byte that must match, or * an operand to read ("b" a byte, "w" a little-endian word). */ type Step = number | "b" | "w"; /** An ID line: ld hl, coord; three tiles (ld a / ld [hli], a twice, then * ld [hl]); ld hl, the number's coord. */ export const ID_LINE: readonly Step[] = [0x21, "w", 0x3e, "b", 0x22, 0x3e, "b", 0x22, 0x36, "b", 0x21, "w"]; /** The operands of the instructions at `address`, which must follow * `pattern`. The European releases keep many routines' instructions and * change only their operands (where a string or a number goes, which tiles a * line draws), so the port reads those operands from the cartridge. Another * instruction there is logic the port has not ported for that cartridge. */ export function operands(r: RomReader, address: number, pattern: readonly Step[]): number[] { const found = match(r, address, pattern); if (!found) throw new Error(`The routine at 0x${address.toString(16)} differs on this cartridge; it is not ported yet.`); return found.values; } /** The pattern's operands at `address` and where its instructions end, or * undefined when the instructions there are others. */ export function match(r: RomReader, address: number, pattern: readonly Step[]): { values: number[]; end: number } | undefined { const values: number[] = []; let p = address; for (const step of pattern) { if (step === "b") values.push(r.byte(p++)); else if (step === "w") { values.push(r.word(p)); p += 2; } else if (r.byte(p++) !== step) return undefined; } return { values, end: p }; } /** The operands of the first place in `from` .. `from + limit` where the * pattern's instructions are, for a line a routine draws without a label of * its own. */ export function findOperands(r: RomReader, from: number, limit: number, pattern: readonly Step[]): number[] { return findMatch(r, from, limit, pattern).values; } export function findMatch(r: RomReader, from: number, limit: number, pattern: readonly Step[]) { for (let p = from; p < from + limit; p++) { const found = match(r, p, pattern); if (found) return found; } throw new Error(`The routine at 0x${from.toString(16)} differs on this cartridge; it is not ported yet.`); } /** A pointer operand of code in `routine`'s bank, as a ROM offset. */ export const nearPointer = (r: RomReader, routine: number, pointer: number) => pointer < 0x4000 ? pointer : r.bank(routine) * 0x4000 + pointer - 0x4000; /** hlcoord's tile position from a wTilemap address. */ export function tileCoord(address: number): [number, number] { const offset = address - WRAM.wTilemap; if (offset < 0 || offset >= 20 * 18) throw new Error("A screen position lies outside the tilemap."); return [offset % 20, Math.floor(offset / 20)]; }