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.
108 lines (107 loc) • 4.12 kB
JavaScript
import ArrayPoly from '../../polyfill/arrayPoly.js';
import APSPatch from './apsPatch.js';
import BPSPatch from './bpsPatch.js';
import DPSPatch from './dpsPatch.js';
import IPSPatch from './ipsPatch.js';
import NinjaPatch from './ninjaPatch.js';
import PPFPatch from './ppfPatch.js';
import UPSPatch from './upsPatch.js';
import VcdiffPatch from './vcdiffPatch.js';
/**
* NOTE(cemmer): this file exists to prevent circular dependencies between Patch and its children.
*/
export default class PatchFactory {
static PATCH_PARSERS = [
{
extensions: APSPatch.SUPPORTED_EXTENSIONS,
fileSignatures: [APSPatch.FILE_SIGNATURE],
factory: APSPatch.patchFrom,
},
{
extensions: BPSPatch.SUPPORTED_EXTENSIONS,
fileSignatures: [BPSPatch.FILE_SIGNATURE],
factory: BPSPatch.patchFrom,
},
{
extensions: DPSPatch.SUPPORTED_EXTENSIONS,
fileSignatures: [],
factory: DPSPatch.patchFrom,
},
{
extensions: IPSPatch.SUPPORTED_EXTENSIONS,
fileSignatures: IPSPatch.FILE_SIGNATURES,
factory: IPSPatch.patchFrom,
},
{
extensions: NinjaPatch.SUPPORTED_EXTENSIONS,
fileSignatures: [NinjaPatch.FILE_SIGNATURE],
factory: NinjaPatch.patchFrom,
},
{
extensions: PPFPatch.SUPPORTED_EXTENSIONS,
fileSignatures: [PPFPatch.FILE_SIGNATURE],
factory: PPFPatch.patchFrom,
},
{
extensions: UPSPatch.SUPPORTED_EXTENSIONS,
fileSignatures: [UPSPatch.FILE_SIGNATURE],
factory: UPSPatch.patchFrom,
},
{
extensions: VcdiffPatch.SUPPORTED_EXTENSIONS,
fileSignatures: [VcdiffPatch.FILE_SIGNATURE],
factory: VcdiffPatch.patchFrom,
},
];
static MAX_HEADER_LENGTH_BYTES = Object.values(PatchFactory.PATCH_PARSERS)
.flatMap((parser) => parser.fileSignatures)
.reduce((max, fileSignature) => Math.max(max, fileSignature.length), 0);
static getSupportedExtensions() {
return Object.values(PatchFactory.PATCH_PARSERS)
.flatMap((parser) => parser.extensions)
.reduce(ArrayPoly.reduceUnique(), [])
.sort();
}
static async patchFromFilename(file) {
const filePath = file.getExtractedFilePath();
const parsers = Object.values(this.PATCH_PARSERS);
for (const parser of parsers) {
if (parser.extensions.some((ext) => filePath.toLowerCase().endsWith(ext))) {
return parser.factory(file);
}
}
return undefined;
}
static async readHeaderHex(stream, length) {
return new Promise((resolve, reject) => {
stream.resume();
const chunks = [];
const resolveHeader = () => {
const header = Buffer.concat(chunks).subarray(0, length).toString('hex').toLowerCase();
resolve(header);
};
stream.on('data', (chunk) => {
if (chunk.length > 0) {
chunks.push(chunk);
}
// Stop reading when we get enough data, trigger a 'close' event
if (chunks.reduce((sum, buff) => sum + buff.length, 0) >= length) {
resolveHeader();
stream.destroy();
}
});
stream.on('end', resolveHeader);
stream.on('error', reject);
});
}
static async patchFromFileContents(file) {
const fileHeader = await file.createReadStream(async (readable) => PatchFactory.readHeaderHex(readable, this.MAX_HEADER_LENGTH_BYTES));
const parsers = Object.values(this.PATCH_PARSERS);
for (const parser of parsers) {
if (parser.fileSignatures.some((fileSignature) => fileHeader.startsWith(fileSignature.toString('hex')))) {
return parser.factory(file);
}
}
return undefined;
}
}