UNPKG

igir

Version:

🕹 A zero-setup ROM collection manager that sorts, filters, extracts or archives, patches, and reports on collections of any size on any OS.

91 lines (90 loc) • 3.97 kB
import IOFile from '../../polyfill/ioFile.js'; import IgirException from '../exceptions/igirException.js'; import Patch from './patch.js'; const APSN64PatchType = { SIMPLE: 0, N64: 1, }; /** * @see https://github.com/btimofeev/UniPatcher/wiki/APS-(N64) */ export default class APSN64Patch extends Patch { static FILE_SIGNATURE = Buffer.from('APS10'); patchType; constructor(patchType, file, crcBefore, sizeAfter) { super(file, crcBefore, undefined, sizeAfter); this.patchType = patchType; } static async patchFrom(file) { let patchType = APSN64PatchType.SIMPLE; const crcBefore = Patch.getCrcFromPath(file.getExtractedFilePath()); let targetSize = 0; await file.extractToTempIOFile('r', async (patchFile) => { patchFile.seek(APSN64Patch.FILE_SIGNATURE.length); patchType = (await patchFile.readNext(1)).readUInt8(); patchFile.skipNext(1); // encoding method patchFile.skipNext(50); // description if (patchType === APSN64PatchType.SIMPLE) { targetSize = (await patchFile.readNext(4)).readUInt32LE(); } else if (patchType === APSN64PatchType.N64) { patchFile.skipNext(1); // ROM format patchFile.skipNext(2); // cart ID string (*'s from: NUS-N**X-XXX) patchFile.skipNext(1); // country string (* from: NUS-NXX*-XXX) patchFile.skipNext(8); // CRC within the ROM (NOT the entire ROM CRC) patchFile.skipNext(5); // padding targetSize = (await patchFile.readNext(4)).readUInt32LE(); } else { throw new IgirException(`APS (N64) patch type ${patchType} isn't supported: ${patchFile.getPathLike().toString()}`); } }); return new APSN64Patch(patchType, file, crcBefore, targetSize); } async createPatchedFile(inputRomFile, outputRomPath) { return this.getFile().extractToTempIOFile('r', async (patchFile) => { const header = await patchFile.readNext(APSN64Patch.FILE_SIGNATURE.length); if (!header.equals(APSN64Patch.FILE_SIGNATURE)) { throw new IgirException(`APS (N64) patch header is invalid: ${this.getFile().toString()}`); } if (this.patchType === APSN64PatchType.SIMPLE) { patchFile.seek(61); } else if (this.patchType === APSN64PatchType.N64) { patchFile.seek(78); } else { throw new IgirException(`APS (N64) patch type ${this.patchType} isn't supported: ${patchFile.getPathLike().toString()}`); } return APSN64Patch.writeOutputFile(inputRomFile, outputRomPath, patchFile); }); } static async writeOutputFile(inputRomFile, outputRomPath, patchFile) { await inputRomFile.extractToFile(outputRomPath); const targetFile = await IOFile.fileFrom(outputRomPath, 'r+'); try { await APSN64Patch.applyPatch(patchFile, targetFile); } finally { await targetFile.close(); } } static async applyPatch(patchFile, targetFile) { while (patchFile.getPosition() < patchFile.getSize()) { const offset = (await patchFile.readNext(4)).readUInt32LE(); const size = (await patchFile.readNext(1)).readUInt8(); let data; if (size === 0) { // Run-length encoding record const byte = await patchFile.readNext(1); const rleSize = (await patchFile.readNext(1)).readUInt8(); data = Buffer.from(byte.toString('hex').repeat(rleSize), 'hex'); } else { // Standard record data = await patchFile.readNext(size); } await targetFile.writeAt(data, offset); } } }