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.
75 lines (74 loc) • 3.17 kB
JavaScript
import IgirException from "../../exceptions/igirException.js";
import IOFile from "../../models/files/ioFile.js";
import FsUtil from "../../utils/fsUtil.js";
import Patch from "./patch.js";
class APSGBAPatch extends Patch {
static FILE_SIGNATURE = Buffer.from("APS1");
/**
* Parse a GBA-format .aps patch file and return an {@link APSGBAPatch}.
*/
static async patchFrom(file) {
const crcBefore = super.getCrcFromPath(file.getExtractedFilePath());
let targetSize = 0;
await file.extractToTempIOFile("r", async (patchFile) => {
patchFile.seek(this.FILE_SIGNATURE.length);
patchFile.skipNext(4);
targetSize = (await patchFile.readNext(4)).readUInt32LE();
});
return new APSGBAPatch(file, crcBefore, void 0, targetSize);
}
/**
* Apply this patch to the input ROM file and write the patched result to the output path.
*/
async createPatchedFile(inputRomFile, outputRomPath, callback) {
await this.getFile().extractToTempIOFile("r", async (patchFile) => {
const header = await patchFile.readNext(APSGBAPatch.FILE_SIGNATURE.length);
if (!header.equals(APSGBAPatch.FILE_SIGNATURE)) {
throw new IgirException(`APS (GBA) patch header is invalid: ${this.getFile().toString()}`);
}
const originalSize = (await patchFile.readNext(4)).readUInt32LE();
if (inputRomFile.getSize() !== originalSize) {
throw new IgirException(
`APS (GBA) patch expected ROM size of ${FsUtil.sizeReadable(originalSize)}: ${this.getFile().toString()}`
);
}
patchFile.skipNext(4);
await APSGBAPatch.writeOutputFile(inputRomFile, outputRomPath, patchFile, callback);
});
}
static async writeOutputFile(inputRomFile, outputRomPath, patchFile, callback) {
await inputRomFile.extractToTempFile(async (tempRomFile) => {
const sourceFile = await IOFile.fileFrom(tempRomFile, "r");
await FsUtil.copyFile(tempRomFile, outputRomPath);
const targetFile = await IOFile.fileFrom(outputRomPath, "r+");
try {
await this.applyPatch(patchFile, sourceFile, targetFile, callback);
} finally {
await targetFile.close();
await sourceFile.close();
}
});
}
static async applyPatch(patchFile, sourceFile, targetFile, callback) {
while (patchFile.getPosition() < patchFile.getSize()) {
const offset = (await patchFile.readNext(4)).readUInt32LE();
patchFile.skipNext(2);
patchFile.skipNext(2);
const xorData = await patchFile.readNext(1024 * 1024);
const sourceData = await sourceFile.readAt(offset, xorData.length);
const targetData = Buffer.allocUnsafe(xorData.length);
for (const [idx, xorDatum] of xorData.entries()) {
targetData[idx] = (idx < sourceData.length ? sourceData[idx] : 0) ^ xorDatum;
}
await targetFile.writeAt(targetData, offset);
if (callback !== void 0) {
const progressPercentage = patchFile.getPosition() / patchFile.getSize();
callback(Math.floor(progressPercentage * targetFile.getSize()));
}
}
}
}
export {
APSGBAPatch as default
};
//# sourceMappingURL=apsGbaPatch.js.map