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