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.
203 lines (202 loc) • 14 kB
JavaScript
import { ProgressBarSymbol } from "../../console/progressBar.js";
import Game from "../../models/dats/game.js";
import Internationalization from "../../models/dats/internationalization.js";
import IntlUtil from "../../utils/intlUtil.js";
import Module from "../module.js";
class DATParentInferrer extends Module {
// ***** stripGameRegionAndLanguage *****
static REGION_CODES_REGEX = new RegExp(
`\\(((${Internationalization.REGION_CODES.join("|")})[,+-]? ?)+\\)`,
"i"
);
static REGION_NAMES_REGEX = new RegExp(
`\\(((${Internationalization.REGION_NAMES.join("|")})[,+-]? ?)+\\)`,
"i"
);
static LATIN_AMERICA_REGEX = /\(Latin America\)/i;
static LANGUAGES_REGEX = new RegExp(
`\\(((${Internationalization.LANGUAGES.join("|")})[,+-]? ?)+\\)`,
"i"
);
static MULTI_SPACE_REGEX = / +/g;
// ***** stripGameVariants — retail types *****
static ALT_PAREN_REGEX = /\(Alt( [a-z0-9. ]*)?\)/i;
static COLLECTORS_EDITION_REGEX = /\([^)]*Collector's Edition\)/i;
static DIGITAL_RELEASE_REGEX = /\(Digital Release\)/i;
static DISNEY_CLASSIC_REGEX = /\(Disney Classic Games\)/i;
static EVERCADE_REGEX = /\(Evercade\)/i;
static EXTRA_BOX_REGEX = /\(Extra Box\)/i;
static EUROPEAN_VERSION_REGEX = / - European Version/i;
static FUKKOKUBAN_REGEX = /\(Fukkokuban\)/i;
static GENTEIBAN_REGEX = /\([^)]*Genteiban\)/i;
static LIMITED_EDITION_REGEX = /\(Limited[^)]+Edition\)/i;
static LIMITED_RUN_REGEX = /\(Limited Run Games\)/i;
static LODGENET_REGEX = /\(LodgeNet\)/i;
static MADE_IN_REGEX = /\(Made in [^)]+\)/i;
static MAJOR_WAVE_REGEX = /\(Major Wave\)/i;
static MIDWAY_CLASSICS_REGEX = /\((Midway Classics)\)/i;
static PREMIUM_REGEX = /\([^)]*Premium [^)]+\)/i;
static PREVIEW_DISC_REGEX = /\([^)]*Preview Disc\)/i;
static QUBYTE_REGEX = /\(QUByte Classics\)/i;
static RECALLED_REGEX = /\(Recalled\)/i;
static RENKABAN_REGEX = /\(Renkaban\)/i;
static REPRINT_REGEX = /\(Reprint\)/i;
static RERELEASE_REGEX = /\(Rerelease\)/i;
static RETRO_BIT_REGEX = /\(Retro-Bit\)/i;
static REV_VERSION_REGEX = /\((Rev|Version)\s*[a-z0-9.-]*\)/i;
static SEISANBAN_REGEX = /\([^)]*Seisanban\)/i;
static SHOTENBAN_REGEX = /\(Shotenban\)/i;
static SPECIAL_PACK_REGEX = /\(Special Pack\)/i;
static STEAM_REGEX = /\(Steam\)/i;
static SWITCH_ONLINE_REGEX = /\(Switch Online\)/i;
static THE_BEST_REGEX = /\([^)]+ the Best\)/i;
static TAIOUBAN_REGEX = /\([^)]*Taiouban[^)]*\)/i;
static TOKUBETSUBAN_REGEX = /\([^)]*Tokubetsu-?ban[^)]*\)/i;
static VIRTUAL_CONSOLE_REGEX = /\([^)]*Virtual Console\)/i;
// ***** stripGameVariants — non-retail types *****
static DATE_REGEX = /\([0-9]{4}-[0-9]{2}-[0-9]{2}\)/;
static BUILD_REGEX = /\(Build [a-z0-9. ]+\)/i;
static BUNG_PAREN_REGEX = /\(Bung\)/i;
static KIOSK_REGEX = /\(Kiosk[^)]*\)/i;
static NOT_FOR_RESALE_REGEX = /\(Not for Resale\)/i;
static PUBLIC_DOMAIN_REGEX = /\(PD\)/i;
static SPACEWORLD_REGEX = /\(Spaceworld[a-z0-9. ]*\)/i;
static PAREN_VERSION_REGEX = /\(v[0-9.-]+[a-z]*\)/i;
static PAREN_VERSION_LONG_REGEX = /\(Version [0-9.]+[a-z]*\)/i;
// ***** stripGameVariants — Good Tools *****
static VERIFIED_REGEX = /\[!\]/;
static ALT_BRACKET_REGEX = /\[a[0-9]*\]/;
static BUNG_BRACKET_REGEX = /\[bf\]/;
static BAD_CHECKSUM_REGEX = /\[c\]/;
static MIA_STRIP_REGEX = /\[MIA\]/;
static PENDING_DUMP_REGEX = /\[!p\]/;
static BAD_DUMP_REGEX = /\[x\]/;
static WXN_REGEX = /\(Wxn\)/i;
static GOOD_SMS_REGEX = /\((SC-3000|SG-1000|SF-7000|GG2SMS|MSX2SMS|SG2GG)\)/;
static GOOD_GBA_BRACKET_REGEX = /\[(v|eb|eba|ebb|f125|f126)\]/;
static GOOD_GBA_PAREN_REGEX = /\((IQue|MB|MB2GBA)\)/;
static GOOD_GBX_REGEX = /\[(C|S|BF)\]/;
static GOOD_GEN_PAREN_REGEX = /\((1|4|5|8|F|B|J-Cart|SN|REVXB|REVSC02|MP|MD Bundle|Alt Music)\)/;
static GOOD_GEN_BRACKET_REGEX = /\[(c|x)\]/;
static GOOD_NES_PAREN_REGEX = /\((RU|PC10|VS|Aladdin|Sachen|KC|FamiStudio|PRG0|PRG1|FDS Hack|GBA E-reader|E-GC|J-GC)\)/;
static GOOD_NES_BRACKET_REGEX = /\[(FDS|FCN|U)\]/;
static GOOD_SNES_REGEX = /\((BS|ST|NP|NSS)\)/;
static GOOD_N64_REGEX = /\((Beta-WIP|Debug Version|GC|Save|Save-PAL|Z64-Save)\)/;
// ***** stripGameVariants — TOSEC *****
static TOSEC_REGION_REGEX = /\((AE|AL|AS|AT|AU|BA|BE|BG|BR|CA|CH|CL|CN|CS|CY|CZ|DE|DK|EE|EG|ES|EU|FI|FR|GB|GR|HK|HR|HU|ID|IE|IL|IN|IR|IS|IT|JO|JP|KR|LT|LU|LV|MN|MX|MY|NL|NO|NP|NZ|OM|PE|PH|PL|PT|QA|RO|RU|SE|SG|SI|SK|TH|TR|TW|US|VN|YU|ZA)\)/;
static TOSEC_LANGUAGE_REGEX = /\((ar|bg|bs|cs|cy|da|de|el|en|eo|es|et|fa|fi|fr|ga|gu|he|hi|hr|hu|is|it|ja|ko|lt|lv|ms|nl|no|pl|pt|ro|ru|sk|sl|sq|sr|sv|th|tr|ur|vi|yi|zh)\)/;
static TOSEC_DEMO_REGEX = /\((demo|demo-kiosk|demo-playable|demo-rolling|demo-slideshow)\)/;
static TOSEC_DATE_REGEX = /\([0-9x]{4}(-[0-9x]{2}(-[0-9x]{2})?)?\)/;
static TOSEC_VIDEO_REGEX = /\((CGA|EGA|HGC|MCGA|MDA|NTSC|NTSC-PAL|PAL|PAL-60|PAL-NTSC|SVGA|VGA|XGA)\)/i;
static TOSEC_MULTI_LANG_REGEX = /\(M[0-9]+\)/;
static TOSEC_COPYRIGHT_REGEX = /\((CW|CW-R|FW|GW|GW-R|LW|PD|SW|SW-R)\)/i;
static TOSEC_DEVELOPMENT_REGEX = /\((alpha|beta|preview|pre-release|proto)\)/i;
static TOSEC_DUMP_FLAGS_REGEX = /(\[(cr|f|h|m|p|t|tr|o|u|v|b|a|!)([0-9]+| [^\]]+)?\])+/i;
static TOSEC_INLINE_VERSION_REGEX = /(\W)v[0-9]+\.[0-9]+(\W)/i;
// ***** stripGameVariants — specific cases *****
static YEAR_ABBREVIATION_REGEX = /'([0-9][0-9])/;
// ***** stripGameVariants — console-specific *****
static SGB_ENHANCED_REGEX = /\(SGB Enhanced\)/i;
static GB_COMPATIBLE_REGEX = /\(GB Compatible\)/i;
static GAMECUBE_REGEX = /\(GameCube\)/i;
static NINTENDO_POWER_REGEX = /\(NP\)/i;
static DREAMCAST_BOXCODE_REGEX = /\[([0-9A-Z ]+(, )?)+\]$/;
static DREAMCAST_RING_CODE_REGEX = /\[[0-9]+S\]/;
static DREAMCAST_TOSEC_REGEX = /\[(compilation|data identical to retail|fixed version|keyboard|limited edition|req\. microphone|scrambled|unscrambled|white label)\]/gi;
static FOR_DREAMCAST_REGEX = /for Dreamcast/i;
static MEGAPLAY_REGEX = /\(MP\)/i;
static RING_CODE_STRIP_REGEX = /\(RE?-?[0-9]*\)/;
static EDC_REGEX = /\(EDC\)/i;
static PSONE_BOOKS_REGEX = /\(PSone Books\)/i;
static PS1_SERIAL_REGEX = /[(\]](SCES|SCUS|SLES|SLUS)-[0-9]+[(\]]/i;
static PS3_BIOS_REGEX = /\((Arcade|AVTool|Debug|Disc|Patch|Shop|Tool)\)/;
static PSP_SERIAL_REGEX = /[(\]][UN][CLP][AEJKU][BFGHJMSXZ]-[0-9]+[(\]]/i;
options;
constructor(options, progressBar) {
super(progressBar, DATParentInferrer.name);
this.options = options;
}
/**
* Infer {@link Parent}s from {@link Game}s.
*/
infer(dat) {
if (dat.hasParentCloneInfo() && !this.options.getDatIgnoreParentClone()) {
this.prefixedLogger.trace(`${dat.getName()}: DAT has parent/clone info, skipping`);
return dat;
}
if (dat.getGames().length === 0) {
this.prefixedLogger.trace(`${dat.getName()}: no games to process`);
return dat;
}
this.prefixedLogger.trace(
`${dat.getName()}: inferring parents for ${IntlUtil.toLocaleString(dat.getGames().length)} game${dat.getGames().length === 1 ? "" : "s"}`
);
this.progressBar.setSymbol(ProgressBarSymbol.DAT_GROUPING_SIMILAR);
this.progressBar.resetProgress(dat.getGames().length);
const strippedNamesToGames = dat.getGames().reduce((map, game) => {
let strippedGameName = game.getName();
strippedGameName = DATParentInferrer.stripGameRegionAndLanguage(strippedGameName);
strippedGameName = DATParentInferrer.stripGameVariants(strippedGameName);
if (map.has(strippedGameName)) {
map.get(strippedGameName)?.push(game);
} else {
map.set(strippedGameName, [game]);
}
return map;
}, /* @__PURE__ */ new Map());
const groupedGames = [...strippedNamesToGames].toSorted((a, b) => a[0].localeCompare(b[0])).map(([, games]) => games);
const newGames = groupedGames.flatMap((games) => DATParentInferrer.electParent(games));
const inferredDat = dat.withGames(newGames);
this.prefixedLogger.trace(
`${inferredDat.getName()}: grouped to ${IntlUtil.toLocaleString(inferredDat.getParents().length)} parent${inferredDat.getParents().length === 1 ? "" : "s"}`
);
this.prefixedLogger.trace("done inferring parents");
return inferredDat;
}
static stripGameRegionAndLanguage(name) {
let strippedName = name.replace(this.REGION_CODES_REGEX, "").replace(this.REGION_NAMES_REGEX, "").replace(this.LATIN_AMERICA_REGEX, "");
for (const regex of Internationalization.REGION_REGEX) {
strippedName = strippedName.replace(regex, "");
}
return strippedName.replace(this.LANGUAGES_REGEX, "").replaceAll(this.MULTI_SPACE_REGEX, " ").trim();
}
static stripGameVariants(name) {
return name.replace(this.ALT_PAREN_REGEX, "").replace(this.COLLECTORS_EDITION_REGEX, "").replace(this.DIGITAL_RELEASE_REGEX, "").replace(this.DISNEY_CLASSIC_REGEX, "").replace(this.EVERCADE_REGEX, "").replace(this.EXTRA_BOX_REGEX, "").replace(this.EUROPEAN_VERSION_REGEX, "").replace(this.FUKKOKUBAN_REGEX, "").replace(this.GENTEIBAN_REGEX, "").replace(this.LIMITED_EDITION_REGEX, "").replace(this.LIMITED_RUN_REGEX, "").replace(this.LODGENET_REGEX, "").replace(this.MADE_IN_REGEX, "").replace(this.MAJOR_WAVE_REGEX, "").replace(this.MIDWAY_CLASSICS_REGEX, "").replace(this.PREMIUM_REGEX, "").replace(this.PREVIEW_DISC_REGEX, "").replace(this.QUBYTE_REGEX, "").replace(this.RECALLED_REGEX, "").replace(this.RENKABAN_REGEX, "").replace(this.REPRINT_REGEX, "").replace(this.RERELEASE_REGEX, "").replace(this.RETRO_BIT_REGEX, "").replace(this.REV_VERSION_REGEX, "").replace(this.SEISANBAN_REGEX, "").replace(this.SHOTENBAN_REGEX, "").replace(this.SPECIAL_PACK_REGEX, "").replace(this.STEAM_REGEX, "").replace(this.SWITCH_ONLINE_REGEX, "").replace(this.THE_BEST_REGEX, "").replace(this.TAIOUBAN_REGEX, "").replace(this.TOKUBETSUBAN_REGEX, "").replace(this.VIRTUAL_CONSOLE_REGEX, "").replace(this.DATE_REGEX, "").replace(Game.AFTERMARKET_REGEX, "").replace(Game.ALPHA_REGEX, "").replace(Game.BETA_REGEX, "").replace(this.BUILD_REGEX, "").replace(this.BUNG_PAREN_REGEX, "").replace(Game.DEBUG_REGEX, "").replace(Game.DEMO_REGEX, "").replace(Game.HACK_PAREN_REGEX, "").replace(Game.HOMEBREW_REGEX, "").replace(this.KIOSK_REGEX, "").replace(this.NOT_FOR_RESALE_REGEX, "").replace(this.PUBLIC_DOMAIN_REGEX, "").replace(Game.PIRATED_PAREN_REGEX, "").replace(Game.PROGRAM_REGEX, "").replace(Game.PROTOTYPE_REGEX, "").replace(Game.SAMPLE_REGEX, "").replace(this.SPACEWORLD_REGEX, "").replace(Game.UNLICENSED_REGEX, "").replace(this.PAREN_VERSION_REGEX, "").replace(this.PAREN_VERSION_LONG_REGEX, "").replace(this.VERIFIED_REGEX, "").replace(this.ALT_BRACKET_REGEX, "").replace(Game.BAD_REGEX, "").replace(this.BUNG_BRACKET_REGEX, "").replace(this.BAD_CHECKSUM_REGEX, "").replace(Game.FIXED_REGEX, "").replace(Game.HACK_BRACKET_REGEX, "").replace(this.MIA_STRIP_REGEX, "").replace(Game.OVERDUMP_REGEX, "").replace(this.PENDING_DUMP_REGEX, "").replace(Game.PIRATED_BRACKET_REGEX, "").replace(Game.TRAINER_REGEX, "").replace(Game.TRANSLATED_REGEX, "").replace(this.BAD_DUMP_REGEX, "").replace(this.WXN_REGEX, "").replace(this.GOOD_SMS_REGEX, "").replace(this.GOOD_GBA_BRACKET_REGEX, "").replace(this.GOOD_GBA_PAREN_REGEX, "").replace(this.GOOD_GBX_REGEX, "").replace(this.GOOD_GEN_PAREN_REGEX, "").replace(this.GOOD_GEN_BRACKET_REGEX, "").replace(this.GOOD_NES_PAREN_REGEX, "").replace(this.GOOD_NES_BRACKET_REGEX, "").replace(this.GOOD_SNES_REGEX, "").replace(this.GOOD_N64_REGEX, "").replace(this.TOSEC_REGION_REGEX, "").replace(this.TOSEC_LANGUAGE_REGEX, "").replace(this.TOSEC_DEMO_REGEX, "").replace(this.TOSEC_DATE_REGEX, "").replace(this.TOSEC_VIDEO_REGEX, "").replace(this.TOSEC_MULTI_LANG_REGEX, "").replace(this.TOSEC_COPYRIGHT_REGEX, "").replace(this.TOSEC_DEVELOPMENT_REGEX, "").replace(this.TOSEC_DUMP_FLAGS_REGEX, "").replace(this.TOSEC_INLINE_VERSION_REGEX, "$1 $2").replace(this.YEAR_ABBREVIATION_REGEX, "$1").replace(this.SGB_ENHANCED_REGEX, "").replace(this.GB_COMPATIBLE_REGEX, "").replace(this.GAMECUBE_REGEX, "").replace(this.NINTENDO_POWER_REGEX, "").replace(this.DREAMCAST_BOXCODE_REGEX, "").replace(this.DREAMCAST_RING_CODE_REGEX, "").replaceAll(this.DREAMCAST_TOSEC_REGEX, "").replace(this.FOR_DREAMCAST_REGEX, "").replace(this.MEGAPLAY_REGEX, "").replace(this.RING_CODE_STRIP_REGEX, "").replace(this.EDC_REGEX, "").replace(this.PSONE_BOOKS_REGEX, "").replace(this.PS1_SERIAL_REGEX, "").replace(this.PS3_BIOS_REGEX, "").replace(this.PSP_SERIAL_REGEX, "").replaceAll(this.MULTI_SPACE_REGEX, " ").trim();
}
static electParent(games) {
const strippedNamesToGames = games.reduce((map, game) => {
let strippedGameName = game.getName();
strippedGameName = this.stripGameRegionAndLanguage(strippedGameName);
if (!map.has(strippedGameName)) {
map.set(strippedGameName, game);
}
return map;
}, /* @__PURE__ */ new Map());
return games.map((game, idx) => {
let strippedGameName = game.getName();
strippedGameName = this.stripGameRegionAndLanguage(strippedGameName);
strippedGameName = this.stripGameVariants(strippedGameName);
const retailParent = strippedNamesToGames.get(strippedGameName);
if (retailParent) {
if (retailParent.hashCode() === game.hashCode()) {
return game.withProps({ cloneOf: void 0, cloneOfId: void 0 });
}
if (retailParent.getId() !== void 0) {
return game.withProps({ cloneOfId: retailParent.getId(), cloneOf: void 0 });
}
return game.withProps({ cloneOfId: void 0, cloneOf: retailParent.getName() });
}
if (idx === 0) {
return game.withProps({ cloneOf: void 0, cloneOfId: void 0 });
}
if (games[0].getId() !== void 0) {
return game.withProps({ cloneOfId: games[0].getId(), cloneOf: void 0 });
}
return game.withProps({ cloneOfId: void 0, cloneOf: games[0].getName() });
});
}
}
export {
DATParentInferrer as default
};
//# sourceMappingURL=datParentInferrer.js.map