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.

203 lines (202 loc) • 14 kB
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