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.

161 lines (160 loc) • 6.75 kB
import path from "node:path"; import { ProgressBarSymbol } from "../../console/progressBar.js"; import ROM from "../../models/dats/rom.js"; import ArchiveEntry from "../../models/files/archives/archiveEntry.js"; import ArchiveFile from "../../models/files/archives/archiveFile.js"; import Zip from "../../models/files/archives/zip.js"; import ROMWithFiles from "../../models/romWithFiles.js"; import WriteCandidate from "../../models/writeCandidate.js"; import IntlUtil from "../../utils/intlUtil.js"; import Module from "../module.js"; class CandidatePatchGenerator extends Module { options; constructor(options, progressBar) { super(progressBar, CandidatePatchGenerator.name); this.options = options; } /** * Generate the patched candidates. */ generate(dat, candidates, patches) { if (candidates.length === 0) { this.prefixedLogger.trace(`${dat.getName()}: no candidates to make patched candidates for`); return candidates; } if (patches.length === 0) { this.prefixedLogger.trace(`${dat.getName()}: no patches to make patched candidates for`); return candidates; } this.prefixedLogger.trace(`${dat.getName()}: generating patched candidates`); this.progressBar.setSymbol(ProgressBarSymbol.CANDIDATE_PATCHING); this.progressBar.resetProgress(candidates.length); const crcToPatches = CandidatePatchGenerator.indexPatchesByCrcBefore(patches); this.prefixedLogger.trace( `${dat.getName()}: found patches for ${IntlUtil.toLocaleString(crcToPatches.size)} unique CRC32s` ); const patchedCandidates = this.build(dat, candidates, crcToPatches); const patchedCandidateCount = this.options.getPatchOnly() ? patchedCandidates.length : patchedCandidates.length - candidates.length; this.prefixedLogger.trace( `${dat.getName()}: done generating ${IntlUtil.toLocaleString(patchedCandidateCount)} patched candidate${patchedCandidateCount === 1 ? "" : "s"}` ); return patchedCandidates; } static indexPatchesByCrcBefore(patches) { return patches.reduce((map, patch) => { const key = patch.getCrcBefore().toLowerCase(); if (map.has(key)) { map.get(key)?.push(patch); } else { map.set(key, [patch]); } return map; }, /* @__PURE__ */ new Map()); } build(dat, candidates, crcToPatches) { if (this.options.getPatchOnly()) { this.prefixedLogger.trace(`${dat.getName()}: only returning patched candidates`); } return candidates.flatMap((unpatchedCandidate) => { const patchedCandidates = this.buildPatchedCandidates(dat, unpatchedCandidate, crcToPatches); if (this.options.getPatchOnly()) { return patchedCandidates; } return [unpatchedCandidate, ...patchedCandidates]; }); } buildPatchedCandidates(dat, unpatchedCandidate, crcToPatches) { if (unpatchedCandidate.getRomsWithFiles().some( (romWithFiles) => romWithFiles.getInputFile() instanceof ArchiveFile && !this.options.shouldZipRom(romWithFiles.getRom()) )) { return []; } const candidatePatches = unpatchedCandidate.getRomsWithFiles().flatMap((romWithFiles) => { const inputFile = romWithFiles.getInputFile(); if (inputFile instanceof ArchiveFile) { const entryCrc32 = inputFile.getArchiveEntry().getCrc32(); if (entryCrc32 === void 0) { return []; } return crcToPatches.get(entryCrc32) ?? []; } const inputFileCrc32 = inputFile.getCrc32(); if (inputFileCrc32 === void 0) { return []; } return crcToPatches.get(inputFileCrc32) ?? []; }); if (candidatePatches.length === 0) { return []; } return candidatePatches.map((patch) => { const patchedRomName = patch.getRomName(); const romsWithFiles = unpatchedCandidate.getRomsWithFiles().map((romWithFiles) => { let rom = romWithFiles.getRom(); let inputFile = romWithFiles.getInputFile(); let outputFile = romWithFiles.getOutputFile(); if (inputFile instanceof ArchiveFile) { const archiveEntry = inputFile.getArchiveEntry(); inputFile = archiveEntry; outputFile = archiveEntry.withProps({ archive: new Zip(outputFile.getFilePath()), entryPath: archiveEntry.getEntryPath() }); } if (patch.getCrcBefore() === romWithFiles.getRom().getCrc32()) { inputFile = inputFile.withPatch(patch); const extMatch = /[^.]+((\.[a-zA-Z0-9]+)+)$/.exec(romWithFiles.getRom().getName()); const extractedFileName = patchedRomName + (extMatch === null ? "" : extMatch[1]); if (outputFile instanceof ArchiveEntry) { outputFile = outputFile.withProps({ archive: outputFile.getArchive().withFilePath( path.join(path.dirname(outputFile.getFilePath()), patchedRomName) + outputFile.getArchive().getExtension() ), // Output is an archive of a single file, the entry path should also change entryPath: unpatchedCandidate.getRomsWithFiles().length === 1 ? extractedFileName : outputFile.getEntryPath(), size: patch.getSizeAfter(), crc32: patch.getCrcAfter(), md5: void 0, sha1: void 0, sha256: void 0 }); } else { const dirName = path.dirname(outputFile.getFilePath()); outputFile = outputFile.withFilePath(path.join(dirName, extractedFileName)).withProps({ size: patch.getSizeAfter(), crc32: patch.getCrcAfter(), md5: void 0, sha1: void 0, sha256: void 0 }); } const romName = path.join( path.dirname(rom.getName().replaceAll("/", path.sep)), path.basename(outputFile.getExtractedFilePath()) ); rom = new ROM({ name: romName, size: outputFile.getSize(), crc32: outputFile.getCrc32() }); this.prefixedLogger.trace( `${dat.getName()}: ${inputFile.toString()}: patch candidate generated: ${outputFile.toString()}` ); } return new ROMWithFiles(rom, inputFile, outputFile); }); const gameName = path.join( path.dirname(unpatchedCandidate.getGame().getName().replaceAll(/[\\/]/g, path.sep)), patchedRomName ); const patchedGame = unpatchedCandidate.getGame().withProps({ name: gameName }); return new WriteCandidate(patchedGame, romsWithFiles); }); } } export { CandidatePatchGenerator as default }; //# sourceMappingURL=candidatePatchGenerator.js.map