Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Output token twmenu #804

Merged
merged 18 commits into from
Nov 10, 2023
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ Advanced usage:
{batocera} The ROM's emulator-specific /roms/* directory for Batocera (e.g. "gb")
{jelos} The ROM's emulator-specific /roms/* directory for JELOS (e.g. "gb")
{funkeyos} The ROM's emulator-specific /Roms* directory for FunKey OS (e.g. "Game Boy")
{twmenu} The ROM's emulator-specific /roms/* directory for FunKey OS (e.g. "gb")
juliadin marked this conversation as resolved.
Show resolved Hide resolved


Example use cases:

Expand Down
1 change: 1 addition & 0 deletions docs/output/tokens.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ To help sort ROMs into unique file structures for popular frontends & hardware,
- `{onion}` the [OnionOS / GarlicOS](../usage/handheld/onionos.md) emulator's directory for the ROM
- `{batocera}` the [Batocera](../usage/desktop/batocera.md) emulator's directory for the ROM
- `{funkeyos}` the [FunKey OS](../usage/handheld/funkeyos.md) emulator's directory for the ROM
- `{twmenu}` the [TWiLightMenu++](../usage/desktop/twmenu.md) emulator's directory for the ROM

!!! tip

Expand Down
54 changes: 54 additions & 0 deletions docs/usage/desktop/twmenu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# TWiLightMenu++
juliadin marked this conversation as resolved.
Show resolved Hide resolved

!!! info

