From d8e8c2526d94d155816a2a531480fb6932611d9f Mon Sep 17 00:00:00 2001 From: Christian Emmer <10749361+emmercm@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:46:13 -0700 Subject: [PATCH 1/2] Implementation --- src/modules/argumentsParser.ts | 1 + src/types/logiqx/game.ts | 72 ++++++++++++++++++++++++++++++++++ src/types/options.ts | 13 ++++++ 3 files changed, 86 insertions(+) diff --git a/src/modules/argumentsParser.ts b/src/modules/argumentsParser.ts index 657bfe0d7..89c27558f 100644 --- a/src/modules/argumentsParser.ts +++ b/src/modules/argumentsParser.ts @@ -528,6 +528,7 @@ Advanced usage: {datName} The name of the DAT that contains the ROM (e.g. "Nintendo - Game Boy") {datReleaseRegion} The region of the ROM release (e.g. "USA"), each ROM can have multiple {datReleaseLanguage} The language of the ROM release (e.g. "En"), each ROM can have multiple + {gameType} The type of the game (e.g. "Retail", "Demo", "Prototype") {inputDirname} The input file's dirname {outputBasename} Equivalent to "{outputName}.{outputExt}" diff --git a/src/types/logiqx/game.ts b/src/types/logiqx/game.ts index 842177a2f..7943c21bc 100644 --- a/src/types/logiqx/game.ts +++ b/src/types/logiqx/game.ts @@ -9,6 +9,29 @@ import Release from './release.js'; import ROM from './rom.js'; import Sample from './sample.js'; +enum GameType { + AFTERMARKET = 'Aftermarket', + ALPHA = 'Alpha', + BAD = 'Bad', + BETA = 'Beta', + BIOS = 'BIOS', + DEMO = 'Demo', + DEVICE = 'Device', + FIXED = 'Fixed', + HACKED = 'Hacked', + HOMEBREW = 'Homebrew', + OVERDUMP = 'Overdump', + PENDING_DUMP = 'Pending Dump', + PIRATED = 'Pirated', + PROTOTYPE = 'Prototype', + RETAIL = 'Retail', + SAMPLE = 'Sample', + TEST = 'Test', + TRAINED = 'Trained', + TRANSLATED = 'Translated', + UNLICENSED = 'Unlicensed', +} + /** * "There are two 'semi-optional' fields that can be included for each game; * 'year' and 'manufacturer'. However, CMPro displays the manufacturer in the @@ -254,6 +277,55 @@ export default class Game implements GameProps { && !this.hasTrainer(); } + getGameType(): GameType { + // NOTE(cemmer): priority here matters! + if (this.isBios()) { + return GameType.BIOS; + } if (this.isVerified()) { + return GameType.RETAIL; + } + + if (this.isAftermarket()) { + return GameType.AFTERMARKET; + } if (this.isAlpha()) { + return GameType.ALPHA; + } if (this.isBad()) { + return GameType.BAD; + } if (this.isBeta()) { + return GameType.BETA; + } if (this.isDemo()) { + return GameType.DEMO; + } if (this.isDevice()) { + return GameType.DEVICE; + } if (this.isFixed()) { + return GameType.FIXED; + } if (this.hasHack()) { + return GameType.HACKED; + } if (this.isHomebrew()) { + return GameType.HOMEBREW; + } if (this.isOverdump()) { + return GameType.OVERDUMP; + } if (this.isPendingDump()) { + return GameType.PENDING_DUMP; + } if (this.isPirated()) { + return GameType.PIRATED; + } if (this.isPrototype()) { + return GameType.PROTOTYPE; + } if (this.isSample()) { + return GameType.SAMPLE; + } if (this.isTest()) { + return GameType.TEST; + } if (this.hasTrainer()) { + return GameType.TRAINED; + } if (this.isTranslated()) { + return GameType.TRANSLATED; + } if (this.isUnlicensed()) { + return GameType.UNLICENSED; + } + + return GameType.RETAIL; + } + isParent(): boolean { return !this.isClone(); } diff --git a/src/types/options.ts b/src/types/options.ts index b4c33a844..b5c82623f 100644 --- a/src/types/options.ts +++ b/src/types/options.ts @@ -592,6 +592,7 @@ export default class Options implements OptionsProps { output, dat, inputRomPath, + game, release, romFilenameSanitized, ); @@ -703,11 +704,13 @@ export default class Options implements OptionsProps { outputPath: string, dat: DAT, inputRomPath?: string, + game?: Game, release?: Release, outputRomFilename?: string, ): string { let result = outputPath; result = this.replaceDatTokens(result, dat); + result = this.replaceGameTokens(result, game); result = this.replaceReleaseTokens(result, release); result = this.replaceInputTokens(result, inputRomPath); result = this.replaceOutputTokens(result, outputRomFilename); @@ -725,6 +728,16 @@ export default class Options implements OptionsProps { return input.replace('{datName}', dat.getName().replace(/[\\/]/g, '_')); } + private static replaceGameTokens(input: string, game?: Game): string { + if (!game) { + return input; + } + + let output = input; + output = output.replace('{gameType}', game.getGameType()); + return output; + } + private static replaceReleaseTokens(input: string, release?: Release): string { if (!release) { return input; From 765cab6f4093bec4faae4e9d9ed006ab5430f7b0 Mon Sep 17 00:00:00 2001 From: Christian Emmer <10749361+emmercm@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:46:37 -0700 Subject: [PATCH 2/2] Tests --- test/types/options.test.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/types/options.test.ts b/test/types/options.test.ts index 9e4084953..43ea7fe09 100644 --- a/test/types/options.test.ts +++ b/test/types/options.test.ts @@ -54,6 +54,39 @@ describe('getOutputFileParsed', () => { expect(new Options({ commands: ['copy'], output }).getOutputFileParsed(dat, dummyInputRomPath, dummyGame, release, 'game.rom')).toEqual(expectedPath); }); + test.each([ + // Highest priority + ['Game [BIOS]', 'BIOS'], + ['Game [!]', 'Retail'], + // No particular priority + ['Game (Aftermarket)', 'Aftermarket'], + ['Game (Alpha)', 'Alpha'], + ['Game [b]', 'Bad'], + ['Game (Beta)', 'Beta'], + ['Game (Demo)', 'Demo'], + ['Game [f]', 'Fixed'], + ['Game (Hack)', 'Hacked'], + ['Game [h]', 'Hacked'], + ['Game (Homebrew)', 'Homebrew'], + ['Game [o]', 'Overdump'], + ['Game [!p]', 'Pending Dump'], + ['Game [p]', 'Pirated'], + ['Game (Pirate)', 'Pirated'], + ['Game (Proto)', 'Prototype'], + ['Game (Sample)', 'Sample'], + ['Game (Test)', 'Test'], + ['Game [t]', 'Trained'], + ['Game [T+Eng]', 'Translated'], + ['Game (Unl)', 'Unlicensed'], + // Default + ['Game', 'Retail'], + ])('should replace {gameType}: %s', (gameName, expectedPath) => { + const game = new Game({ name: gameName }); + const outputFileParsed = new Options({ commands: ['copy'], output: '{gameType}' }).getOutputFileParsed(dummyDat, dummyInputRomPath, game, dummyRelease, 'dummy.rom'); + const outputDirname = path.dirname(outputFileParsed); + expect(outputDirname).toEqual(expectedPath); + }); + test.each([ ['{inputDirname}', path.join('path', 'to', 'game.rom')], ])('should replace {input*}: %s', (output, expectedPath) => {