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.

267 lines (266 loc) • 9.71 kB
import { writeToString } from "@fast-csv/format"; import chalk from "chalk"; import ArrayUtil from "../utils/arrayUtil.js"; import IntlUtil from "../utils/intlUtil.js"; const ROMType = { GAME: "games", BIOS: "BIOSes", DEVICE: "devices", RETAIL: "retail releases", PATCHED: "patched games" }; const GameStatus = { // The Game wanted to be written, and it has no ROMs or every ROM was found FOUND: 1, // Only some of the Game's ROMs were found INCOMPLETE: 2, // The Game wanted to be written, but there was no matching ReleaseCandidate MISSING: 3, // The input file was not used in any ReleaseCandidate, but a duplicate file was DUPLICATE: 4, // The input File was not used in any ReleaseCandidate, and neither was any duplicate file UNUSED: 5, // The output File was not from any ReleaseCandidate, so it was deleted DELETED: 6 }; const GameStatusInverted = Object.fromEntries( Object.entries(GameStatus).map(([key, value]) => [value, key]) ); class DATStatus { dat; allRomTypesToGames = /* @__PURE__ */ new Map(); foundRomTypesToCandidates = /* @__PURE__ */ new Map(); incompleteRomTypesToCandidates = /* @__PURE__ */ new Map(); constructor(options, dat, candidates) { this.dat = dat; const indexedCandidates = candidates.reduce((map, candidate) => { const key = candidate.getGame().hashCode(); if (map.has(key)) { map.get(key)?.push(candidate); } else { map.set(key, [candidate]); } return map; }, /* @__PURE__ */ new Map()); for (const game of dat.getGames()) { DATStatus.pushValueIntoMap(this.allRomTypesToGames, game, game); const expectedCount = DATStatus.getExpectedFileCount(game, options); const gameCandidates = indexedCandidates.get(game.hashCode()); if (gameCandidates !== void 0 || expectedCount === 0) { const gameCandidate = gameCandidates?.at(0); if (gameCandidate && gameCandidate.getRomsWithFiles().length !== expectedCount) { DATStatus.pushValueIntoMap(this.incompleteRomTypesToCandidates, game, gameCandidate); continue; } DATStatus.pushValueIntoMap(this.foundRomTypesToCandidates, game, gameCandidate); } } for (const candidate of candidates) { if (!candidate.isPatched()) { continue; } const game = candidate.getGame(); DATStatus.append(this.allRomTypesToGames, ROMType.PATCHED, game); DATStatus.append(this.foundRomTypesToCandidates, ROMType.PATCHED, candidate); } } /** * Return the number of {@link ROM}s and {@link Disk}s that must be present for a * {@link Game} to be considered FOUND, taking into account options that exclude * certain file types (e.g. `--exclude-disks`). */ static getExpectedFileCount(game, options) { return game.getRoms().length + (options.getExcludeDisks() ? 0 : game.getDisks().length); } static pushValueIntoMap(map, game, value) { this.append(map, ROMType.GAME, value); if (game.getIsBios()) { this.append(map, ROMType.BIOS, value); } if (game.getIsDevice()) { this.append(map, ROMType.DEVICE, value); } if (game.isRetail()) { this.append(map, ROMType.RETAIL, value); } } static append(map, romType, value) { if (map.has(romType)) { map.get(romType)?.push(value); } else { map.set(romType, [value]); } } getDATName() { return this.dat.getName(); } getInputFiles() { return [ ...this.foundRomTypesToCandidates.values(), ...this.incompleteRomTypesToCandidates.values() ].flat().flatMap((candidate) => candidate === void 0 ? [] : candidate.getRomsWithFiles()).map((romWithFiles) => romWithFiles.getInputFile()); } /** * If any {@link Game} in the entire {@link DAT} was found in the input files. */ anyGamesFound(options) { return DATStatus.getAllowedTypes(options).reduce((result, romType) => { const foundCandidates = this.foundRomTypesToCandidates.get(romType)?.length ?? 0; return result || foundCandidates > 0; }, false); } /** * Return a string of CLI-friendly output to be printed by a {@link Logger}. */ toConsole(options) { return `${DATStatus.getAllowedTypes(options).filter((type) => { const games = this.allRomTypesToGames.get(type); return games !== void 0 && games.length > 0; }).map((type) => { const found = this.foundRomTypesToCandidates.get(type) ?? []; if (!options.usingDats()) { return `${IntlUtil.toLocaleString(found.length)} ${type}`; } const all = this.allRomTypesToGames.get(type) ?? []; const percentage = found.length / all.length * 100; let color; if (percentage >= 100) { color = chalk.rgb(0, 166, 0); } else if (percentage >= 75) { color = chalk.rgb(153, 153, 0); } else if (percentage >= 50) { color = chalk.rgb(160, 124, 0); } else if (percentage >= 25) { color = chalk.rgb(162, 93, 0); } else if (percentage > 0) { color = chalk.rgb(160, 59, 0); } else { color = chalk.rgb(153, 0, 0); } if (type === ROMType.PATCHED) { return `${color(IntlUtil.toLocaleString(all.length))} ${type}`; } return `${color(IntlUtil.toLocaleString(found.length))}/${IntlUtil.toLocaleString(all.length)} ${type}`; }).filter((string_) => string_.length > 0).join(", ")} ${options.shouldWrite() ? "written" : "found"}`; } /** * Return the file contents of a CSV with status information for every {@link Game}. */ async toCsv(options) { const foundCandidates = DATStatus.getValuesForAllowedTypes( options, this.foundRomTypesToCandidates ); const incompleteCandidates = DATStatus.getValuesForAllowedTypes( options, this.incompleteRomTypesToCandidates ); const rows = DATStatus.getValuesForAllowedTypes(options, this.allRomTypesToGames).reduce(ArrayUtil.reduceUnique(), []).toSorted((a, b) => a.getName().localeCompare(b.getName())).map((game) => { let status = GameStatus.MISSING; const incompleteCandidate = incompleteCandidates.find( (candidate) => candidate.getGame().equals(game) ); if (incompleteCandidate) { status = GameStatus.INCOMPLETE; } const foundCandidate = foundCandidates.find( (candidate) => candidate?.getGame().equals(game) ); if (foundCandidate !== void 0 || DATStatus.getExpectedFileCount(game, options) === 0) { status = GameStatus.FOUND; } const filePaths = [ ...incompleteCandidate ? incompleteCandidate.getRomsWithFiles() : [], ...foundCandidate ? foundCandidate.getRomsWithFiles() : [] ].map( (romWithFiles) => options.shouldWrite() ? romWithFiles.getOutputFile() : romWithFiles.getInputFile() ).map((file) => file.getFilePath()).reduce(ArrayUtil.reduceUnique(), []); return DATStatus.buildCsvRow( this.getDATName(), game.getName(), status, filePaths, foundCandidate?.isPatched() ?? false, game.getIsBios(), game.isRetail(), game.isUnlicensed(), game.isDebug(), game.isDemo(), game.isBeta(), game.isSample(), game.isPrototype(), game.isProgram(), game.isAftermarket(), game.isHomebrew(), game.isBad() ); }); return await writeToString(rows, { headers: [ "DAT Name", "Game Name", "Status", "ROM Files", "Patched", "BIOS", "Retail Release", "Unlicensed", "Debug", "Demo", "Beta", "Sample", "Prototype", "Program", "Aftermarket", "Homebrew", "Bad" ] }); } /** * Return a string of CSV rows without headers for a certain {@link GameStatusValue}. */ static async filesToCsv(filePaths, status) { return await writeToString( filePaths.map((filePath) => this.buildCsvRow("", "", status, [filePath])) ); } static buildCsvRow(datName, gameName, status, filePaths = [], isPatched = false, isBios = false, isRetail = false, isUnlicensed = false, isDebug = false, isDemo = false, isBeta = false, isSample = false, isPrototype = false, isTest = false, isAftermarket = false, isHomebrew = false, isBad = false) { return [ datName, gameName, GameStatusInverted[status], filePaths.join("|"), String(isPatched), String(isBios), String(isRetail), String(isUnlicensed), String(isDebug), String(isDemo), String(isBeta), String(isSample), String(isPrototype), String(isTest), String(isAftermarket), String(isHomebrew), String(isBad) ]; } static getValuesForAllowedTypes(options, romTypesToValues) { return this.getAllowedTypes(options).flatMap((type) => romTypesToValues.get(type)).filter((value) => value !== void 0).reduce(ArrayUtil.reduceUnique(), []).toSorted(); } static getAllowedTypes(options) { return [ !options.getOnlyBios() && !options.getOnlyDevice() && !options.getOnlyRetail() ? ROMType.GAME : void 0, options.getOnlyBios() || !options.getNoBios() && !options.getOnlyDevice() ? ROMType.BIOS : void 0, options.getOnlyDevice() || !options.getOnlyBios() && !options.getNoDevice() ? ROMType.DEVICE : void 0, options.getOnlyRetail() || !options.getOnlyBios() && !options.getOnlyDevice() ? ROMType.RETAIL : void 0, ROMType.PATCHED ].filter((romType) => romType !== void 0); } } export { GameStatus, DATStatus as default }; //# sourceMappingURL=datStatus.js.map