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