Repository: lutris/lutris

Issue:
Pre-launch/post-exit environment variables for wrapper script to detect game
For context, I'm the author of [Ludusavi](https://github.com/mtkennerly/ludusavi). It has a feature to be used as a wrapper to back up your save data, like `ludusavi wrap --name "Game Title" -- ./game --foo`. To avoid writing out the title, it also supports `--infer [steam|heroic]`, which will check for some environment variables in order to figure out which game is running.

I'd like to add support for `--infer lutris`, and I was wondering if you'd be open to adding some environment variables to support that. It would just need enough information so it can uniquely identify the game in the database and/or YAML files. Would `LUTRIS_GAME_SLUG` (table: `games`, column: `slug`) be sufficient, or would `LUTRIS_GAME_ID` (table: `games`, column: `id`) be better?

These were the closest I could find, but I don't think they cover this use case:

https://github.com/lutris/lutris/blob/3866cf21bd522b546a6f3f75653cf82dd3156c46/lutris/game.py#L689-L691

https://github.com/lutris/lutris/blob/3866cf21bd522b546a6f3f75653cf82dd3156c46/lutris/monitored_command.py#L133



Discussion:
I do not think either of those will do. ``GAMEID`` is an Umu thing, not a Lutris ID. ``LUTRIS_GAME_UUID`` is a identifier of a single game execution, used to find processes to kill when you click 'Stop'.

I think you would want something like ``LUTRIS_GAME_ID``, which would be the 'id' from the games table. That is the id that shows up in the output of ``lutris --list-games --json``, and that gives the game's directory, name, slug, etc.

But I'm not sure this is right. I don't know what data you need to infer, and if it is in the json it might be nice to get it from just one game, not a dump of all of them. Or maybe just get what you need in an env-var- that'd be less likely to go wrong I think.

Anyway, 'id' is the unique ID for that table, and the slug is not unique. You can absolutely have two rows with the same slug; that might be two configurations of the same underlying game.
Thanks for the quick reply :)

> But I'm not sure this is right. I don't know what data you need to infer

Ludusavi needs:

* Title (we try to match this to an entry in Ludusavi's data set)
* Install folder (to look for save data)
* Wine prefix folder (to look for save data)

If wrapper scripts could have direct access to those pieces of information via environment variables, that would be the simplest solution for my use case.

> That is the id that shows up in the output of `lutris --list-games --json`, and that gives the game's directory, name, slug, etc.

Right now, Ludusavi collects the info it needs from the YAML files, which lets it support some unusual cases like scanning a disk backup from another system, where the system running Ludusavi may not actually have Lutris.

It could certainly use the Lutris CLI in wrap mode, but there might be some complications with that:

* If Ludusavi is installed via Flatpak but Lutris is a standalone copy, then `$XDG_DATA_HOME` won't have the user's normal Lutris config.
* If you have multiple Lutris config folders (maybe another system's backup or because you're testing a snapshot build), Ludusavi would need to know which Lutris instance is active during wrap mode.
We've reached a point where cloud saves is the single most important feature left to implement in Lutris. While there is some preliminary code for handling saves in Lutris, it is very minimal and not usable yet. 

Anyway, the plan is to support multiple backends to handle saves:
- Integration with Lutris.net (saves are uploaded to the website, feature for Lutris Patreons)
- Nextcloud App
- Local filesystem
- Managed by something else (Ludusavi, etc)

The Lutris API will make available some data on where to find saves. Like everywhere else in the project, we use the game slug as the identifier. We don't use the name ever because of games like Doom and Tomb Raider.

I would suggest against using pre-launch and post-exit scripts. I've never used those and don't plan to, pre-launch / post-exit is one of the most untested / here be dragons parts of Lutris. It is mostly used by people who don't want to make a pull request to add missing functionality to the client. 

There will be (there already is, actually) parts of the code that handles game save sync. We hand over any data needed by Ludusavi at this stage. We could even send the location of the save files when we have that in our API.

Anyway, this is the perfect time to start working on this since it will be the focus of Lutris 0.5.18. 

In any case the "id" field of the sqlite database should never be used and the LUTRIS_GAME_UUID even less, since it only exists for the duration of a single game launch.


I've used pre/post launch scripts, though for compatibility not integration. Some games have weird requirements to get them to work. Sometimes you just have to joust with the dragons.

I'm prepping a PR to help these scripts, but I'll leave out the ID if you don't want it used. WINEPREFIX seems to be there already, right enough.

The name is also provided in the env-var "game_name", which is an odd name for an env-var, and it has a comment ``# What is this used for??``- but that was there from the commit that introduced it. Weird. I'm tempted to rename it.

That leaves the game directory. I'll add GAME_DIRECTORY.

I suspect it is too late for this to make 0.5.17, though.

Resolving patch:
diff --git a/lutris/game.py b/lutris/game.py
--- a/lutris/game.py
+++ b/lutris/game.py
@@ -666,15 +666,20 @@ def configure_game(self, launch_ui_delegate):
         This method sets the game_runtime_config attribute.
         """
         gameplay_info = self.get_gameplay_info(launch_ui_delegate)
-        if not gameplay_info:  # if user cancelled- not an error
+        if not gameplay_info:  # if user cancelled - not an error
             return False
         command, env = get_launch_parameters(self.runner, gameplay_info)
 
         if env.get("WINEARCH") == "win32" and "umu" in " ".join(command):
             raise RuntimeError("Proton is not compatible with 32bit prefixes")
-        env["game_name"] = self.name  # What is this used for??
         env["GAMEID"] = proton.get_game_id(self)
         env["STORE"] = self.get_store_name()
+
+        # Some environment variables for the use of custom pre-launch and post-exit scripts.
+        env["game_name"] = self.name
+        if self.directory:
+            env["GAME_DIRECTORY"] = self.directory
+
         self.game_runtime_config = {
             "args": command,
             "env": env,


Test patch: