Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatic backup after playing a game #211

Closed
mtkennerly opened this issue May 21, 2023 · 26 comments
Closed

Automatic backup after playing a game #211

mtkennerly opened this issue May 21, 2023 · 26 comments
Labels
enhancement New feature or request

Comments

@mtkennerly
Copy link
Owner

mtkennerly commented May 21, 2023

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:

  • Watch for certain process names. That's easier on Windows since the process name just matches the executable name, but process names are unpredictable on Linux. This probably isn't feasible since it would require a lot of manual additions to the manifest. Game Backup Monitor uses this approach.
  • Watch for certain process paths. This would be more reliable since we have the executable paths in the manifest. However, a process can change its path while running, and launchers/wrapper scripts may interfere with this as well.
  • The most robust approach would be to directly query Steam and other launchers to see if they're running a game. This would depend on the launchers exposing that information, but if they do, then it would probably yield the best results.

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.

@mtkennerly mtkennerly added the enhancement New feature or request label May 21, 2023
@lucasfabre
Copy link

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:

  • It allow to check if the files are in sync with the remote before starting the game, and warn the user or ask the user to resolve a conflict (with a small window for example).
  • It allow to upload the game files just after the game is closed (and maybe display a small uploading window in steamDeck gamemode).
  • It is possible to upload something like a lockfile to warn the user before starting the game again if the saves file was not pushed to the remote.
  • No daemon is required, this allow for a simpler setup (in gamemode for example).

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.

@PPORCH3bis
Copy link

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.

@sluedecke
Copy link
Contributor

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.

@sluedecke
Copy link
Contributor

sluedecke commented Jun 19, 2023

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

sluedecke added a commit to sluedecke/ludusavi that referenced this issue Jun 23, 2023
…a game

Reflects the core function of ludusavi-launcher.sh minus the UI message and
logging.  Please consider this a proof of concept.
@sluedecke
Copy link
Contributor

sluedecke commented Jun 23, 2023

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

@mtkennerly
Copy link
Owner Author

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:

  • Looking at the code for gogdl, while it's true that 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.
  • Instead of parsing the 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.
  • I'm not sure yet, but I think I would prefer the command name wrap.
  • The -- 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.

@sluedecke
Copy link
Contributor

Thanks for the feedback!

  1. Depending gogdl positional parameters is error prone, since it is an heroic internal project and can change without notice :( Still, according to https://github.com/Heroic-Games-Launcher/heroic-gogdl/blob/d7f29dfef5818e8b323d04761e18a9abb750f93e/gogdl/args.py#L90 the order is "fixed"
  2. moving away from info/goggame: definitely worth looking into it
  3. done
  4. I fully agree!

Recent commit is about:

  • renamed launcher to wrap
  • refactored some code into new module wrap

@sluedecke
Copy link
Contributor

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

@mtkennerly
Copy link
Owner Author

Ah, I guess Clap can't mark the name as "positional, but only if it comes before --". That looks fine then 👍

Feel free to open a draft PR, and we can move the implementation discussion there.

@seniorm0ment
Copy link

This would be beneficial alongside #90

@nioncode
Copy link

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.

@sluedecke
Copy link
Contributor

After merging #235, is this still an open issue?

@mtkennerly
Copy link
Owner Author

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.

@kekonn
Copy link
Contributor

kekonn commented Apr 3, 2024

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.

@mtkennerly
Copy link
Owner Author

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

@kekonn
Copy link
Contributor

kekonn commented Apr 4, 2024

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

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?

@mtkennerly
Copy link
Owner Author

@kekonn I've inquired about adding some environment variables here: lutris/lutris#5407

@mtkennerly
Copy link
Owner Author

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

@kekonn
Copy link
Contributor

kekonn commented Apr 9, 2024

I might have some time tonight or tomorrow evening (CEST).

@kekonn
Copy link
Contributor

kekonn commented Apr 9, 2024

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

@mtkennerly
Copy link
Owner Author

mtkennerly commented Apr 9, 2024

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?

@kekonn
Copy link
Contributor

kekonn commented Apr 10, 2024

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 😅 . 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.

@mtkennerly
Copy link
Owner Author

Found a ticket for the formatting issue (native-dialog-rs/native-dialog-rs#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.

@kekonn
Copy link
Contributor

kekonn commented Apr 16, 2024

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.

@mtkennerly
Copy link
Owner Author

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

@mtkennerly
Copy link
Owner Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

7 participants