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
JavaScript
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