55 lines
1.5 KiB
TypeScript
55 lines
1.5 KiB
TypeScript
/** Bounds-checked access to the user's cartridge. No CPU or hardware state. */
|
|
export class RomReader {
|
|
constructor(readonly bytes: Uint8Array) {}
|
|
byte(offset: number): number {
|
|
this.check(offset, 1);
|
|
return this.bytes[offset];
|
|
}
|
|
word(offset: number): number {
|
|
return this.byte(offset) | (this.byte(offset + 1) << 8);
|
|
}
|
|
slice(offset: number, length: number): Uint8Array {
|
|
this.check(offset, length);
|
|
return this.bytes.slice(offset, offset + length);
|
|
}
|
|
address(bank: number, address: number): number {
|
|
if (
|
|
!Number.isInteger(bank) ||
|
|
bank < 0 ||
|
|
bank >= this.bytes.length / 0x4000 ||
|
|
!Number.isInteger(address) ||
|
|
address < 0 ||
|
|
address >= 0x8000
|
|
) {
|
|
throw new Error("Invalid ROM bank or address.");
|
|
}
|
|
const offset =
|
|
address < 0x4000 ? address : bank * 0x4000 + address - 0x4000;
|
|
this.check(offset, 1);
|
|
return offset;
|
|
}
|
|
near(pointer: number, bank: number): number {
|
|
return this.address(bank, this.word(pointer));
|
|
}
|
|
far(pointer: number): number {
|
|
return this.near(pointer + 1, this.byte(pointer));
|
|
}
|
|
bank(offset: number): number {
|
|
this.check(offset, 1);
|
|
return Math.floor(offset / 0x4000);
|
|
}
|
|
private check(offset: number, length: number) {
|
|
if (
|
|
!Number.isInteger(offset) ||
|
|
!Number.isInteger(length) ||
|
|
offset < 0 ||
|
|
length < 0 ||
|
|
offset + length > this.bytes.length
|
|
) {
|
|
throw new Error(
|
|
"Cartridge data is truncated or contains an invalid pointer.",
|
|
);
|
|
}
|
|
}
|
|
}
|