From 47a9d70b743b17618b31261a0a5cd323da294d2d Mon Sep 17 00:00:00 2001 From: sergystepanov Date: Mon, 30 Aug 2021 19:50:35 +0300 Subject: [PATCH] Add the ability to set emulator ROM folder (#348) Emulators now can load games with the same file extensions if you either place ROMs into the folder named as the emulator in the config file (libretro.core.list value) or any folder as you specify in the `libretro.core.list.{emulator}.folder` config param. --- configs/config.yaml | 14 ++++++++ pkg/config/emulator/config.go | 19 +++++++--- pkg/config/emulator/config_test.go | 52 ++++++++++++++++++++++++++++ pkg/coordinator/useragenthandlers.go | 1 + pkg/cws/api/coordinator.go | 1 + pkg/games/game_library.go | 5 +-- pkg/games/game_library_test.go | 15 ++++++-- pkg/worker/internalhandlers.go | 21 ++++------- pkg/worker/room/room.go | 7 ++-- 9 files changed, 109 insertions(+), 26 deletions(-) create mode 100644 pkg/config/emulator/config_test.go diff --git a/configs/config.yaml b/configs/config.yaml index 1fe38937..52a5475c 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -125,10 +125,19 @@ emulator: compression: zip # Libretro core configuration # + # The emulator selection will happen in this order: + # - based on the folder name in the folder param + # - based on the folder name (core name) in the list (i.e. nes, snes) + # - based on the rom names in the roms param + # # Available config params: # - lib (string) # - config (string) # - roms ([]string) + # - folder (string) + # By default emulator selection is based on the folder named as cores + # in the list (i.e. nes, snes) but if you specify folder param, + # then it will try to load the ROM file from that folder first. # - width (int) -- broken # - height (int) -- broken # - ratio (float) @@ -142,11 +151,16 @@ emulator: pcsx: lib: pcsx_rearmed_libretro roms: [ "cue" ] + # example of folder override + folder: psx # MAME core requires additional manual setup, please read: # https://docs.libretro.com/library/fbneo/ mame: lib: fbneo_libretro roms: [ "zip" ] + mame2003: + lib: mame2003_libretro + roms: [ "zip" ] nes: lib: nestopia_libretro roms: [ "nes" ] diff --git a/pkg/config/emulator/config.go b/pkg/config/emulator/config.go index 0b2af352..35cf870e 100644 --- a/pkg/config/emulator/config.go +++ b/pkg/config/emulator/config.go @@ -3,6 +3,7 @@ package emulator import ( "path" "path/filepath" + "strings" ) type Emulator struct { @@ -42,6 +43,7 @@ type LibretroCoreConfig struct { Lib string Config string Roms []string + Folder string Width int Height int Ratio float64 @@ -64,17 +66,24 @@ func (e Emulator) GetLibretroCoreConfig(emulator string) LibretroCoreConfig { return conf } -// GetEmulatorByRom returns emulator name by its supported ROM name. -// !to cache into an optimized data structure -func (e Emulator) GetEmulatorByRom(rom string) string { +// GetEmulator tries to find a suitable emulator. +// !to remove quadratic complexity +func (e Emulator) GetEmulator(rom string, path string) string { + found := "" for emu, core := range e.Libretro.Cores.List { for _, romName := range core.Roms { if rom == romName { - return emu + found = emu + if p := strings.SplitN(filepath.ToSlash(path), "/", 2); len(p) > 1 { + folder := p[0] + if (folder != "" && folder == core.Folder) || folder == emu { + return emu + } + } } } } - return "" + return found } func (e Emulator) GetSupportedExtensions() []string { diff --git a/pkg/config/emulator/config_test.go b/pkg/config/emulator/config_test.go new file mode 100644 index 00000000..ebef77c2 --- /dev/null +++ b/pkg/config/emulator/config_test.go @@ -0,0 +1,52 @@ +package emulator + +import "testing" + +func TestGetEmulator(t *testing.T) { + tests := []struct { + rom string + path string + config map[string]LibretroCoreConfig + emulator string + }{ + { + rom: "nes", + path: "test/game.nes", + config: map[string]LibretroCoreConfig{ + "snes": {Roms: []string{"nes"}}, + "nes": {Folder: "test", Roms: []string{"nes"}}, + }, + emulator: "nes", + }, + { + rom: "nes", + path: "nes/game.nes", + config: map[string]LibretroCoreConfig{ + "snes": {Roms: []string{"nes"}}, + "nes": {Roms: []string{"nes"}}, + }, + emulator: "nes", + }, + { + rom: "nes", + path: "test/game.nes", + config: map[string]LibretroCoreConfig{ + "snes": {Roms: []string{"nes"}}, + "nes": {Roms: []string{"nes"}}, + }, + emulator: "nes", + }, + } + + emu := Emulator{ + Libretro: LibretroConfig{}, + } + + for _, test := range tests { + emu.Libretro.Cores.List = test.config + em := emu.GetEmulator(test.rom, test.path) + if test.emulator != em { + t.Errorf("expected result: %v, but was %v with: %v, %v", test.emulator, em, test.rom, test.path) + } + } +} diff --git a/pkg/coordinator/useragenthandlers.go b/pkg/coordinator/useragenthandlers.go index 3a120e71..fbbcb7b4 100644 --- a/pkg/coordinator/useragenthandlers.go +++ b/pkg/coordinator/useragenthandlers.go @@ -205,6 +205,7 @@ func newGameStartCall(roomId string, data string, library games.GameLibrary) (ap return api.GameStartCall{ Name: gameInfo.Name, + Base: gameInfo.Base, Path: gameInfo.Path, Type: gameInfo.Type, }, nil diff --git a/pkg/cws/api/coordinator.go b/pkg/cws/api/coordinator.go index 696e5223..2e5340fe 100644 --- a/pkg/cws/api/coordinator.go +++ b/pkg/cws/api/coordinator.go @@ -30,6 +30,7 @@ func (packet *GameStartRequest) From(data string) error { return from(packet, da type GameStartCall struct { Name string `json:"name"` + Base string `json:"base"` Path string `json:"path"` Type string `json:"type"` } diff --git a/pkg/games/game_library.go b/pkg/games/game_library.go index 76046af9..dfd06228 100644 --- a/pkg/games/game_library.go +++ b/pkg/games/game_library.go @@ -74,6 +74,7 @@ type GameMetadata struct { Name string // the game file extension (e.g. nes, n64) Type string + Base string // the game path relative to the library base path Path string } @@ -126,8 +127,8 @@ func (lib *library) GetAll() []GameMetadata { func (lib *library) FindGameByName(name string) GameMetadata { var game GameMetadata if val, ok := lib.games[name]; ok { - val.Path = filepath.Join(lib.config.path, val.Path) - game = val + val.Base = lib.config.path + return val } return game } diff --git a/pkg/games/game_library_test.go b/pkg/games/game_library_test.go index d360abd2..638bc277 100644 --- a/pkg/games/game_library_test.go +++ b/pkg/games/game_library_test.go @@ -1,7 +1,6 @@ package games import ( - "reflect" "testing" ) @@ -31,7 +30,19 @@ func TestLibraryScan(t *testing.T) { return meta.Name }) - if !reflect.DeepEqual(test.expected, list) { + // ^2 complexity (; + all := true + for _, expect := range test.expected { + found := false + for _, game := range list { + if game == expect { + found = true + break + } + } + all = all && found + } + if !all { t.Errorf("Test fail for dir %v with %v != %v", test.directory, list, test.expected) } } diff --git a/pkg/worker/internalhandlers.go b/pkg/worker/internalhandlers.go index 46cfe5bf..58755b3a 100644 --- a/pkg/worker/internalhandlers.go +++ b/pkg/worker/internalhandlers.go @@ -110,28 +110,21 @@ func (h *Handler) handleGameStart() cws.PacketHandler { log.Println("Received a start request from coordinator") session := h.getSession(resp.SessionID) if session == nil { - log.Printf("Error: No session for ID: %s\n", resp.SessionID) + log.Printf("error: no session with id: %s", resp.SessionID) return cws.EmptyPacket } - peerconnection := session.peerconnection // TODO: Standardize for all types of packet. Make WSPacket generic - startPacket := api.GameStartCall{} - if err := startPacket.From(resp.Data); err != nil { + rom := api.GameStartCall{} + if err := rom.From(resp.Data); err != nil { return cws.EmptyPacket } - gameMeta := games.GameMetadata{ - Name: startPacket.Name, - Type: startPacket.Type, - Path: startPacket.Path, - } - - room := h.startGameHandler(gameMeta, resp.RoomID, resp.PlayerIndex, peerconnection) + game := games.GameMetadata{Name: rom.Name, Type: rom.Type, Base: rom.Base, Path: rom.Path} + room := h.startGameHandler(game, resp.RoomID, resp.PlayerIndex, session.peerconnection) session.RoomID = room.ID - // TODO: can data race + // TODO: can data race (and it does) h.rooms[room.ID] = room - - return cws.WSPacket{ID: "start", RoomID: room.ID} + return cws.WSPacket{ID: api.GameStart, RoomID: room.ID} } } diff --git a/pkg/worker/room/room.go b/pkg/worker/room/room.go index 779b06b3..65039dc8 100644 --- a/pkg/worker/room/room.go +++ b/pkg/worker/room/room.go @@ -10,6 +10,7 @@ import ( "math" "net" "os" + "path/filepath" "sync" "github.com/giongto35/cloud-game/v2/pkg/config/worker" @@ -157,8 +158,8 @@ func NewRoom(roomID string, game games.GameMetadata, onlineStorage *storage.Clie // If not then load room or create room from local. log.Printf("Room %s started. GameName: %s, WithGame: %t", roomID, game.Name, cfg.Encoder.WithoutGame) - // Spawn new emulator based on gameName and plug-in all channels - emuName := cfg.Emulator.GetEmulatorByRom(game.Type) + // Spawn new emulator and plug-in all channels + emuName := cfg.Emulator.GetEmulator(game.Type, game.Path) libretroConfig := cfg.Emulator.GetLibretroCoreConfig(emuName) if cfg.Encoder.WithoutGame { @@ -176,7 +177,7 @@ func NewRoom(roomID string, game games.GameMetadata, onlineStorage *storage.Clie room.audioChannel = audioChannel } - gameMeta := room.director.LoadMeta(game.Path) + gameMeta := room.director.LoadMeta(filepath.Join(game.Base, game.Path)) // nwidth, nheight are the WebRTC output size var nwidth, nheight int