Closed mtkennerly closed 4 months ago
Hello, I am not currently a user of ludusavi (but I plan to use it very soon). I was working on something similar and came up with an alterative option, that wasn't listed here.
It is to modify the launch options of the launcher entry, and make ludusavi manage the game process.
For example you can run a game using "C:\Windows\System32\cmd.exe" /C %command%
as a launchoption in steam. In this case cmd.exe
is responsible of the game process.
For ludusavi it can be something like ludusavi startgame -- %command%
It has several advantages and disadvantages:
The main disadvantage is that it require to edit the steam entry. And some of them are generated and managed by other tools (ex: emudeck)
This can be used in combination with a process watcher, for the best of both worlds.
Of course this is just an idea, I did not do any proof of concept and I don't know if it is possible implement in ludusavi.
I just finished configure ludusavi on my Windows but used it with playnite and loved this hability to backup the save after I close the game.
It will be nice if you find a way to make possible "just" with ludusavi cause with my steam deck it will be nice to have to install playnite just for that.
Sry for my English it's not my first language.
It is to modify the launch options of the launcher entry, and make ludusavi manage the game process.
I use this approach to "hook" ludusavi into the game launching process with Heroic. It allows to set a wrapper command which receives the actual command to launch the game, including all parameters. This way, my wrapper can do some checks and restore backups before the game is launched and do a backup once it has finished.
The actual command contains enough information to determine the game name used in ludusavi, for example:
/usr/bin/mangohud --dlsym ./gogdl --auth-config-path /home/MYUSER/.config/heroic/gog_store/auth.json launch /home/MYUSER/Games/Avernum 1207663333 --no-wine --wrapper '/home/MYUSER/.config/heroic/tools/proton/Proton-GE-Proton8-3/proton' run --platform windows --prefer-task 0
Unfortunately this heavily depends on how Heroic launches a game and which parameters are used. Using mangohud for example results in parameters being shifted.
This is the script (not working if mangohud is used!), also found here: https://github.com/sluedecke/ludusavi-launcher
#!/bin/sh
# -- ABOUT --
#
# Wrapper script to use ludusavi for savegame backup / restoration.
#
# Assumes that $5 is the directory with the game install which contains the file
# `gameinfo`.
#
# This file comes without warranty, use at your own risk!
#
# License: MIT License
# Author: Sascha Lüdecke <sascha@meta-x.de>
# -- BUGS --
#
# - does not work if mangohud is used
# -- HISTORY --
#
# Version: 0.X - WIP
#
#
# Version: 0.2 - 2022-09-29
#
# . use zenity to tell user that we back up / restore
#
# Version: 0.1 - 2022-09-27
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
LOGFILE=$SCRIPT_DIR/ludusavi-cloud.log
# Standard paths
## LUDUSAVI=/usr/bin/ludusavi
LUDUSAVI=$HOME/Projekte/contributing/ludusavi/target/debug/ludusavi
ZENITY=/usr/bin/zenity
TEE=/usr/bin/tee
JQ=/usr/bin/jq
HEAD=/usr/bin/head
DATE=/usr/bin/date
ECHO=/usr/bin/echo
GAMENAME=""
if [ -r "$5/gameinfo" ]
then
GAMENAME=`$HEAD -1 "$5/gameinfo"`
else
GAMENAME=`$JQ -r .name "$5/goggame-$6.info"`
fi
# Path on steam deck if installed via flatpack
if [ $USER == deck ]
then
LUDUSAVI=/home/.steamos/offload/var/lib/flatpak/exports/bin/com.github.mtkennerly.ludusavi
fi
{
$ECHO ==================================================
$ECHO
$ECHO Gamename is: $GAMENAME
$ECHO Start time: `$DATE`
$ECHO Parameters: $@
# restore savegame
$ECHO $LUDUSAVI restore --force "$GAMENAME"
(
$ECHO "# Restoring savegame for $GAMENAME"
# bypass STDOUT
$LUDUSAVI restore --force "$GAMENAME" 1>&2
) | $ZENITY --progress \
--title="Savegame restore" \
--no-cancel \
--auto-close \
--pulsate
# run game
$ECHO Game run command:
$ECHO $@
"$@"
# backup savegame
$ECHO $LUDUSAVI backup --merge --force "$GAMENAME"
(
$ECHO "# Backing up savegames for $GAMENAME"
# bypass STDOUT
$LUDUSAVI backup --merge --force "$GAMENAME" 1>&2
) | $ZENITY --progress \
--title="Savegame backup" \
--no-cancel \
--auto-close \
--pulsate
$ECHO End time: `$DATE`
$ECHO
$ECHO ==================================================
} 2>&1 | $TEE -a $LOGFILE
I have added a first shot at implementing a launcher command. It is based on the shell script above and currently needs to be invoked as (note the double dash after launcher, they are needed ATM):
ludusavi launcher -- /opt/Heroic/resources/app.asar.unpacked/build/bin/linux/gogdl --auth-config-path /home/MYUSER/.config/heroic/gog_store/auth.json launch /home/MYUSER/Games/Factorio 1238653230 --platform linux
Currently only gogdl based launches (if installed from heroic) are implemented.
See here: https://github.com/sluedecke/ludusavi/tree/feature/launcher
Thanks for looking into this \:D
The only reservation I have is about how hard it may be to maintain this approach when Heroic/etc make changes to how they launch games. That said, if we can make the implementation safe and robust, then I think it's worth incorporating as an option.
Some initial feedback:
launch
, the path, and the ID will appear in that order, I don't think there's any guarantee that they'll appear next to each other, since they're just positional arguments. We should be able to use Clap to reparse the wrapped command in a more robust way.info
/goggame
files, maybe we can reuse launchers.rs
/heroic.rs
. The Launchers
struct could store some extra info (probably the numeric ID and/or the Heroic name) for each game to facilitate a reverse mapping to Ludusavi's normal name.wrap
.--
are fine (I would even say ideal), since it gives us room to add other flags in Ludusavi.We could support these two styles:
# Predetermined name - don't need to do any special lookups.
ludusavi wrap Factorio -- ...
# Infer info from CLI explicitly for Heroic.
ludusavi wrap --infer heroic -- ...
I think we should make it required to pass either a name or an --infer
option. That way, it's very explicit about what it supports inferring from, which could help to avoid false positives and to constrain how many possibilities we have to consider at any give time.
Thanks for the feedback!
info/goggame
: definitely worth looking into itRecent commit is about:
wrap
Could look like this:
cargo run -- wrap --help
Compiling ludusavi v0.19.0 (/home/saschal/Projekte/contributing/ludusavi)
Finished dev [optimized + debuginfo] target(s) in 13.10s
Running `target/debug/ludusavi wrap --help`
ludusavi-wrap
Wrap restore/backup around game execution
USAGE:
ludusavi wrap [OPTIONS] [COMMANDS]...
ARGS:
<COMMANDS>... Commands to launch the game
OPTIONS:
-h, --help Print help information
--infer <LAUNCHER> Infer game name from commands based on launcher
type [possible values: heroic]
--name <NAME> Directly set game name as known to ludusavi
Ah, I guess Clap can't mark the name as "positional, but only if it comes before --". That looks fine then :+1:
Feel free to open a draft PR, and we can move the implementation discussion there.
This would be beneficial alongside #90
An alternative solution would be to run ludusavi as a daemon and set up a file system event watcher (e.g. inotifywait) for all the known paths. Then the savegames could be copied whenever a new savegame is made even while playing the game and not just after the game has been stopped.
After merging https://github.com/mtkennerly/ludusavi/pull/235, is this still an open issue?
I think there's still room to explore the monitoring options in the first message. There could be different pros/cons between monitoring and the wrap
CLI, so I think both have value.
I have a proposition to add to wrap
's functionality: using a parameter, let me specify if I want to restore or backup?
You'd get something like ludusavi wrap --backup --gui
. I can probably add that myself, but before I put in the effort, I'd like to run the idea by you @mtkennerly .
Second idea: add more options to --infer
. I would think that if we have the complete name of the exe, we can try at least matching Lutris etc as well. Only caveat I can think of is that it could not be used to make the first backup.
I have a proposition to add to wrap's functionality: using a parameter, let me specify if I want to restore or backup?
That sounds fine to me :) I'd be open to --no-backup
and --no-restore
.
Second idea: add more options to
--infer
. I would think that if we have the complete name of the exe, we can try at least matching Lutris etc as well.
This one's a bit tricky. How do we know which argument is the game exe? We might have cases with wrapper scripts like mangohud ./Celeste
(where mangohud
and Celeste
are both executables), or cases where just the game name is passed like legendary launch Celeste
(where only legendary
is an executable).
That said, maybe Lutris sets some environment variables that we could check, or they might be open to adding some (like I did for Heroic).
This one's a bit tricky. How do we know which argument is the game exe? We might have cases with wrapper scripts like
mangohud ./Celeste
(wheremangohud
andCeleste
are both executables), or cases where just the game name is passed likelegendary launch Celeste
(where onlylegendary
is an executable).That said, maybe Lutris sets some environment variables that we could check, or they might be open to adding some (like I did for Heroic).
It is indeed tricky, but I was just thining of Lutris in this case. Lutris has a setting where you can set a command prefix, that is where you'd set ludusavi wrap --infer Lutris
. So in this case infer would be "Assume you are wrapped around a game being launched with Lutris".
But investigating the env vars it sets is also a good idea.
EDIT: I could ofcourse also have a look at what it would take to make an official integration between lutris and ludusavi, but I'm not a Python dev :) I think if they could just set some env vars that'd be specific to ludusavi, that could be enough?
@kekonn I've inquired about adding some environment variables here: https://github.com/lutris/lutris/issues/5407
@kekonn I've pushed a branch called feature/wrap-infer-lutris
that uses the environment variables discussed in that ticket. Could you test it out? The game_name
and WINEPREFIX
variables should already be set in the current version of Lutris. The code will check for the proposed GAME_DIRECTORY
variable as well, but it should still work even if that one's not present (just won't check <base>
paths).
I might have some time tonight or tomorrow evening (CEST).
@mtkennerly good news and bad news: it is able to pick up the game, but I can't set the backup location this way and so it is looking in the wrong place 😅 This shouldn't be a problem when not running from a target folder in a branch though, no?
[2024-04-09T20:21:34.780Z] DEBUG [ludusavi::cli] Title finder result: Some("Horizon Forbidden West")
[2024-04-09T20:21:34.780Z] ERROR [ludusavi::scan::layout] Unable to load mapping: StrictPath { raw: "/home/kekkon/ludusavi-backup/Horizon Forbidden West/mapping.yaml", basis: None } | "File does not exist"
[2024-04-09T20:21:34.781Z] DEBUG [ludusavi::cli::ui] Showing confirmation to user (GUI=true, force=None): This game does not have a backup to restore.
Incidentally the line breaks added in the error dialog are not translated:
it is able to pick up the game
Nice 🎉
I can't set the backup location this way and so it is looking in the wrong place 😅 This shouldn't be a problem when not running from a target folder in a branch though, no?
If you normally run Ludusavi via Flatpak, then there might be an issue with $XDG_DATA_HOME
not pointing to your normal config when you run it standalone. If so, you could try passing --config ~/.var/app/com.github.mtkennerly.ludusavi/config/ludusavi
. It could also be that Lutris is setting a different value for $XDG_DATA_HOME
.
Incidentally the line breaks added in the error dialog are not translated:
Oh, that's weird. It looks like it's coming from the native-dialog crate when it invokes kdialog. Thanks for letting me know.
What version of kdialog do you have?
it is able to pick up the game
Nice 🎉
I can't set the backup location this way and so it is looking in the wrong place 😅 This shouldn't be a problem when not running from a target folder in a branch though, no?
If you normally run Ludusavi via Flatpak, then there might be an issue with
$XDG_DATA_HOME
not pointing to your normal config when you run it standalone. If so, you could try passing--config ~/.var/app/com.github.mtkennerly.ludusavi/config/ludusavi
. It could also be that Lutris is setting a different value for$XDG_DATA_HOME
.
I think this is because I run ludusavi from the flatpak, but I run lutris from the system. Guess I should start thinking about packaging ludusavi for OpenSUSE :sweat_smile: . Adding --config did the trick though, so barring the little formatting bug with KDialog, this is just fine for me.
Incidentally the line breaks added in the error dialog are not translated:
Oh, that's weird. It looks like it's coming from the native-dialog crate when it invokes kdialog. Thanks for letting me know.
What version of kdialog do you have?
kdialog --version returns 24.02.1. I am currently on OpenSUSE Tumbleweed.
Found a ticket for the formatting issue (https://github.com/native-dialog-rs/native-dialog-rs/issues/41) and added a comment with some more info. It looks like downgrading the native-dialog version might help, although I'm not sure what else changed between the versions.
Just to confirm, it worked when I set the $XDG_DATA_HOME. I know you're busy, but any idea when this would hit main? Using infer I could simply add ludusavi to my standard Lutris Wine settings and not have to do game per game settings.
@kekonn I've merged the --infer lutris
changes to master already. I'd like to make a new release within a week or two as well, although I'm still figuring out what else to include.
So there are a few solutions here that work today and get pretty close to this functionality:
Given the technical challenges with detecting game events without hooking into a launcher, and given that the above options are probably "good enough" for most cases, I think I'll refrain from implementing a process monitor in Ludusavi.
What's your idea?
This would require some research to see if we can do it accurately enough. The manifest
launch
section has executable paths/names, which may be useful for this. Some potential options:My first choice would probably be by process path, as long as it ends up being accurate enough, since it would be one implementation that could work generically.