Add new saveStateFs config param

Used when you need a copy of FS for new game sessions (i.e. DOSBox uniqueSaveDir=true).
This commit is contained in:
Sergey Stepanov 2024-11-26 18:08:14 +03:00 committed by sergystepanov
parent 71f5de3bf9
commit 1831e44eef
6 changed files with 91 additions and 17 deletions

View file

@ -28,6 +28,8 @@ library:
ignored:
- neogeo
- pgm
# DOSBox filesystem state
- .pure
# an explicit list of supported file extensions
# which overrides Libretro emulator ROMs configs
supported:
@ -216,6 +218,8 @@ emulator:
# - skip_same_thread_save -- skip thread lock save (used with PPSSPP).
# - uniqueSaveDir (bool) -- needed only for cores (like DosBox) that persist their state into one shared file.
# This will allow for concurrent reading and saving of current states.
# - saveStateFs (string) -- the name of the file that will be initially copied into the save folder.
# All * symbols will be replaced to the name of the ROM.
list:
gba:
lib: mgba_libretro
@ -281,6 +285,7 @@ emulator:
folder: dos
kbMouseSupport: true
nonBlockingSave: true
saveStateFs: "*.pure.zip"
hid:
0: [ 257, 513 ]
1: [ 257, 513 ]

View file

