diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e72fc180..013825f0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -140,12 +140,13 @@ jobs: env: GROUT_VERSION: ${{ needs.prepare.outputs.version }} - - name: Package Batocera AMD64 - run: task package:batocera-amd64 + - name: Package AMD64 platforms + run: task package:batocera-amd64 package:esde - - name: Create distribution + - name: Create distributions run: | - cd dist/Batocera-amd64 && zip -r ../Grout-Batocera-amd64.zip Grout.sh Grout + cd dist/Batocera-amd64 && zip -r ../Grout-Batocera-amd64.zip Grout.sh Grout && cd ../.. + cd dist/ESDE && zip -r ../Grout-ESDE.zip Grout.sh Grout && cd ../.. - name: Upload artifacts uses: actions/upload-artifact@v4 @@ -153,6 +154,7 @@ jobs: name: amd64-artifacts path: | dist/Grout-Batocera-amd64.zip + dist/Grout-ESDE.zip build-x86: needs: prepare @@ -306,6 +308,7 @@ jobs: **/Grout-Batocera-arm64.zip **/Grout-Batocera-x86.zip **/Grout-Batocera-amd64.zip + **/Grout-ESDE.zip **/grout draft: false prerelease: ${{ inputs.beta }} @@ -383,6 +386,7 @@ jobs: "Grout-Batocera-arm64.zip" "Grout-Batocera-amd64.zip" "Grout-Batocera-x86.zip" + "Grout-ESDE.zip" ) # Build the assets JSON object diff --git a/README.md b/README.md index 248c16c2..14cb2d82 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A RomM Client for your retro doo-dad. -[Allium][allium] · [Batocera][batocera] · [Knulli][knulli] · [Koriki][koriki] · [MinUI][minui] · [muOS][muos] · [NextUI][nextui] · [Onion][onion] · [ROCKNIX][rocknix] · [Spruce][spruce] · [SprigUI][sprigui] · [TwigUI][twigui] · [TrimUI][trimui] +[Allium][allium] · [Batocera][batocera] · [EmuDeck / ES-DE][emudeck] · [Knulli][knulli] · [Koriki][koriki] · [MinUI][minui] · [muOS][muos] · [NextUI][nextui] · [Onion][onion] · [ROCKNIX][rocknix] · [Spruce][spruce] · [SprigUI][sprigui] · [TwigUI][twigui] · [TrimUI][trimui]
@@ -88,6 +88,8 @@ If you enjoy using Grout and feel inclined to pay it forward, go do something ni [allium]: https://github.com/goweiwen/Allium [batocera]: https://batocera.org +[emudeck]: https://www.emudeck.com +[es-de]: https://es-de.org [knulli]: https://knulli.org [koriki]: https://github.com/Rparadise-Team/Koriki [minui]: https://github.com/shauninman/MinUI diff --git a/app/setup.go b/app/setup.go index a09d4154..18dbf891 100644 --- a/app/setup.go +++ b/app/setup.go @@ -21,12 +21,14 @@ import ( "log/slog" "os" "path/filepath" + "strings" "time" gaba "github.com/BrandonKowalski/gabagool/v2/pkg/gabagool" buttons "github.com/BrandonKowalski/gabagool/v2/pkg/gabagool/constants" "github.com/BrandonKowalski/gabagool/v2/pkg/gabagool/i18n" goi18n "github.com/nicksnyder/go-i18n/v2/i18n" + "github.com/veandco/go-sdl2/sdl" ) type SetupResult struct { @@ -58,6 +60,24 @@ func setup() SetupResult { } } +func isSteamDeckHardware() bool { + for _, path := range []string{ + "/sys/class/dmi/id/product_name", + "/sys/class/dmi/id/product_version", + "/sys/class/dmi/id/board_name", + } { + data, err := os.ReadFile(path) + if err != nil { + continue + } + value := strings.ToLower(strings.TrimSpace(string(data))) + if strings.Contains(value, "steam deck") || strings.Contains(value, "jupiter") || strings.Contains(value, "galileo") { + return true + } + } + return false +} + func setupInputMapping(currentCFW cfw.CFW) { cwd, err := os.Getwd() if err != nil { @@ -126,6 +146,14 @@ func initFramework(currentCFW cfw.CFW) { } gaba.Init(gabaOptions) + if currentCFW == cfw.ESDE || isSteamDeckHardware() { + // Steam Deck/Steam Input can emit continuous tiny analog-stick motion events, + // which flood SDL's event queue and make button input appear delayed or jittery. + // D-pad/buttons are sufficient for Grout navigation, so drop axis motion events. + sdl.EventState(sdl.CONTROLLERAXISMOTION, sdl.IGNORE) + sdl.EventState(sdl.JOYAXISMOTION, sdl.IGNORE) + } + gaba.RegisterChord("unlock-kid-mode", []buttons.VirtualButton{ buttons.VirtualButtonL1, buttons.VirtualButtonR1, diff --git a/cfw/cfw.go b/cfw/cfw.go index b45cde6e..320cc90a 100644 --- a/cfw/cfw.go +++ b/cfw/cfw.go @@ -19,6 +19,7 @@ const ( Onion CFW = "ONION" Koriki CFW = "KORIKI" Batocera CFW = "BATOCERA" + ESDE CFW = "ESDE" MinUI CFW = "MINUI" ) @@ -27,18 +28,18 @@ func GetCFW() CFW { cfw := CFW(cfwEnv) switch cfw { - case MuOS, NextUI, Knulli, Spruce, ROCKNIX, Trimui, Allium, Onion, Koriki, Batocera, MinUI: + case MuOS, NextUI, Knulli, Spruce, ROCKNIX, Trimui, Allium, Onion, Koriki, Batocera, ESDE, MinUI: return cfw default: log.SetOutput(os.Stderr) - log.Fatalf("Unsupported CFW: '%s'. Valid options: NextUI, muOS, Knulli, Spruce, ROCKNIX, Trimui, Allium, Onion, Koriki, Batocera, MinUI", cfwEnv) + log.Fatalf("Unsupported CFW: '%s'. Valid options: NextUI, muOS, Knulli, Spruce, ROCKNIX, Trimui, Allium, Onion, Koriki, Batocera, ESDE, MinUI", cfwEnv) return "" } } func (c CFW) IsBasedOnEmulationStation() bool { switch c { - case Knulli, ROCKNIX, Batocera: + case Knulli, ROCKNIX, Batocera, ESDE: return true default: return false diff --git a/cfw/directories.go b/cfw/directories.go index 5d648670..b78dad8c 100644 --- a/cfw/directories.go +++ b/cfw/directories.go @@ -3,6 +3,7 @@ package cfw import ( "grout/cfw/allium" "grout/cfw/batocera" + "grout/cfw/esde" "grout/cfw/knulli" "grout/cfw/koriki" "grout/cfw/minui" @@ -38,6 +39,8 @@ func GetRomDirectory() string { return koriki.GetRomDirectory() case Batocera: return batocera.GetRomDirectory() + case ESDE: + return esde.GetRomDirectory() case MinUI: return minui.GetRomDirectory() } @@ -79,6 +82,8 @@ func GetBIOSDirectory() string { return koriki.GetBIOSDirectory() case Batocera: return batocera.GetBIOSDirectory() + case ESDE: + return esde.GetBIOSDirectory() case MinUI: return minui.GetBIOSDirectory() } @@ -130,6 +135,8 @@ func GetArtDirectory(romDir string, platformFSSlug, platformName string) string return koriki.GetArtDirectory(romDir) case Batocera: return batocera.GetArtDirectory(romDir) + case ESDE: + return esde.GetArtDirectory(romDir) case MinUI: return minui.GetArtDirectory(romDir) default: @@ -178,6 +185,8 @@ func BaseSavePath() string { return koriki.GetBaseSavePath() case Batocera: return batocera.GetBaseSavePath() + case ESDE: + return esde.GetBaseSavePath() case MinUI: return minui.GetBaseSavePath() } @@ -192,6 +201,8 @@ func GetArtMarqueeDirectory(romDir string, platformFSSlug, platformName string) return knulli.GetArtDirectory(romDir) case Batocera: return batocera.GetArtDirectory(romDir) + case ESDE: + return esde.GetArtDirectory(romDir) default: return "" } @@ -205,6 +216,8 @@ func GetArtVideoDirectory(romDir string, platformFSSlug, platformName string) st return knulli.GetVideoDirectory(romDir) case Batocera: return batocera.GetVideoDirectory(romDir) + case ESDE: + return esde.GetVideoDirectory(romDir) default: return "" @@ -219,6 +232,8 @@ func GetArtThumbnailDirectory(romDir string, platformFSSlug, platformName string return knulli.GetArtDirectory(romDir) case Batocera: return knulli.GetArtDirectory(romDir) + case ESDE: + return esde.GetArtDirectory(romDir) default: return "" } @@ -232,6 +247,8 @@ func GetArtBezelDirectory(romDir string, platformFSSlug, platformName string) st return knulli.GetBezelDirectory(romDir) case Batocera: return batocera.GetBezelDirectory(romDir) + case ESDE: + return esde.GetBezelDirectory(romDir) default: return "" } @@ -245,6 +262,8 @@ func GetManualDirectory(romDir string, platformFSSlug, platformName string) stri return knulli.GetManualDirectory(romDir) case Batocera: return batocera.GetManualDirectory(romDir) + case ESDE: + return esde.GetManualDirectory(romDir) default: return "" } @@ -258,6 +277,8 @@ func GetBoxbackDirectory(romDir string, platformFSSlug, platformName string) str return knulli.GetArtDirectory(romDir) case Batocera: return batocera.GetArtDirectory(romDir) + case ESDE: + return esde.GetArtDirectory(romDir) default: return "" } @@ -271,6 +292,8 @@ func GetFanartDirectory(romDir string, platformFSSlug, platformName string) stri return knulli.GetArtDirectory(romDir) case Batocera: return batocera.GetArtDirectory(romDir) + case ESDE: + return esde.GetArtDirectory(romDir) default: return "" } diff --git a/cfw/esde/data/platforms.json b/cfw/esde/data/platforms.json new file mode 100644 index 00000000..b7031d62 --- /dev/null +++ b/cfw/esde/data/platforms.json @@ -0,0 +1,265 @@ +{ + "3do": [ + "3do" + ], + "3ds": [ + "n3ds", + "3ds" + ], + "acpc": [ + "amstradcpc" + ], + "amiga": [ + "amiga", + "amigacd32" + ], + "arcade": [ + "arcade", + "mame", + "fbneo", + "neogeo" + ], + "arduboy": [ + "arduboy" + ], + "atari-st": [ + "atarist" + ], + "atari2600": [ + "atari2600" + ], + "atari5200": [ + "atari5200" + ], + "atari7800": [ + "atari7800" + ], + "atari800": [ + "atari800" + ], + "c128": [ + "c128" + ], + "c16": [ + "c16" + ], + "c64": [ + "c64" + ], + "cave-story": [ + "cavestory" + ], + "colecovision": [ + "colecovision" + ], + "cpet": [ + "pet" + ], + "dc": [ + "dreamcast" + ], + "dos": [ + "dos" + ], + "fairchild-channel-f": [ + "channelf" + ], + "famicom": [ + "famicom", + "nes" + ], + "fds": [ + "fds" + ], + "g-and-w": [ + "gameandwatch" + ], + "galaksija": [], + "gamegear": [ + "gamegear" + ], + "gb": [ + "gb" + ], + "gba": [ + "gba" + ], + "gbc": [ + "gbc" + ], + "genesis": [ + "genesis", + "megadrive" + ], + "intellivision": [ + "intellivision" + ], + "j2me": [ + "j2me" + ], + "jaguar": [ + "atarijaguar", + "jaguar" + ], + "lynx": [ + "atarilynx", + "lynx" + ], + "mega-duck-slash-cougar-boy": [ + "megaduck" + ], + "msx": [ + "msx", + "msx1", + "msx2", + "msx2plus", + "msxturbor" + ], + "n64": [ + "n64" + ], + "nds": [ + "nds" + ], + "neo-geo-cd": [ + "neogeocd" + ], + "neo-geo-pocket": [ + "ngp" + ], + "neo-geo-pocket-color": [ + "ngpc" + ], + "neogeoaes": [ + "neogeo" + ], + "neogeomvs": [ + "neogeo" + ], + "nes": [ + "nes" + ], + "ngc": [ + "gc", + "gamecube" + ], + "odyssey": [ + "odyssey2", + "videopac" + ], + "openbor": [ + "openbor" + ], + "pc-8000": [ + "pc88" + ], + "pc-9800-series": [ + "pc98" + ], + "pc-fx": [ + "pcfx" + ], + "philips-cd-i": [ + "cdi" + ], + "pico": [ + "pico8" + ], + "pico-8": [ + "pico8" + ], + "pokemon-mini": [ + "pokemini" + ], + "ps2": [ + "ps2" + ], + "ps3": [ + "ps3" + ], + "psp": [ + "psp" + ], + "psx": [ + "psx", + "ps1" + ], + "saturn": [ + "saturn" + ], + "sega32": [ + "sega32x" + ], + "segacd": [ + "segacd" + ], + "sfam": [ + "snes" + ], + "sg1000": [ + "sg-1000", + "sg1000" + ], + "sharp-x68000": [ + "x68000" + ], + "sms": [ + "mastersystem" + ], + "snes": [ + "snes" + ], + "supergrafx": [ + "supergrafx" + ], + "supervision": [ + "supervision" + ], + "tg16": [ + "pcengine", + "tg16" + ], + "tic-80": [ + "tic80" + ], + "turbografx-cd": [ + "pcenginecd", + "tg-cd" + ], + "vectrex": [ + "vectrex" + ], + "vic-20": [ + "vic20", + "c20" + ], + "videopac": [ + "videopac" + ], + "virtualboy": [ + "virtualboy" + ], + "wii": [ + "wii" + ], + "wiiu": [ + "wiiu" + ], + "wonderswan": [ + "wonderswan", + "wswan" + ], + "wonderswan-color": [ + "wonderswancolor", + "wswanc" + ], + "x1": [ + "x1" + ], + "zx81": [ + "zx81" + ], + "zxs": [ + "zxspectrum" + ] +} diff --git a/cfw/esde/data/save_directories.json b/cfw/esde/data/save_directories.json new file mode 100644 index 00000000..b772cbe5 --- /dev/null +++ b/cfw/esde/data/save_directories.json @@ -0,0 +1,200 @@ +{ + "3do": [ + "retroarch/saves" + ], + "acpc": [ + "retroarch/saves" + ], + "arcade": [ + "retroarch/saves" + ], + "atari-st": [ + "retroarch/saves" + ], + "atari2600": [ + "retroarch/saves" + ], + "atari5200": [ + "retroarch/saves" + ], + "atari7800": [ + "retroarch/saves" + ], + "atari800": [ + "retroarch/saves" + ], + "c128": [ + "retroarch/saves" + ], + "c16": [ + "retroarch/saves" + ], + "c64": [ + "retroarch/saves" + ], + "colecovision": [ + "retroarch/saves" + ], + "cpet": [ + "retroarch/saves" + ], + "fairchild-channel-f": [ + "retroarch/saves" + ], + "famicom": [ + "retroarch/saves" + ], + "fds": [ + "retroarch/saves" + ], + "gamegear": [ + "retroarch/saves" + ], + "gb": [ + "retroarch/saves" + ], + "gba": [ + "retroarch/saves" + ], + "gbc": [ + "retroarch/saves" + ], + "genesis": [ + "retroarch/saves" + ], + "intellivision": [ + "retroarch/saves" + ], + "jaguar": [ + "retroarch/saves" + ], + "lynx": [ + "retroarch/saves" + ], + "msx": [ + "retroarch/saves" + ], + "n64": [ + "retroarch/saves" + ], + "nds": [ + "retroarch/saves" + ], + "neo-geo-cd": [ + "retroarch/saves" + ], + "neo-geo-pocket": [ + "retroarch/saves" + ], + "neo-geo-pocket-color": [ + "retroarch/saves" + ], + "neogeoaes": [ + "retroarch/saves" + ], + "neogeomvs": [ + "retroarch/saves" + ], + "nes": [ + "retroarch/saves" + ], + "odyssey": [ + "retroarch/saves" + ], + "pc-8000": [ + "retroarch/saves" + ], + "pc-9800-series": [ + "retroarch/saves" + ], + "pc-fx": [ + "retroarch/saves" + ], + "pico": [ + "retroarch/saves" + ], + "pico-8": [ + "retroarch/saves" + ], + "pokemon-mini": [ + "retroarch/saves" + ], + "psx": [ + "retroarch/saves" + ], + "saturn": [ + "retroarch/saves" + ], + "sega32": [ + "retroarch/saves" + ], + "segacd": [ + "retroarch/saves" + ], + "sfam": [ + "retroarch/saves" + ], + "sg1000": [ + "retroarch/saves" + ], + "sharp-x68000": [ + "retroarch/saves" + ], + "sms": [ + "retroarch/saves" + ], + "snes": [ + "retroarch/saves" + ], + "supergrafx": [ + "retroarch/saves" + ], + "supervision": [ + "retroarch/saves" + ], + "tg16": [ + "retroarch/saves" + ], + "tic-80": [ + "retroarch/saves" + ], + "turbografx-cd": [ + "retroarch/saves" + ], + "vectrex": [ + "retroarch/saves" + ], + "vic-20": [ + "retroarch/saves" + ], + "videopac": [ + "retroarch/saves" + ], + "virtualboy": [ + "retroarch/saves" + ], + "wonderswan": [ + "retroarch/saves" + ], + "wonderswan-color": [ + "retroarch/saves" + ], + "x1": [ + "retroarch/saves" + ], + "zx81": [ + "retroarch/saves" + ], + "zxs": [ + "retroarch/saves" + ], + "dc": [ + "flycast" + ], + "psp": [ + "ppsspp/PSP/SAVEDATA" + ], + "3ds": [ + "citra/sdmc" + ] +} diff --git a/cfw/esde/esde.go b/cfw/esde/esde.go new file mode 100644 index 00000000..93804f08 --- /dev/null +++ b/cfw/esde/esde.go @@ -0,0 +1,55 @@ +package esde + +import ( + "embed" + "grout/internal/jsonutil" + "os" + "path/filepath" +) + +//go:embed data/*.json +var embeddedFiles embed.FS + +var ( + Platforms = jsonutil.MustLoadJSONMap[string, []string](embeddedFiles, "data/platforms.json") + SaveDirectories = jsonutil.MustLoadJSONMap[string, []string](embeddedFiles, "data/save_directories.json") +) + +func GetBasePath() string { + if basePath := os.Getenv("BASE_PATH"); basePath != "" { + return basePath + } + return filepath.Join(os.Getenv("HOME"), "Emulation") +} + +func GetRomDirectory() string { + return filepath.Join(GetBasePath(), "roms") +} + +func GetBIOSDirectory() string { + return filepath.Join(GetBasePath(), "bios") +} + +func GetBaseSavePath() string { + return filepath.Join(GetBasePath(), "saves") +} + +func GetArtDirectory(romDir string) string { + return filepath.Join(romDir, "images") +} + +func GetGroutGamelist() string { + return filepath.Join(GetRomDirectory(), "ports", "gamelist.xml") +} + +func GetVideoDirectory(romDir string) string { + return filepath.Join(romDir, "videos") +} + +func GetBezelDirectory(romDir string) string { + return filepath.Join(romDir, "bezels") +} + +func GetManualDirectory(romDir string) string { + return filepath.Join(romDir, "manuals") +} diff --git a/cfw/metadata.go b/cfw/metadata.go index de344ca1..bfd4d1ee 100644 --- a/cfw/metadata.go +++ b/cfw/metadata.go @@ -2,6 +2,7 @@ package cfw import ( "grout/cfw/batocera" + "grout/cfw/esde" "grout/cfw/knulli" "grout/cfw/muos" "grout/cfw/rocknix" @@ -26,6 +27,9 @@ func AddGroutToGamelist(c CFW) { gamelist.AddGroutEntry(rocknix.GetGroutGamelist(), "./Grout.sh") case Batocera: gamelist.AddGroutEntry(batocera.GetGroutGamelist(), "./Grout/Grout.sh") + case ESDE: + gamelist.AddGroutEntry(esde.GetGroutGamelist(), "./Grout.sh") + return default: return } @@ -40,6 +44,10 @@ func FillGamesMetadata(entries []gamelist.RomGameEntry) { logger.Warn("Failed to add games to ES gamelist.xml", "error", err) } scheduleESRestart() + case ESDE: + if err := gamelist.AddRomGamesToGamelist(entries, gamelist.GameListFileName); err != nil { + logger.Warn("Failed to add games to ES-DE gamelist.xml", "error", err) + } case Spruce, Allium, Onion, Koriki: if err := gamelist.AddRomGamesToGamelist(entries, gamelist.MiyooGameListFileName); err != nil { logger.Warn("Failed to add games to miyoogamelist.xml", "error", err) diff --git a/cfw/platforms.go b/cfw/platforms.go index 82a5b41c..4d9476c2 100644 --- a/cfw/platforms.go +++ b/cfw/platforms.go @@ -3,6 +3,7 @@ package cfw import ( "grout/cfw/allium" "grout/cfw/batocera" + "grout/cfw/esde" "grout/cfw/knulli" "grout/cfw/koriki" "grout/cfw/minui" @@ -32,6 +33,7 @@ func buildPlatformAliasMap() map[string][]string { onion.Platforms, koriki.Platforms, batocera.Platforms, + esde.Platforms, minui.Platforms, } @@ -136,6 +138,8 @@ func GetPlatformMap(c CFW) map[string][]string { return koriki.Platforms case Batocera: return batocera.Platforms + case ESDE: + return esde.Platforms case MinUI: return minui.Platforms default: diff --git a/cfw/saves.go b/cfw/saves.go index 71ecce50..31bc0634 100644 --- a/cfw/saves.go +++ b/cfw/saves.go @@ -3,6 +3,7 @@ package cfw import ( "grout/cfw/allium" "grout/cfw/batocera" + "grout/cfw/esde" "grout/cfw/knulli" "grout/cfw/koriki" "grout/cfw/minui" @@ -38,6 +39,8 @@ func EmulatorFolderMap(c CFW) map[string][]string { return trimui.SaveDirectories case Batocera: return batocera.Platforms + case ESDE: + return esde.SaveDirectories case MinUI: return minui.SaveDirectories default: diff --git a/docs/_includes/cfw-links.md b/docs/_includes/cfw-links.md index 1fbdf27a..213dc74f 100644 --- a/docs/_includes/cfw-links.md +++ b/docs/_includes/cfw-links.md @@ -1,5 +1,7 @@ [allium]: https://github.com/goweiwen/Allium [batocera]: https://batocera.org +[emudeck]: https://www.emudeck.com +[es-de]: https://es-de.org [knulli]: https://knulli.org [koriki]: https://github.com/Rparadise-Team/Koriki [minui]: https://github.com/shauninman/MinUI diff --git a/docs/contributing/development.md b/docs/contributing/development.md index 91919ee2..b20ef3b4 100644 --- a/docs/contributing/development.md +++ b/docs/contributing/development.md @@ -33,7 +33,7 @@ brew install sdl2 sdl2_image sdl2_ttf sdl2_gfx - `WINDOW_WIDTH` (optional) - `WINDOW_HEIGHT` (optional) - `NITRATES` [true | false] (optional) This is used for Gabagool development debugging - - `CFW` [MUOS | KNULLI | SPRUCE | NEXTUI] (mandatory), this controls how Grout interacts with and places files + - `CFW` [MUOS | KNULLI | SPRUCE | NEXTUI | ESDE] (mandatory), this controls how Grout interacts with and places files - `BASE_PATH` (mandatory), this acts as the root path like you would have on a handheld (e.g. `/mmc/sdcard` on muOS). Have the subdirectory structure of this path match the CFW you are working on. 5. Run / Debug `app/grout.go`, making sure to reference the `.env` file in your run configuration. @@ -93,6 +93,7 @@ After building, you can package for individual platforms: | `task package-next` | NextUI (TrimUI) | `build/Grout.pak/` | | `task package-muos` | muOS | `build/muOS/Grout/`, `build/Grout.muxapp` | | `task package-knulli` | Knulli | `build/Knulli/Grout/` | +| `task package-esde` | EmuDeck / ES-DE | `dist/ESDE/` | | `task package-spruce` | Spruce | `build/Spruce/Grout/` | Each packaging task copies the binary, launch scripts from `scripts//`, shared libraries, and documentation diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md index 3751d966..02ba82d4 100644 --- a/docs/getting-started/index.md +++ b/docs/getting-started/index.md @@ -7,7 +7,7 @@ Get up and running with Grout in five steps. Make sure you have: - A RomM server running and accessible -- A compatible device running [Allium][allium], [Batocera][batocera], [Knulli][knulli], [muOS][muos], [NextUI][nextui], [Onion][onion], [ROCKNIX][rocknix], [Spruce v4][spruce], or [TrimUI][trimui] +- A compatible device running [Allium][allium], [Batocera][batocera], [EmuDeck / ES-DE][emudeck], [Knulli][knulli], [muOS][muos], [NextUI][nextui], [Onion][onion], [ROCKNIX][rocknix], [Spruce v4][spruce], or [TrimUI][trimui] - Your device connected to Wi-Fi --- @@ -20,6 +20,7 @@ Choose your platform: - [Allium Installation](install-allium.md) - [Batocera Installation](install-batocera.md) +- [EmuDeck / ES-DE Installation](install-emudeck.md) - [Knulli Installation](install-knulli.md) - [muOS Installation](install-muos.md) - [NextUI Installation](install-nextui.md) diff --git a/docs/getting-started/install-emudeck.md b/docs/getting-started/install-emudeck.md new file mode 100644 index 00000000..27b25df6 --- /dev/null +++ b/docs/getting-started/install-emudeck.md @@ -0,0 +1,54 @@ +# Installation Guide for Steam Deck / EmuDeck ES-DE + +This guide will help you install Grout as an ES-DE port on a Steam Deck using [EmuDeck][emudeck]. + +## Tested Devices + +Grout has been tested on the following devices running EmuDeck with ES-DE: + +| Manufacturer | Device | +|--------------|------------| +| _None yet_ | _Please report your results!_ | + +_Please help verify compatibility by reporting your results!_ + +## Installation Steps + +1. Ensure EmuDeck and ES-DE are installed on your Steam Deck. +2. Download the [latest Grout release](https://github.com/rommapp/grout/releases/latest/download/Grout-ESDE.zip) for + EmuDeck / ES-DE. +3. Unzip the downloaded archive. +4. Copy the `Grout` folder and `Grout.sh` file to your EmuDeck Ports directory: + - Internal storage: `~/Emulation/roms/ports/` + - SD card: `/run/media//Emulation/roms/ports/` +5. Restart ES-DE or refresh your game list. +6. Launch Grout from the `Ports` system and enjoy! + +## Important Configuration + +!!! important + Grout expects the standard EmuDeck folder layout where `roms`, `bios`, and `saves` are inside the same + `Emulation` directory. The launcher derives this path from the `roms/ports` folder that contains `Grout.sh`. + +!!! note + Save sync support on EmuDeck is best-effort because save locations vary by emulator. ROM downloads, BIOS downloads, + artwork downloads, and ES-DE `gamelist.xml` updates use the standard EmuDeck directories. + +## Update + +### In-App update (Recommended) + +Grout has a built-in update mechanism. To update Grout, launch the application and navigate to the `Settings` menu. From there, +select `Check for Updates`. If a new version is available, follow the on-screen prompts to download and install the update. + +### Manual update + +To update Grout manually, download the latest release and replace the existing `Grout` folder and `Grout.sh` file in your +EmuDeck Ports directory. If you have made any custom configurations, ensure to back them up before replacing the folder. +Be sure to keep the `config.json` file if you do not want to authenticate again, and configure platform folder mappings again. + +## Next Steps + +After installation is complete, check out the [User Guide](../usage/guide.md) to learn how to use Grout. + +--8<-- "docs/_includes/cfw-links.md" diff --git a/docs/index.md b/docs/index.md index 4be37f8f..a5529c67 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,7 +13,7 @@ hide: Download and manage games from your [RomM](https://romm.app) instance directly on your Linux based retro handheld. -[Allium](getting-started/install-allium.md) · [Batocera](getting-started/install-batocera.md) · [Knulli](getting-started/install-knulli.md) · [MinUI](getting-started/install-minui.md) · [muOS](getting-started/install-muos.md) · [NextUI](getting-started/install-nextui.md) · [Onion](getting-started/install-onion.md) · [ROCKNIX](getting-started/install-rocknix.md) · [Spruce](getting-started/install-spruce.md) · [TrimUI](getting-started/install-trimui.md) +[Allium](getting-started/install-allium.md) · [Batocera](getting-started/install-batocera.md) · [EmuDeck / ES-DE](getting-started/install-emudeck.md) · [Knulli](getting-started/install-knulli.md) · [MinUI](getting-started/install-minui.md) · [muOS](getting-started/install-muos.md) · [NextUI](getting-started/install-nextui.md) · [Onion](getting-started/install-onion.md) · [ROCKNIX](getting-started/install-rocknix.md) · [Spruce](getting-started/install-spruce.md) · [TrimUI](getting-started/install-trimui.md) [:fontawesome-solid-gamepad: Get Started](getting-started/index.md){ .md-button .md-button--primary }    diff --git a/docs/platforms/esde.md b/docs/platforms/esde.md new file mode 100644 index 00000000..0b205e71 --- /dev/null +++ b/docs/platforms/esde.md @@ -0,0 +1,87 @@ +# EmuDeck / ES-DE Platform Mappings + +This table shows the mappings of RomM Fs Slug to EmuDeck's ES-DE platform folders. + +| Platform Name | RomM Fs Slug | Folder(s) | +|---|---|---| +| 3DO Interactive Multiplayer | 3do | 3do | +| Amiga | amiga | amiga, amigacd32 | +| Amstrad CPC | acpc | amstradcpc | +| Arcade | arcade | arcade, mame, fbneo, neogeo | +| Arduboy | arduboy | arduboy | +| Atari 2600 | atari2600 | atari2600 | +| Atari 5200 | atari5200 | atari5200 | +| Atari 7800 | atari7800 | atari7800 | +| Atari 800 | atari800 | atari800 | +| Atari Jaguar | jaguar | atarijaguar, jaguar | +| Atari Lynx | lynx | atarilynx, lynx | +| Atari ST/STE | atari-st | atarist | +| Cave Story | cave-story | cavestory | +| ColecoVision | colecovision | colecovision | +| Commodore 128 | c128 | c128 | +| Commodore 16 | c16 | c16 | +| Commodore C64/128/MAX | c64 | c64 | +| Commodore PET | cpet | pet | +| Commodore VIC-20 | vic-20 | vic20, c20 | +| DOS | dos | dos | +| Dreamcast | dc | dreamcast | +| Fairchild Channel F | fairchild-channel-f | channelf | +| Family Computer | famicom | famicom, nes | +| Family Computer Disk System | fds | fds | +| Galaksija | galaksija | *(none)* | +| Game & Watch | g-and-w | gameandwatch | +| Game Boy | gb | gb | +| Game Boy Advance | gba | gba | +| Game Boy Color | gbc | gbc | +| Intellivision | intellivision | intellivision | +| J2ME | j2me | j2me | +| MSX | msx | msx, msx1, msx2, msx2plus, msxturbor | +| Mega Duck/Cougar Boy | mega-duck-slash-cougar-boy | megaduck | +| Neo Geo AES | neogeoaes | neogeo | +| Neo Geo CD | neo-geo-cd | neogeocd | +| Neo Geo MVS | neogeomvs | neogeo | +| Neo Geo Pocket | neo-geo-pocket | ngp | +| Neo Geo Pocket Color | neo-geo-pocket-color | ngpc | +| Nintendo 3DS | 3ds | n3ds, 3ds | +| Nintendo 64 | n64 | n64 | +| Nintendo DS | nds | nds | +| Nintendo Entertainment System | nes | nes | +| Nintendo GameCube | ngc | gc, gamecube | +| Nintendo Wii | wii | wii | +| Nintendo Wii U | wiiu | wiiu | +| Odyssey | odyssey | odyssey2, videopac | +| OpenBOR | openbor | openbor | +| PC Engine SuperGrafx | supergrafx | supergrafx | +| PC-8000 | pc-8000 | pc88 | +| PC-9800 Series | pc-9800-series | pc98 | +| PC-FX | pc-fx | pcfx | +| PICO-8 | pico | pico8 | +| PICO-8 | pico-8 | pico8 | +| Philips CD-i | philips-cd-i | cdi | +| PlayStation | psx | psx, ps1 | +| PlayStation 2 | ps2 | ps2 | +| PlayStation 3 | ps3 | ps3 | +| PlayStation Portable | psp | psp | +| Pokemon Mini | pokemon-mini | pokemini | +| SG-1000 | sg1000 | sg-1000, sg1000 | +| Sega 32X | sega32 | sega32x | +| Sega CD | segacd | segacd | +| Sega Game Gear | gamegear | gamegear | +| Sega Genesis | genesis | genesis, megadrive | +| Sega Master System | sms | mastersystem | +| Sega Saturn | saturn | saturn | +| Sharp X1 | x1 | x1 | +| Sharp X68000 | sharp-x68000 | x68000 | +| Super Famicom | sfam | snes | +| Super Nintendo | snes | snes | +| Supervision | supervision | supervision | +| TIC-80 | tic-80 | tic80 | +| TurboGrafx-16 | tg16 | pcengine, tg16 | +| TurboGrafx-CD | turbografx-cd | pcenginecd, tg-cd | +| Vectrex | vectrex | vectrex | +| Videopac | videopac | videopac | +| Virtual Boy | virtualboy | virtualboy | +| WonderSwan | wonderswan | wonderswan, wswan | +| WonderSwan Color | wonderswan-color | wonderswancolor, wswanc | +| ZX Spectrum | zxs | zxspectrum | +| ZX81 | zx81 | zx81 | diff --git a/docs/usage/guide.md b/docs/usage/guide.md index e099d3d2..c18354f8 100644 --- a/docs/usage/guide.md +++ b/docs/usage/guide.md @@ -177,6 +177,7 @@ Grout uses platform mappings to determine where to save downloaded games on your different folder naming conventions. Use these references to see the exact folder names used by your CFW: - [KNULLI](../platforms/knulli.md) - ES-DE style folder names (e.g., `gb`, `snes`, `psx`) +- [EmuDeck / ES-DE](../platforms/esde.md) - ES-DE style folder names (e.g., `gb`, `snes`, `psx`) - [muOS](../platforms/muos.md) - Mixed short codes and descriptive names (e.g., `gb`, `Nintendo Game Boy`) - [NextUI](../platforms/nextui.md) - Descriptive names with tags (e.g., `Game Boy (GB)`) - [ROCKNIX](../platforms/rocknix.md) - ES-DE style folder names (e.g., `gb`, `snes`, `psx`) diff --git a/docs/usage/settings.md b/docs/usage/settings.md index a3d2d188..f2959224 100644 --- a/docs/usage/settings.md +++ b/docs/usage/settings.md @@ -127,6 +127,7 @@ For detailed documentation on platform mapping, see the [User Guide](guide.md#pl Each CFW uses different folder naming conventions: - [KNULLI](../platforms/knulli.md) - ES-DE style folder names (e.g., `gb`, `snes`, `psx`) +- [EmuDeck / ES-DE](../platforms/esde.md) - ES-DE style folder names (e.g., `gb`, `snes`, `psx`) - [muOS](../platforms/muos.md) - Mixed short codes and descriptive names (e.g., `gb`, `Nintendo Game Boy`) - [NextUI](../platforms/nextui.md) - Descriptive names with tags (e.g., `Game Boy (GB)`) - [ROCKNIX](../platforms/rocknix.md) - ES-DE style folder names (e.g., `gb`, `snes`, `psx`) diff --git a/mkdocs.yml b/mkdocs.yml index 8377774f..e963c067 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -68,6 +68,7 @@ nav: - getting-started/index.md - Allium: getting-started/install-allium.md - Batocera: getting-started/install-batocera.md + - EmuDeck / ES-DE: getting-started/install-emudeck.md - Knulli: getting-started/install-knulli.md - MinUI: getting-started/install-minui.md - muOS: getting-started/install-muos.md @@ -84,6 +85,7 @@ nav: - CFW Specific Info: - Allium: platforms/allium.md - Batocera: platforms/BATOCERA.md + - EmuDeck / ES-DE: platforms/esde.md - Knulli: platforms/knulli.md - Koriki: platforms/koriki.md - MinUI: platforms/minui.md diff --git a/scripts/ESDE/Grout.sh b/scripts/ESDE/Grout.sh new file mode 100644 index 00000000..ba340e6d --- /dev/null +++ b/scripts/ESDE/Grout.sh @@ -0,0 +1,43 @@ +#!/bin/bash +CUR_DIR="$(cd "$(dirname "$0")" && pwd)" +APP_DIR="$CUR_DIR/Grout" +LOCK_DIR="${XDG_RUNTIME_DIR:-/tmp}/grout-esde.lock" + +if ! mkdir "$LOCK_DIR" 2>/dev/null; then + if [ -f "$LOCK_DIR/pid" ] && kill -0 "$(cat "$LOCK_DIR/pid")" 2>/dev/null; then + exit 0 + fi + rm -rf "$LOCK_DIR" + mkdir "$LOCK_DIR" 2>/dev/null || exit 0 +fi +printf '%s\n' "$$" > "$LOCK_DIR/pid" +trap 'rm -rf "$LOCK_DIR"' EXIT + +# Apply pending update +if [ -d "$CUR_DIR/.update" ]; then + cp -rf "$CUR_DIR/.update/"* "$CUR_DIR/" + rm -rf "$CUR_DIR/.update" +fi + +cd "$APP_DIR" || exit 1 + +# EmuDeck's ES-DE ports folder is normally /roms/ports. +export CFW=ESDE +export BASE_PATH="$(cd "$CUR_DIR/../.." && pwd)" +export LD_LIBRARY_PATH="$APP_DIR/lib:$LD_LIBRARY_PATH" + +# Steam Deck uses Xbox-style face buttons, so use direct A=A/B=B mappings. +export FLIP_FACE_BUTTONS=1 + +# Prefer SDL's game controller API and ignore duplicate keyboard/raw joystick events. +export DISABLE_KEYBOARD_INPUT=1 +export DISABLE_JOYSTICK_INPUT=1 + +# Grout uses its own gamepad-driven keyboard. On Steam Deck, SDL/Steam can +# also surface the Steam keyboard, which can interfere with Grout's keyboard. +export SDL_ENABLE_SCREEN_KEYBOARD=0 +export SDL_IME_SHOW_UI=0 + +./grout + +exit 0 diff --git a/scripts/ESDE/logo.png b/scripts/ESDE/logo.png new file mode 100644 index 00000000..bb49d84b Binary files /dev/null and b/scripts/ESDE/logo.png differ diff --git a/taskfiles/build.yml b/taskfiles/build.yml index e875d1f2..0e14ff69 100644 --- a/taskfiles/build.yml +++ b/taskfiles/build.yml @@ -23,6 +23,7 @@ tasks: - echo "Extracting {{.BUILD_DIR}}..." - docker cp {{.CONTAINER_NAME}}-{{.BUILD_DIR}}:/build/grout {{.BUILD_DIR}}/grout - 'if [ -n "{{.LIB_PATH}}" ]; then docker cp {{.CONTAINER_NAME}}-{{.BUILD_DIR}}:{{.LIB_PATH}} {{.BUILD_DIR}}/lib/libSDL2_gfx-1.0.so.0; fi' + - 'EXTRA_LIBS="{{.EXTRA_LIBS}}"; if [ -n "$EXTRA_LIBS" ]; then for spec in $EXTRA_LIBS; do src="${spec%%:*}"; dest="${spec##*:}"; docker cp {{.CONTAINER_NAME}}-{{.BUILD_DIR}}:$src {{.BUILD_DIR}}/lib/$dest; done; fi' - docker rm {{.CONTAINER_NAME}}-{{.BUILD_DIR}} >/dev/null 2>&1 - docker image prune --filter "label={{.LABEL}}" -f >/dev/null 2>&1 || true - echo "Extract {{.BUILD_DIR}} complete" @@ -35,7 +36,7 @@ tasks: - task: _build vars: { PLATFORM: linux/arm64, BUILD_DIR: build64, IMAGE_TAG: "{{.IMAGE_NAME}}-arm64", DOCKERFILE: docker/Dockerfile, LOCAL: "{{.LOCAL}}" } - task: _extract - vars: { BUILD_DIR: build64, IMAGE_TAG: "{{.IMAGE_NAME}}-arm64", LIB_PATH: /usr/lib/aarch64-linux-gnu/libSDL2_gfx-1.0.so.0.0.2 } + vars: { BUILD_DIR: build64, IMAGE_TAG: "{{.IMAGE_NAME}}-arm64", LIB_PATH: /usr/lib/aarch64-linux-gnu/libSDL2_gfx-1.0.so.0.0.2, EXTRA_LIBS: "" } amd64: desc: Build and extract for AMD64 (x86_64) @@ -43,7 +44,11 @@ tasks: - task: _build vars: { PLATFORM: linux/amd64, BUILD_DIR: build, IMAGE_TAG: "{{.IMAGE_NAME}}-amd64", DOCKERFILE: docker/Dockerfile } - task: _extract - vars: { BUILD_DIR: build, IMAGE_TAG: "{{.IMAGE_NAME}}-amd64", LIB_PATH: /usr/lib/x86_64-linux-gnu/libSDL2_gfx-1.0.so.0.0.2 } + vars: + BUILD_DIR: build + IMAGE_TAG: "{{.IMAGE_NAME}}-amd64" + LIB_PATH: /usr/lib/x86_64-linux-gnu/libSDL2_gfx-1.0.so.0.0.2 + EXTRA_LIBS: "/usr/lib/x86_64-linux-gnu/libSDL2-2.0.so.0.14.0:libSDL2-2.0.so.0 /usr/lib/x86_64-linux-gnu/libSDL2_image-2.0.so.0.2.3:libSDL2_image-2.0.so.0 /usr/lib/x86_64-linux-gnu/libSDL2_ttf-2.0.so.0.14.1:libSDL2_ttf-2.0.so.0 /usr/lib/x86_64-linux-gnu/libjpeg.so.62.3.0:libjpeg.so.62 /usr/lib/x86_64-linux-gnu/libpng16.so.16.37.0:libpng16.so.16 /usr/lib/x86_64-linux-gnu/libtiff.so.5.6.0:libtiff.so.5 /usr/lib/x86_64-linux-gnu/libwebp.so.6.0.2:libwebp.so.6 /usr/lib/x86_64-linux-gnu/libfreetype.so.6.17.4:libfreetype.so.6 /usr/lib/x86_64-linux-gnu/libbrotlidec.so.1.0.9:libbrotlidec.so.1 /usr/lib/x86_64-linux-gnu/libbrotlicommon.so.1.0.9:libbrotlicommon.so.1 /usr/lib/x86_64-linux-gnu/libzstd.so.1.4.8:libzstd.so.1 /lib/x86_64-linux-gnu/liblzma.so.5.2.5:liblzma.so.5 /usr/lib/x86_64-linux-gnu/libjbig.so.0:libjbig.so.0 /usr/lib/x86_64-linux-gnu/libdeflate.so.0:libdeflate.so.0 /lib/x86_64-linux-gnu/libz.so.1.2.11:libz.so.1" x86: desc: Build and extract for x86 (32-bit) @@ -51,7 +56,7 @@ tasks: - task: _build vars: { PLATFORM: linux/386, BUILD_DIR: buildx86, IMAGE_TAG: "{{.IMAGE_NAME}}-x86", DOCKERFILE: docker/Dockerfile } - task: _extract - vars: { BUILD_DIR: buildx86, IMAGE_TAG: "{{.IMAGE_NAME}}-x86", LIB_PATH: /usr/lib/i386-linux-gnu/libSDL2_gfx-1.0.so.0.0.2 } + vars: { BUILD_DIR: buildx86, IMAGE_TAG: "{{.IMAGE_NAME}}-x86", LIB_PATH: /usr/lib/i386-linux-gnu/libSDL2_gfx-1.0.so.0.0.2, EXTRA_LIBS: "" } arm32: desc: Build and extract for ARM32 @@ -62,4 +67,4 @@ tasks: vars: { PLATFORM: linux/arm/v7, BUILD_DIR: build32, IMAGE_TAG: "{{.IMAGE_NAME}}-arm32", DOCKERFILE: docker/32.Dockerfile, LOCAL: "{{.LOCAL}}" } # TODO: add LIB_PATH once custom SDL build for Miyoo is ready - task: _extract - vars: { BUILD_DIR: build32, IMAGE_TAG: "{{.IMAGE_NAME}}-arm32", LIB_PATH: "" } + vars: { BUILD_DIR: build32, IMAGE_TAG: "{{.IMAGE_NAME}}-arm32", LIB_PATH: "", EXTRA_LIBS: "" } diff --git a/taskfiles/package.yml b/taskfiles/package.yml index 9007c443..e5cba408 100644 --- a/taskfiles/package.yml +++ b/taskfiles/package.yml @@ -4,9 +4,9 @@ tasks: all: desc: Package for all platforms - deps: [ next, muos, knulli, spruce, rocknix, trimui, allium, onion, koriki, minui, batocera, batocera-x86, batocera-amd64 ] + deps: [ next, muos, knulli, spruce, rocknix, trimui, allium, onion, koriki, minui, batocera, batocera-x86, batocera-amd64, esde ] cmds: - - echo "Packaging complete (14 platforms)" + - echo "Packaging complete (15 platforms)" silent: true next: @@ -147,3 +147,13 @@ tasks: - cp -R build/lib/* dist/Batocera-amd64/Grout/lib/ - chmod a+x dist/Batocera-amd64/Grout/grout dist/Batocera-amd64/Grout.sh silent: true + + esde: + cmds: + - rm -rf dist/ESDE + - mkdir -p dist/ESDE/Grout/lib + - cp scripts/ESDE/Grout.sh dist/ESDE/ + - cp build/grout scripts/ESDE/logo.png README.md LICENSE dist/ESDE/Grout/ + - cp -R build/lib/* dist/ESDE/Grout/lib/ + - chmod a+x dist/ESDE/Grout/grout dist/ESDE/Grout.sh + silent: true diff --git a/update/updater.go b/update/updater.go index dd9b0f1a..f62755d5 100644 --- a/update/updater.go +++ b/update/updater.go @@ -64,6 +64,11 @@ func GetDistributionAssetName(c cfw.CFW) string { default: return "" } + case cfw.ESDE: + if runtime.GOARCH == "amd64" { + return "Grout-ESDE.zip" + } + return "" default: return "" } @@ -257,6 +262,8 @@ func getLaunchScriptPath(c cfw.CFW) string { return "Grout.pak/launch.sh" case cfw.Batocera: return "Grout.sh" + case cfw.ESDE: + return "Grout.sh" default: return "" }