[TWiLightMenu++](https://github.com/DS-Homebrew/TWiLightMenu) is a Retro Emulation OS for the Nintendo 3DS and DSi handhelds. It is not very well documented which consoles are supported. While the contributors list in [README.md](https://github.com/DS-Homebrew/TWiLightMenu/blob/master/README.md) suggests that a lot more systems are supported (Atari A800 for example) than we are filtering with the `{twmenu}` tag. This list may evolve while more users use and test the tag.

[TWiLightMenu++](https://github.com/DS-Homebrew/TWiLightMenu) is a launcher replacement software for the Nintendo 3DS and DSi handhelds. It aims to make launching and opening a multitude of media content types (Roms, music, videos etc.) easier and more convenient. It comes with many emulators preinstalled (see the link above). While large rom collections are hard to browse, it provides a neat way to carry more of your ROM collection on a great handheld.

## BIOS

TWiLightMenu++ ships with most emulators not needing BIOS files. No exceptions are known to the author.

## ROMs

TWiLightMenu uses its own proprietary [ROM folder structure](https://github.com/DS-Homebrew/TWiLightMenu/tree/master/7zfile/roms) based in the root of the SD card, so `igir` has a replaceable `{twmenu}` token to sort ROMs into the right place. See the [replaceable tokens page](../../output/tokens.md) for more information.

=== ":simple-windowsxp: Windows"

Replace the `E:\` drive letter with wherever your SD card is:

```batch
igir copy extract test clean ^
--dat "No-Intro*.zip" ^
--input ROMs\ ^
--output "E:\{twmenu}" ^
--dir-letter ^
--no-bios
```

=== ":simple-apple: macOS"

Replace the `/Volumes/DSCard` drive name with whatever your SD card is named:

```shell
igir copy extract test clean \
--dat "No-Intro*.zip" \
--input ROMs/ \
--output "/Volumes/DSCard/roms/{twmenu}" \
--dir-letter \
--no-bios
```

=== ":simple-linux: Linux"

Replace the `/media/DSCard` path with wherever your SD card is mounted:

```shell
igir copy extract test clean \
--dat "No-Intro*.zip" \
--input ROMs/ \
--output "/media/DSCard/roms/{twmenu}" \
--dir-letter \
--no-bios
```
1 change: 1 addition & 0 deletions src/modules/argumentsParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,7 @@ Advanced usage:
{batocera} The ROM's emulator-specific /roms/* directory for Batocera (e.g. "gb")
{jelos} The ROM's emulator-specific /roms/* directory for JELOS (e.g. "gb")
{funkeyos} The ROM's emulator-specific /Roms* directory for FunKey OS (e.g. "Game Boy")
{twmenu} The ROM's emulator-specific /roms/* directory for FunKey OS (e.g. "gb")
juliadin marked this conversation as resolved.
Show resolved Hide resolved

Example use cases:

Expand Down
35 changes: 35 additions & 0 deletions src/types/gameConsole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ interface OutputTokens {
// FunKey S ROMs go into the subfolder of / for the console:
// @see https://github.com/FunKey-Project/FunKey-OS/tree/master/FunKey/board/funkey/rootfs-overlay/usr/games/collections
funkeyos?: string,

// TWiLightMenu++ Roms go into the /roms subfolder on the 3DS/DSi SD card
// @see https://github.com/DS-Homebrew/TWiLightMenu/tree/master/7zfile/roms
twmenu?: string,
}

/**
Expand All @@ -50,6 +54,7 @@ export default class GameConsole {
onion: 'CPC',
batocera: 'amstradcpc',
jelos: 'amstradcpc',
twmenu: 'cpc',
}),
new GameConsole(/PCW/i, [], {
mister: 'AmstradPCW',
Expand Down Expand Up @@ -82,19 +87,22 @@ export default class GameConsole {
onion: 'ATARI',
batocera: 'atari2600',
jelos: 'atari2600',
twmenu: 'a26',
}),
new GameConsole(/5200/, ['.a52'], {
mister: 'Atari5200',
onion: 'FIFTYTWOHUNDRED',
batocera: 'atari5200',
jelos: 'atari5200',
twmenu: 'a52',
}),
new GameConsole(/7800/, ['.a78'], {
pocket: '7800',
mister: 'Atari7800',
onion: 'SEVENTYEIGHTHUNDRED',
batocera: 'atari7800',
jelos: 'atari7800',
twmenu: 'a78',
}),
new GameConsole(/Jaguar/i, ['.j64'], {
onion: 'JAGUAR',
Expand Down Expand Up @@ -133,6 +141,7 @@ export default class GameConsole {
batocera: 'wswan',
jelos: 'wonderswan',
funkeyos: 'WonderSwan',
twmenu: 'ws',
}),
new GameConsole(/WonderSwan Color/i, ['.wsc'], {
pocket: 'wonderswan',
Expand All @@ -141,6 +150,7 @@ export default class GameConsole {
batocera: 'wswanc',
jelos: 'wonderswancolor',
funkeyos: 'WonderSwan',
twmenu: 'ws',
}),
// Bit Corporation
new GameConsole(/Gamate/i, [/* '.bin' */], {
Expand Down Expand Up @@ -195,6 +205,7 @@ export default class GameConsole {
onion: 'COLECO',
batocera: 'colecovision',
jelos: 'coleco',
twmenu: 'col',
}),
// Emerson
new GameConsole(/Arcadia/i, [/* '.bin' */], {
Expand Down Expand Up @@ -294,6 +305,7 @@ export default class GameConsole {
batocera: 'pcengine',
jelos: 'tg16',
funkeyos: 'PCE-TurboGrafx',
twmenu: 'tg16',
}),
new GameConsole(/(PC Engine|TurboGrafx) CD/i, [/* '.bin', '.cue' */], {
pocket: 'pcecd',
Expand Down Expand Up @@ -346,6 +358,7 @@ export default class GameConsole {
batocera: 'gb',
jelos: 'gb',
funkeyos: 'Game Boy',
twmenu: 'gb',
}), // pocket:sgb for spiritualized1997
new GameConsole(/GBA|Game ?Boy Advance/i, ['.gba', '.srl'], {
pocket: 'gba',
Expand All @@ -354,6 +367,7 @@ export default class GameConsole {
batocera: 'gba',
jelos: 'gba',
funkeyos: 'Game Boy Advance',
twmenu: 'gba',
}),
new GameConsole(/GBC|Game ?Boy Color/i, ['.gbc'], {
pocket: 'gbc',
Expand All @@ -362,6 +376,7 @@ export default class GameConsole {
batocera: 'gbc',
jelos: 'gbc',
funkeyos: 'Game Boy Color',
twmenu: 'gb',
}),
new GameConsole(/Nintendo 64|N64/i, ['.n64', '.v64', '.z64'], {
mister: 'N64',
Expand All @@ -378,14 +393,19 @@ export default class GameConsole {
new GameConsole(/(\W|^)NDS(\W|$)|Nintendo DS/i, ['.nds'], {
batocera: 'nds',
jelos: 'nds',
twmenu: 'nds',
}),
new GameConsole(/(\W|^)NDSi(\W|$)|Nintendo DSi([Ww]are)?/i, [], {
twmenu: 'dsiware',
}), // try to map DSiWare
new GameConsole(/(\W|^)NES(\W|$)|Nintendo Entertainment System/i, ['.nes', '.nez'], {
pocket: 'nes',
mister: 'NES',
onion: 'FC',
batocera: 'nes',
jelos: 'nes',
funkeyos: 'NES',
twmenu: 'nes',
}),
new GameConsole(/Pokemon Mini/i, ['.min'], {
pocket: 'poke_mini',
Expand Down Expand Up @@ -414,6 +434,7 @@ export default class GameConsole {
batocera: 'snes',
jelos: 'snes',
funkeyos: 'SNES',
twmenu: 'snes',
}),
new GameConsole(/Virtual Boy/i, ['.vb', '.vboy'], {
onion: 'VB',
Expand Down Expand Up @@ -467,6 +488,7 @@ export default class GameConsole {
batocera: 'gamegear',
jelos: 'gamegear',
funkeyos: 'Game Gear',
twmenu: 'gg',
}),
new GameConsole(/Master System/i, ['.sms'], {
pocket: 'sms',
Expand All @@ -475,6 +497,7 @@ export default class GameConsole {
batocera: 'mastersystem',
jelos: 'mastersystem',
funkeyos: 'Sega Master System',
twmenu: 'sms',
}),
new GameConsole(/(Mega|Sega) CD/i, [/* '.bin', '.cue' */], {
mister: 'MegaCD',
Expand All @@ -489,6 +512,7 @@ export default class GameConsole {
batocera: 'megadrive',
jelos: 'genesis',
funkeyos: 'Sega Genesis',
twmenu: 'gen',
}),
new GameConsole(/Saturn/i, [/* '.bin', '.cue' */], {
batocera: 'saturn',
Expand All @@ -500,6 +524,7 @@ export default class GameConsole {
onion: 'SEGASGONE',
batocera: 'sg1000',
jelos: 'sg-1000',
twmenu: 'sg',
}),
// Sharp
new GameConsole(/MZ/i, [], {
Expand Down Expand Up @@ -550,12 +575,14 @@ export default class GameConsole {
batocera: 'ngp',
jelos: 'ngp',
funkeyos: 'Neo Geo Pocket',
twmenu: 'ngp',
}),
new GameConsole(/Neo ?Geo Pocket Color/i, ['.ngc'], {
onion: 'NGP',
batocera: 'ngpc',
jelos: 'ngpc',
funkeyos: 'Neo Geo Pocket',
twmenu: 'ngp',
}),
// Sony
new GameConsole(/PlayStation|psx/i, [/* '.bin', '.cue' */], {
Expand All @@ -574,6 +601,10 @@ export default class GameConsole {
jelos: 'ps3',
}),
new GameConsole(/PlayStation [4-9]|ps[4-9]/i, [/* '.bin', '.cue' */], {}),
// Sord
new GameConsole(/Sord[ -]M(5|five)/i, [/* '.bin', '.cas' */], {
twmenu: 'm5',
}),
// Timetop
new GameConsole(/GameKing/i, [/* '.bin' */], {
pocket: 'game_king',
Expand Down Expand Up @@ -663,4 +694,8 @@ export default class GameConsole {
getFunkeyOS(): string | undefined {
return this.outputTokens.funkeyos;
}

getTWMenu(): string | undefined {
return this.outputTokens.twmenu;
}
}
5 changes: 5 additions & 0 deletions src/types/outputFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,11 @@ export default class OutputFactory {
if (funkeyos) {
output = output.replace('{funkeyos}', funkeyos);
}

const twmenu = gameConsole.getTWMenu();
if (twmenu) {
output = output.replace('{twmenu}', twmenu);
}
return output;
}

Expand Down
68 changes: 68 additions & 0 deletions test/outputFactory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,74 @@ describe('token replacement', () => {
)).rejects.toThrow(/failed to replace/);
},
);

test.each([
['game.a26', path.join('roms', 'a26', 'game.a26')],
['game.a52', path.join('roms', 'a52', 'game.a52')],
['game.a78', path.join('roms', 'a78', 'game.a78')],
['game.ws', path.join('roms', 'ws', 'game.ws')],
['game.wsc', path.join('roms', 'ws', 'game.wsc')],
['game.col', path.join('roms', 'col', 'game.col')],
['game.pce', path.join('roms', 'tg16', 'game.pce')],
['game.gb', path.join('roms', 'gb', 'game.gb')],
['game.sgb', path.join('roms', 'gb', 'game.sgb')],
['game.gbc', path.join('roms', 'gb', 'game.gbc')],
['game.gba', path.join('roms', 'gba', 'game.gba')],
['game.srl', path.join('roms', 'gba', 'game.srl')],
['game.nds', path.join('roms', 'nds', 'game.nds')],
['game.nes', path.join('roms', 'nes', 'game.nes')],
['game.sfc', path.join('roms', 'snes', 'game.sfc')],
['game.smc', path.join('roms', 'snes', 'game.smc')],
['game.gg', path.join('roms', 'gg', 'game.gg')],
['game.sms', path.join('roms', 'sms', 'game.sms')],
['game.gen', path.join('roms', 'gen', 'game.gen')],
['game.md', path.join('roms', 'gen', 'game.md')],
['game.smd', path.join('roms', 'gen', 'game.smd')],
['game.sc', path.join('roms', 'sg', 'game.sc')],
['game.sg', path.join('roms', 'sg', 'game.sg')],
['game.ngp', path.join('roms', 'ngp', 'game.ngp')],
['game.ngc', path.join('roms', 'ngp', 'game.ngc')],

])(
'should replace {twmenu} for known extension: %s',
async (outputRomFilename, expectedPath) => {
const options = new Options({ commands: ['copy'], output: 'roms/{twmenu}' });
const rom = new ROM({ name: outputRomFilename, size: 0, crc: '' });

const outputPath = OutputFactory.getPath(
options,
dummyDat,
dummyGame,
dummyRelease,
rom,
await rom.toFile(),
);
expect(outputPath.format()).toEqual(expectedPath);
},
);

test.each([
'game.bin',
'game.rom',
// satellaview is not supported by https://github.com/DS-Homebrew/TWiLightMenu/tree/master/7zfile/roms/snes
'game.bs',
])(
'should throw on {funkeyos} for unknown extension: %s',
async (outputRomFilename) => {
const options = new Options({ commands: ['copy'], output: 'roms/{twmenu}' });

const rom = new ROM({ name: outputRomFilename, size: 0, crc: '' });

await expect(async () => OutputFactory.getPath(
options,
dummyDat,
dummyGame,
dummyRelease,
rom,
await rom.toFile(),
)).rejects.toThrow(/failed to replace/);
},
);
});

describe('should respect "--dir-mirror"', () => {
Expand Down
Loading