@ -54,6 +54,7 @@ type LibretroCoreConfig struct {
Options map[string]string
Options4rom map[string]map[string]string // <(^_^)>
Roms []string
SaveStateFs string
Scale float64
UniqueSaveDir bool
UsesLibCo bool

View file

@ -21,7 +21,7 @@ type libConf struct {
aliasFile string
path string
supported map[string]struct{}
ignored map[string]struct{}
ignored []string
verbose bool
watchMode bool
sessionPath string
@ -98,7 +98,7 @@ func NewLib(conf config.Library, emu WithEmulatorInfo, log *logger.Logger) GameL
aliasFile: conf.AliasFile,
path: dir,
supported: toMap(conf.Supported),
ignored: toMap(conf.Ignored),
ignored: conf.Ignored,
verbose: conf.Verbose,
watchMode: conf.WatchMode,
sessionPath: emu.SessionStoragePath(),
@ -207,22 +207,36 @@ func (lib *library) Scan() {
return err
}
if info != nil && !info.IsDir() && lib.isExtAllowed(path) {
meta := getMetadata(path, dir)
if info == nil || info.IsDir() || !lib.isExtAllowed(path) {
return nil
}
meta.System = lib.emuConf.GetEmulator(meta.Type, meta.Path)
meta := metadata(path, dir)
meta.System = lib.emuConf.GetEmulator(meta.Type, meta.Path)
if aliases != nil {
k, ok := aliases[meta.Name]
if ok {
meta.Alias = k
}
}
if _, ok := lib.config.ignored[meta.Name]; !ok {
games = append(games, meta)
if aliases != nil {
if k, ok := aliases[meta.Name]; ok {
meta.Alias = k
}
}
ignored := false
for _, k := range lib.config.ignored {
if meta.Name == k {
ignored = true
break
}
if len(k) > 0 && k[0] == '.' && strings.Contains(meta.Name, k) {
ignored = true
break
}
}
if !ignored {
games = append(games, meta)
}
return nil
})
@ -322,8 +336,8 @@ func (lib *library) isExtAllowed(path string) bool {
return ok
}
// getMetadata returns game info from a path
func getMetadata(path string, basePath string) GameMetadata {
// metadata returns game info from a path
func metadata(path string, basePath string) GameMetadata {
name := filepath.Base(path)
ext := filepath.Ext(name)
relPath, _ := filepath.Rel(basePath, path)

View file

@ -51,6 +51,18 @@ func GetUserHome() (string, error) {
return me.HomeDir, nil
}
func CopyFile(from string, to string) error {
bytesRead, err := os.ReadFile(from)
if err != nil {
return err
}
err = os.WriteFile(to, bytesRead, 0755)
if err != nil {
return err
}
return nil
}
func WriteFile(name string, data []byte, perm os.FileMode) error {
return os.WriteFile(name, data, perm)
}

View file

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"path/filepath"
"strings"
"sync"
"time"
"unsafe"
@ -67,6 +68,7 @@ type Frontend struct {
DisableCanvasPool bool
SaveOnClose bool
UniqueSaveDir bool
SaveStateFs string
}
type Device byte
@ -154,6 +156,7 @@ func (f *Frontend) LoadCore(emu string) {
KbMouseSupport: conf.KbMouseSupport,
}
f.mu.Lock()
f.SaveStateFs = conf.SaveStateFs
if conf.UniqueSaveDir {
f.UniqueSaveDir = true
f.nano.SetSaveDirSuffix(f.storage.MainPath())
@ -287,6 +290,13 @@ func (f *Frontend) Start() {
}
}
func (f *Frontend) LoadGame(path string) error {
if f.UniqueSaveDir {
f.copyFsMaybe(path)
}
return f.nano.LoadGame(path)
}
func (f *Frontend) AspectRatio() float32 { return f.nano.AspectRatio() }
func (f *Frontend) AudioSampleRate() int { return f.nano.AudioSampleRate() }
func (f *Frontend) FPS() int { return f.nano.VideoFramerate() }
@ -296,7 +306,6 @@ func (f *Frontend) HasSave() bool { return os.Exists(f.HashPath(
func (f *Frontend) HashPath() string { return f.storage.GetSavePath() }
func (f *Frontend) IsPortrait() bool { return f.nano.IsPortrait() }
func (f *Frontend) KbMouseSupport() bool { return f.nano.KbMouseSupport() }
func (f *Frontend) LoadGame(path string) error { return f.nano.LoadGame(path) }
func (f *Frontend) PixFormat() uint32 { return f.nano.Video.PixFmt.C }
func (f *Frontend) RestoreGameState() error { return f.Load() }
func (f *Frontend) Rotation() uint { return f.nano.Rot }
@ -349,6 +358,9 @@ func (f *Frontend) Close() {
}
}
f.UniqueSaveDir = false
f.SaveStateFs = ""
f.mui.Unlock()
f.log.Debug().Msgf("frontend closed")
}
@ -420,3 +432,32 @@ func (f *Frontend) autosave(periodSec int) {
}
}
}
func (f *Frontend) copyFsMaybe(path string) {
if f.SaveStateFs == "" {
return
}
fileName := f.SaveStateFs
hasPlaceholder := strings.HasPrefix(f.SaveStateFs, "*")
if hasPlaceholder {
game := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
fileName = strings.Replace(f.SaveStateFs, "*", game, 1)
}
fullPath := filepath.Join(f.nano.SaveDir(), fileName)
if os.Exists(fullPath) {
return
}
storePath := filepath.Dir(path)
fsPath := filepath.Join(storePath, fileName)
if os.Exists(fsPath) {
if err := os.CopyFile(fsPath, fullPath); err != nil {
f.log.Error().Err(err).Msgf("fs copy fail")
} else {
f.log.Debug().Msgf("copied fs %v to %v", fsPath, fullPath)
}
}
}

View file

@ -157,6 +157,7 @@ func (n *Nanoarch) WaitReady() { <-n.reserved }
func (n *Nanoarch) Close() { n.Stopped.Store(true); n.reserved <- struct{}{} }
func (n *Nanoarch) SetLogger(log *logger.Logger) { n.log = log }
func (n *Nanoarch) SetVideoDebounce(t time.Duration) { n.limiter = NewLimit(t) }
func (n *Nanoarch) SaveDir() string { return C.GoString(n.cSaveDirectory) }
func (n *Nanoarch) SetSaveDirSuffix(sx string) {
dir := C.GoString(n.cSaveDirectory) + "/" + sx
err := os.CheckCreateDir(dir)