Allow config for the remote Libretro core repos

This commit is contained in:
Sergey Stepanov 2024-12-20 01:32:20 +03:00
parent 535e725618
commit f78bcf3e4b
No known key found for this signature in database
GPG key ID: A56B4929BAA8556B
15 changed files with 219 additions and 282 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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()
}

View file

@ -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)
}
}
}

View file

@ -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"
}

View file

@ -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)
}
}
}

View file

@ -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 }

View file

@ -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
}