diff --git a/pkg/config/config.yaml b/pkg/config/config.yaml index 9c1ee1aa..33eb0b2a 100644 --- a/pkg/config/config.yaml +++ b/pkg/config/config.yaml @@ -160,6 +160,32 @@ emulator: sync: true # external cross-process mutex lock extLock: "{user}/.cr/cloud-game.lock" + map: + darwin: + amd64: + arch: x86_64 + ext: .dylib + os: osx + vendor: apple + arm64: + arch: arm64 + ext: .dylib + os: osx + vendor: apple + linux: + amd64: + arch: x86_64 + ext: .so + os: linux + arm: + arch: armv7-neon-hf + ext: .so + os: linux + windows: + amd64: + arch: x86_64 + ext: .dll + os: windows main: type: buildbot url: https://buildbot.libretro.com/nightly diff --git a/pkg/config/emulator.go b/pkg/config/emulator.go index 21c5b2ad..d3daca3e 100644 --- a/pkg/config/emulator.go +++ b/pkg/config/emulator.go @@ -1,8 +1,10 @@ package config import ( + "errors" "path" "path/filepath" + "runtime" "strings" ) @@ -19,12 +21,7 @@ type LibretroConfig struct { Paths struct { Libs string } - Repo struct { - Sync bool - ExtLock string - Main LibretroRepoConfig - Secondary LibretroRepoConfig - } + Repo LibretroRemoteRepo List map[string]LibretroCoreConfig } DebounceMs int @@ -33,12 +30,42 @@ type LibretroConfig struct { LogLevel int } +type LibretroRemoteRepo struct { + Sync bool + ExtLock string + Map map[string]map[string]LibretroRepoMapInfo + Main LibretroRepoConfig + Secondary LibretroRepoConfig +} + +// LibretroRepoMapInfo contains Libretro core lib platform info. +// And the cores are just C-compiled libraries. +// See: https://buildbot.libretro.com/nightly. +type LibretroRepoMapInfo struct { + Arch string // bottom: x86_64, x86, ... + Ext string // platform dependent library file extension (dot-prefixed) + Os string // middle: windows, ios, ... + Vendor string // top level: apple, nintendo, ... +} + type LibretroRepoConfig struct { Type string Url string Compression string } +// Guess tries to map OS + CPU architecture to the corresponding remote URL path. +// See: https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63. +func (lrp LibretroRemoteRepo) Guess() (LibretroRepoMapInfo, error) { + if os, ok := lrp.Map[runtime.GOOS]; ok { + if arch, ok2 := os[runtime.GOARCH]; ok2 { + return arch, nil + } + } + return LibretroRepoMapInfo{}, + errors.New("core mapping not found for " + runtime.GOOS + ":" + runtime.GOARCH) +} + type LibretroCoreConfig struct { AltRepo bool AutoGlContext bool // hack: keep it here to pass it down the emulator diff --git a/pkg/worker/caged/libretro/frontend.go b/pkg/worker/caged/libretro/frontend.go index 341038a4..c3666e98 100644 --- a/pkg/worker/caged/libretro/frontend.go +++ b/pkg/worker/caged/libretro/frontend.go @@ -142,6 +142,14 @@ func NewFrontend(conf config.Emulator, log *logger.Logger) (*Frontend, error) { func (f *Frontend) LoadCore(emu string) { conf := f.conf.GetLibretroCoreConfig(emu) + + libExt := "" + if ar, err := f.conf.Libretro.Cores.Repo.Guess(); err == nil { + libExt = ar.Ext + } else { + f.log.Warn().Err(err).Msg("system arch guesser failed") + } + meta := nanoarch.Metadata{ AutoGlContext: conf.AutoGlContext, FrameDup: f.conf.Libretro.Dup, @@ -155,6 +163,7 @@ func (f *Frontend) LoadCore(emu string) { UsesLibCo: conf.UsesLibCo, CoreAspectRatio: conf.CoreAspectRatio, KbMouseSupport: conf.KbMouseSupport, + LibExt: libExt, } f.mu.Lock() f.SaveStateFs = conf.SaveStateFs diff --git a/pkg/worker/caged/libretro/frontend_test.go b/pkg/worker/caged/libretro/frontend_test.go index e4c4105c..f2b108ee 100644 --- a/pkg/worker/caged/libretro/frontend_test.go +++ b/pkg/worker/caged/libretro/frontend_test.go @@ -26,6 +26,7 @@ type TestFrontend struct { *Frontend corePath string + coreExt string gamePath string system string } @@ -78,6 +79,11 @@ func EmulatorMock(room string, system string) *TestFrontend { nano := nanoarch.NewNano(conf.Emulator.LocalPath) nano.SetLogger(l2) + arch, err := conf.Emulator.Libretro.Cores.Repo.Guess() + if err != nil { + panic(err) + } + // an emu emu := &TestFrontend{ Frontend: &Frontend{ @@ -92,6 +98,7 @@ func EmulatorMock(room string, system string) *TestFrontend { SaveOnClose: false, }, corePath: expand(conf.Emulator.GetLibretroCoreConfig(system).Lib), + coreExt: arch.Ext, gamePath: expand(conf.Library.BasePath), system: system, } @@ -133,6 +140,7 @@ func (emu *TestFrontend) loadRom(game string) { Options4rom: conf.Options4rom, UsesLibCo: conf.UsesLibCo, CoreAspectRatio: conf.CoreAspectRatio, + LibExt: emu.coreExt, } emu.nano.CoreLoad(meta) diff --git a/pkg/worker/caged/libretro/manager/http.go b/pkg/worker/caged/libretro/manager/http.go index 308677a4..ff57a2de 100644 --- a/pkg/worker/caged/libretro/manager/http.go +++ b/pkg/worker/caged/libretro/manager/http.go @@ -4,16 +4,14 @@ import ( "github.com/giongto35/cloud-game/v3/pkg/config" "github.com/giongto35/cloud-game/v3/pkg/logger" "github.com/giongto35/cloud-game/v3/pkg/os" - "github.com/giongto35/cloud-game/v3/pkg/worker/caged/libretro/repo" - "github.com/giongto35/cloud-game/v3/pkg/worker/caged/libretro/repo/arch" ) type Manager struct { BasicManager - arch arch.Info - repo repo.Repository - altRepo repo.Repository + arch ArchInfo + repo Repository + altRepo Repository client Downloader fmu *os.Flock log *logger.Logger @@ -29,24 +27,24 @@ func NewRemoteHttpManager(conf config.LibretroConfig, log *logger.Logger) Manage log.Error().Err(err).Msgf("couldn't make file lock") } - ar, err := arch.Guess() + arch, err := conf.Cores.Repo.Guess() if err != nil { log.Error().Err(err).Msg("couldn't get Libretro core file extension") } m := Manager{ BasicManager: BasicManager{Conf: conf}, - arch: ar, + arch: ArchInfo(arch), client: NewDefaultDownloader(log), fmu: flock, log: log, } if repoConf.Type != "" { - m.repo = repo.New(repoConf.Type, repoConf.Url, repoConf.Compression, "buildbot") + m.repo = NewRepo(repoConf.Type, repoConf.Url, repoConf.Compression, "buildbot") } if altRepoConf.Type != "" { - m.altRepo = repo.New(altRepoConf.Type, altRepoConf.Url, altRepoConf.Compression, "") + m.altRepo = NewRepo(altRepoConf.Type, altRepoConf.Url, altRepoConf.Compression, "") } return m @@ -81,7 +79,7 @@ func (m *Manager) Sync() error { } }() - installed, err := m.GetInstalled(m.arch.LibExt) + installed, err := m.GetInstalled(m.arch.Ext) if err != nil { return err } @@ -92,9 +90,9 @@ func (m *Manager) Sync() error { return nil } -func (m *Manager) getCoreUrls(names []string, repo repo.Repository) (urls []Download) { +func (m *Manager) getCoreUrls(names []string, repo Repository) (urls []Download) { for _, c := range names { - urls = append(urls, Download{Key: c, Address: repo.GetCoreUrl(c, m.arch)}) + urls = append(urls, Download{Key: c, Address: repo.CoreUrl(c, m.arch)}) } return } @@ -137,7 +135,7 @@ func (m *Manager) download(cores []config.CoreInfo) (failed []string) { return } -func (m *Manager) down(cores []string, repo repo.Repository) (failed []string) { +func (m *Manager) down(cores []string, repo Repository) (failed []string) { if len(cores) == 0 || repo == nil { return } diff --git a/pkg/worker/caged/libretro/manager/repository.go b/pkg/worker/caged/libretro/manager/repository.go new file mode 100644 index 00000000..3dbe0686 --- /dev/null +++ b/pkg/worker/caged/libretro/manager/repository.go @@ -0,0 +1,65 @@ +package manager + +import "strings" + +type ArchInfo struct { + Arch string + Ext string + Os string + Vendor string +} + +type Data struct { + Url string + Compression string +} + +type Repository interface { + CoreUrl(file string, info ArchInfo) (url string) +} + +// Repo defines a simple zip file containing all the cores that will be extracted as is. +type Repo struct { + Address string + Compression string +} + +func (r Repo) CoreUrl(_ string, _ ArchInfo) string { return r.Address } + +type Buildbot struct{ Repo } + +func (r Buildbot) CoreUrl(file string, info ArchInfo) string { + var sb strings.Builder + sb.WriteString(r.Address + "/") + if info.Vendor != "" { + sb.WriteString(info.Vendor + "/") + } + sb.WriteString(info.Os + "/" + info.Arch + "/latest/" + file + info.Ext) + if r.Compression != "" { + sb.WriteString("." + r.Compression) + } + return sb.String() +} + +type Github struct{ Buildbot } + +func (r Github) CoreUrl(file string, info ArchInfo) string { + return r.Buildbot.CoreUrl(file, info) + "?raw=true" +} + +func NewRepo(kind string, url string, compression string, defaultRepo string) Repository { + var repository Repository + switch kind { + case "buildbot": + repository = Buildbot{Repo{Address: url, Compression: compression}} + case "github": + repository = Github{Buildbot{Repo{Address: url, Compression: compression}}} + case "raw": + repository = Repo{Address: url, Compression: "zip"} + default: + if defaultRepo != "" { + repository = NewRepo(defaultRepo, url, compression, "") + } + } + return repository +} diff --git a/pkg/worker/caged/libretro/manager/repository_test.go b/pkg/worker/caged/libretro/manager/repository_test.go new file mode 100644 index 00000000..bff2c16a --- /dev/null +++ b/pkg/worker/caged/libretro/manager/repository_test.go @@ -0,0 +1,61 @@ +package manager + +import "testing" + +func TestCoreUrl(t *testing.T) { + testAddress := "https://test.me" + tests := []struct { + arch ArchInfo + compress string + f string + repo string + result string + }{ + { + arch: ArchInfo{Arch: "x86_64", Ext: ".so", Os: "linux"}, + f: "uber_core", + repo: "buildbot", + result: testAddress + "/" + "linux/x86_64/latest/uber_core.so", + }, + { + arch: ArchInfo{Arch: "x86_64", Ext: ".so", Os: "linux"}, + compress: "zip", + f: "uber_core", + repo: "buildbot", + result: testAddress + "/" + "linux/x86_64/latest/uber_core.so.zip", + }, + { + arch: ArchInfo{Arch: "x86_64", Ext: ".dylib", Os: "osx", Vendor: "apple"}, + f: "uber_core", + repo: "buildbot", + result: testAddress + "/" + "apple/osx/x86_64/latest/uber_core.dylib", + }, + { + arch: ArchInfo{Os: "linux", Arch: "x86_64", Ext: ".so"}, + f: "uber_core", + repo: "github", + result: testAddress + "/" + "linux/x86_64/latest/uber_core.so?raw=true", + }, + { + arch: ArchInfo{Os: "linux", Arch: "x86_64", Ext: ".so"}, + compress: "zip", + f: "uber_core", + repo: "github", + result: testAddress + "/" + "linux/x86_64/latest/uber_core.so.zip?raw=true", + }, + { + arch: ArchInfo{Os: "osx", Arch: "x86_64", Vendor: "apple", Ext: ".dylib"}, + f: "uber_core", + repo: "github", + result: testAddress + "/" + "apple/osx/x86_64/latest/uber_core.dylib?raw=true", + }, + } + + for _, test := range tests { + r := NewRepo(test.repo, testAddress, test.compress, "") + url := r.CoreUrl(test.f, test.arch) + if url != test.result { + t.Errorf("seems that expected link address is incorrect (%v) for file %s %+v", url, test.f, test.arch) + } + } +} diff --git a/pkg/worker/caged/libretro/nanoarch/nanoarch.go b/pkg/worker/caged/libretro/nanoarch/nanoarch.go index 40b3c231..1748ec15 100644 --- a/pkg/worker/caged/libretro/nanoarch/nanoarch.go +++ b/pkg/worker/caged/libretro/nanoarch/nanoarch.go @@ -15,7 +15,6 @@ import ( "github.com/giongto35/cloud-game/v3/pkg/logger" "github.com/giongto35/cloud-game/v3/pkg/os" "github.com/giongto35/cloud-game/v3/pkg/worker/caged/libretro/graphics" - "github.com/giongto35/cloud-game/v3/pkg/worker/caged/libretro/repo/arch" "github.com/giongto35/cloud-game/v3/pkg/worker/thread" ) @@ -100,6 +99,7 @@ type Metadata struct { Hid map[int][]int CoreAspectRatio bool KbMouseSupport bool + LibExt string } type PixFmt struct { @@ -200,20 +200,14 @@ func (n *Nanoarch) CoreLoad(meta Metadata) { n.options = maps.Clone(meta.Options) n.options4rom = meta.Options4rom - filePath := meta.LibPath - if ar, err := arch.Guess(); err == nil { - filePath = filePath + ar.LibExt - } else { - n.log.Warn().Err(err).Msg("system arch guesser failed") - } - - coreLib, err = loadLib(filePath) + corePath := meta.LibPath + meta.LibExt + coreLib, err = loadLib(corePath) // fallback to sequential lib loader (first successfully loaded) if err != nil { - n.log.Error().Err(err).Msgf("load fail: %v", filePath) - coreLib, err = loadLibRollingRollingRolling(filePath) + n.log.Error().Err(err).Msgf("load fail: %v", corePath) + coreLib, err = loadLibRollingRollingRolling(corePath) if err != nil { - n.log.Fatal().Err(err).Msgf("core load: %s", filePath) + n.log.Fatal().Err(err).Msgf("core load: %s", corePath) } } diff --git a/pkg/worker/caged/libretro/repo/arch/arch.go b/pkg/worker/caged/libretro/repo/arch/arch.go deleted file mode 100644 index 16e5a88d..00000000 --- a/pkg/worker/caged/libretro/repo/arch/arch.go +++ /dev/null @@ -1,39 +0,0 @@ -package arch - -import ( - "errors" - "runtime" -) - -// See: https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63. -var libretroOsArchMap = map[string]Info{ - "linux:amd64": {Os: "linux", Arch: "x86_64", LibExt: ".so"}, - "linux:arm": {Os: "linux", Arch: "armv7-neon-hf", LibExt: ".so"}, - "windows:amd64": {Os: "windows", Arch: "x86_64", LibExt: ".dll"}, - "darwin:amd64": {Os: "osx", Arch: "x86_64", Vendor: "apple", LibExt: ".dylib"}, - "darwin:arm64": {Os: "osx", Arch: "arm64", Vendor: "apple", LibExt: ".dylib"}, -} - -// Info contains Libretro core lib platform info. -// And cores are just C-compiled libraries. -// See: https://buildbot.libretro.com/nightly. -type Info struct { - // bottom: x86_64, x86, ... - Arch string - // middle: windows, ios, ... - Os string - // top level: apple, nintendo, ... - Vendor string - - // platform dependent library file extension (dot-prefixed) - LibExt string -} - -func Guess() (Info, error) { - key := runtime.GOOS + ":" + runtime.GOARCH - if arch, ok := libretroOsArchMap[key]; ok { - return arch, nil - } else { - return Info{}, errors.New("core mapping not found for " + key) - } -} diff --git a/pkg/worker/caged/libretro/repo/buildbot/repository.go b/pkg/worker/caged/libretro/repo/buildbot/repository.go deleted file mode 100644 index 44bdcd1b..00000000 --- a/pkg/worker/caged/libretro/repo/buildbot/repository.go +++ /dev/null @@ -1,34 +0,0 @@ -package buildbot - -import ( - "strings" - - "github.com/giongto35/cloud-game/v3/pkg/worker/caged/libretro/repo/arch" - "github.com/giongto35/cloud-game/v3/pkg/worker/caged/libretro/repo/raw" -) - -type RepoBuildbot struct { - raw.Repo -} - -func NewBuildbotRepo(address string, compression string) RepoBuildbot { - return RepoBuildbot{ - Repo: raw.Repo{ - Address: address, - Compression: compression, - }, - } -} - -func (r RepoBuildbot) GetCoreUrl(file string, info arch.Info) string { - var sb strings.Builder - sb.WriteString(r.Address + "/") - if info.Vendor != "" { - sb.WriteString(info.Vendor + "/") - } - sb.WriteString(info.Os + "/" + info.Arch + "/latest/" + file + info.LibExt) - if r.Compression != "" { - sb.WriteString("." + r.Compression) - } - return sb.String() -} diff --git a/pkg/worker/caged/libretro/repo/buildbot/repository_test.go b/pkg/worker/caged/libretro/repo/buildbot/repository_test.go deleted file mode 100644 index 5aa007b9..00000000 --- a/pkg/worker/caged/libretro/repo/buildbot/repository_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package buildbot - -import ( - "testing" - - "github.com/giongto35/cloud-game/v3/pkg/worker/caged/libretro/repo/arch" -) - -func TestBuildbotRepo(t *testing.T) { - testAddress := "https://test.me" - tests := []struct { - file string - compression string - arch arch.Info - resultUrl string - }{ - { - file: "uber_core", - arch: arch.Info{ - Os: "linux", - Arch: "x86_64", - LibExt: ".so", - }, - resultUrl: testAddress + "/" + "linux/x86_64/latest/uber_core.so", - }, - { - file: "uber_core", - compression: "zip", - arch: arch.Info{ - Os: "linux", - Arch: "x86_64", - LibExt: ".so", - }, - resultUrl: testAddress + "/" + "linux/x86_64/latest/uber_core.so.zip", - }, - { - file: "uber_core", - arch: arch.Info{ - Os: "osx", - Arch: "x86_64", - Vendor: "apple", - LibExt: ".dylib", - }, - resultUrl: testAddress + "/" + "apple/osx/x86_64/latest/uber_core.dylib", - }, - } - - for _, test := range tests { - rep := NewBuildbotRepo(testAddress, test.compression) - url := rep.GetCoreUrl(test.file, test.arch) - if url != test.resultUrl { - t.Errorf("seems that expected link address is incorrect (%v) for file %s %+v", url, test.file, test.arch) - } - } -} diff --git a/pkg/worker/caged/libretro/repo/github/repository.go b/pkg/worker/caged/libretro/repo/github/repository.go deleted file mode 100644 index 532c02f0..00000000 --- a/pkg/worker/caged/libretro/repo/github/repository.go +++ /dev/null @@ -1,18 +0,0 @@ -package github - -import ( - "github.com/giongto35/cloud-game/v3/pkg/worker/caged/libretro/repo/arch" - "github.com/giongto35/cloud-game/v3/pkg/worker/caged/libretro/repo/buildbot" -) - -type RepoGithub struct { - buildbot.RepoBuildbot -} - -func NewGithubRepo(address string, compression string) RepoGithub { - return RepoGithub{RepoBuildbot: buildbot.NewBuildbotRepo(address, compression)} -} - -func (r RepoGithub) GetCoreUrl(file string, info arch.Info) string { - return r.RepoBuildbot.GetCoreUrl(file, info) + "?raw=true" -} diff --git a/pkg/worker/caged/libretro/repo/github/repository_test.go b/pkg/worker/caged/libretro/repo/github/repository_test.go deleted file mode 100644 index cf3a4380..00000000 --- a/pkg/worker/caged/libretro/repo/github/repository_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package github - -import ( - "testing" - - "github.com/giongto35/cloud-game/v3/pkg/worker/caged/libretro/repo/arch" -) - -func TestBuildbotRepo(t *testing.T) { - testAddress := "https://test.me" - tests := []struct { - file string - compression string - arch arch.Info - resultUrl string - }{ - { - file: "uber_core", - arch: arch.Info{ - Os: "linux", - Arch: "x86_64", - LibExt: ".so", - }, - resultUrl: testAddress + "/" + "linux/x86_64/latest/uber_core.so?raw=true", - }, - { - file: "uber_core", - compression: "zip", - arch: arch.Info{ - Os: "linux", - Arch: "x86_64", - LibExt: ".so", - }, - resultUrl: testAddress + "/" + "linux/x86_64/latest/uber_core.so.zip?raw=true", - }, - { - file: "uber_core", - arch: arch.Info{ - Os: "osx", - Arch: "x86_64", - Vendor: "apple", - LibExt: ".dylib", - }, - resultUrl: testAddress + "/" + "apple/osx/x86_64/latest/uber_core.dylib?raw=true", - }, - } - - for _, test := range tests { - rep := NewGithubRepo(testAddress, test.compression) - url := rep.GetCoreUrl(test.file, test.arch) - if url != test.resultUrl { - t.Errorf("seems that expected link address is incorrect (%v) for file %s %+v", url, test.file, test.arch) - } - } -} diff --git a/pkg/worker/caged/libretro/repo/raw/repository.go b/pkg/worker/caged/libretro/repo/raw/repository.go deleted file mode 100644 index 33c9056a..00000000 --- a/pkg/worker/caged/libretro/repo/raw/repository.go +++ /dev/null @@ -1,14 +0,0 @@ -package raw - -import "github.com/giongto35/cloud-game/v3/pkg/worker/caged/libretro/repo/arch" - -type Repo struct { - Address string - Compression string -} - -// NewRawRepo defines a simple zip file containing -// all the cores that will be extracted as is. -func NewRawRepo(address string) Repo { return Repo{Address: address, Compression: "zip"} } - -func (r Repo) GetCoreUrl(_ string, _ arch.Info) string { return r.Address } diff --git a/pkg/worker/caged/libretro/repo/repository.go b/pkg/worker/caged/libretro/repo/repository.go deleted file mode 100644 index e2a99c1e..00000000 --- a/pkg/worker/caged/libretro/repo/repository.go +++ /dev/null @@ -1,36 +0,0 @@ -package repo - -import ( - "github.com/giongto35/cloud-game/v3/pkg/worker/caged/libretro/repo/arch" - "github.com/giongto35/cloud-game/v3/pkg/worker/caged/libretro/repo/buildbot" - "github.com/giongto35/cloud-game/v3/pkg/worker/caged/libretro/repo/github" - "github.com/giongto35/cloud-game/v3/pkg/worker/caged/libretro/repo/raw" -) - -type ( - Data struct { - Url string - Compression string - } - - Repository interface { - GetCoreUrl(file string, info arch.Info) (url string) - } -) - -func New(kind string, url string, compression string, defaultRepo string) Repository { - var repository Repository - switch kind { - case "raw": - repository = raw.NewRawRepo(url) - case "github": - repository = github.NewGithubRepo(url, compression) - case "buildbot": - repository = buildbot.NewBuildbotRepo(url, compression) - default: - if defaultRepo != "" { - repository = New(defaultRepo, url, compression, "") - } - } - return repository -}