From 69a50bf7b7e72ebaf6630fceefd8ef781b117dfd Mon Sep 17 00:00:00 2001 From: tcsenpai Date: Sun, 25 Feb 2024 22:00:47 +0100 Subject: [PATCH] Merged UWINE into the repo --- .gitignore | 8 +- PREFIX/this_folder_will_be_the_wine_prefix | 0 README.md | 346 +++++ env.example | 13 + ids.json | 3 + launcher/README.md | 139 --- launcher/ULWGL-Runner/compatibilitytool.vdf | 13 - launcher/ULWGL-Runner/toolmanifest.vdf | 8 - launcher/ULWGL-Runner/ulwgl-run | 1 - launcher/mtree.txt.gz | Bin 5495 -> 0 bytes .../org.openwinecomponents.ulwgl.launcher.yml | 183 --- launcher/ruff.toml | 77 -- launcher/run-in-sniper | 23 - launcher/ulwgl-run | 1 - launcher/ulwgl-run-cli | 72 -- launcher/ulwgl-run-posix | 108 -- launcher/ulwgl_consts.py | 26 - launcher/ulwgl_dl_util.py | 261 ---- launcher/ulwgl_log.py | 13 - launcher/ulwgl_plugins.py | 129 -- launcher/ulwgl_run.py | 315 ----- launcher/ulwgl_test.py | 1111 ----------------- launcher/ulwgl_test_plugins.py | 533 -------- launcher/ulwgl_util.py | 20 - libs/customvars_loader.py | 22 + libs/filepath_loader.py | 41 + libs/gameid_loader.py | 33 + libs/ids_loader.py | 33 + libs/mustExist.py | 36 + libs/postdirectives_loader.py | 22 + libs/predirectives_loader.py | 22 + libs/protonpath_loader.py | 36 + libs/ulwgl_loader.py | 30 + libs/ulwlg_runner.py | 42 + libs/wineprefix_loader.py | 30 + .../this_folder_will_contain_proton_instances | 0 requirements.txt | 2 + screenshot.png | Bin 0 -> 157620 bytes uwine | 126 ++ uwine.desktop | 10 + uwine_launcher.desktop | 10 + 41 files changed, 864 insertions(+), 3034 deletions(-) create mode 100644 PREFIX/this_folder_will_be_the_wine_prefix create mode 100755 README.md create mode 100644 env.example create mode 100644 ids.json delete mode 100755 launcher/README.md delete mode 100644 launcher/ULWGL-Runner/compatibilitytool.vdf delete mode 100644 launcher/ULWGL-Runner/toolmanifest.vdf delete mode 120000 launcher/ULWGL-Runner/ulwgl-run delete mode 100755 launcher/mtree.txt.gz delete mode 100644 launcher/org.openwinecomponents.ulwgl.launcher.yml delete mode 100644 launcher/ruff.toml delete mode 100755 launcher/run-in-sniper delete mode 120000 launcher/ulwgl-run delete mode 100755 launcher/ulwgl-run-cli delete mode 100755 launcher/ulwgl-run-posix delete mode 100644 launcher/ulwgl_consts.py delete mode 100644 launcher/ulwgl_dl_util.py delete mode 100644 launcher/ulwgl_log.py delete mode 100644 launcher/ulwgl_plugins.py delete mode 100755 launcher/ulwgl_run.py delete mode 100644 launcher/ulwgl_test.py delete mode 100644 launcher/ulwgl_test_plugins.py delete mode 100644 launcher/ulwgl_util.py create mode 100644 libs/customvars_loader.py create mode 100644 libs/filepath_loader.py create mode 100644 libs/gameid_loader.py create mode 100644 libs/ids_loader.py create mode 100644 libs/mustExist.py create mode 100644 libs/postdirectives_loader.py create mode 100644 libs/predirectives_loader.py create mode 100644 libs/protonpath_loader.py create mode 100644 libs/ulwgl_loader.py create mode 100644 libs/ulwlg_runner.py create mode 100644 libs/wineprefix_loader.py create mode 100644 protons/this_folder_will_contain_proton_instances create mode 100644 requirements.txt create mode 100644 screenshot.png create mode 100755 uwine create mode 100644 uwine.desktop create mode 100644 uwine_launcher.desktop diff --git a/.gitignore b/.gitignore index 0362a8a..c6900a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,7 @@ -var \ No newline at end of file +launcher +.git +.env +__pycache__ +test_launcher +var +_not_needed_ \ No newline at end of file diff --git a/PREFIX/this_folder_will_be_the_wine_prefix b/PREFIX/this_folder_will_be_the_wine_prefix new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100755 index 0000000..ace0512 --- /dev/null +++ b/README.md @@ -0,0 +1,346 @@ +# ULWGL-LAUNCHER-TCS ( ⾣ UWINE ) + +The final wrapper for ULWGL (and its launcher) has come to town. + +Play and run Windows software with Proton without Steam in a (maybe a couple of) click. + +![Hello world](https://raw.githubusercontent.com/tcsenpai/UWINE/main/screenshot.png) + +## What is UWINE + +UWINE is a wrapper around ULWGL designed to help as many users as possible to run WIndows software (included but not limited to games) using Proton (and its flavors like ProtonGE) without Steam client needed. + +## Credits and License + +This software is distributed under the MIT license. + +The original ULWGL-Launcher repository and its creators (https://github.com/Open-Wine-Components/ULWGL-launcher) are to be credited for all the amazing work they are doing. + +This is just an humble wrapper designed for the following reason: + +- I am lazy and I don't want to tinker with the terminal each time I want to play a game + +- Many people are lazy + +- Many others are non tech-savy + +## ⭐ Features + +- Few dependencies and great flexibility thanks to environmental variables + +- Slightly modified ULWGL_Launcher to have a cleaner start + +- Can run programs as simply as "uwine program.exe" or "uwine winecfg" for internal tools + +- Can be used to "Open with..." + +- The Launcher provided can be used to "Open with..." too + +- Is fully customizable through both a human readable CLI interface and a practical format for `env` files + +- Can execute _launchers_ by using the above `env` files mechanism with "uwine -l launcher_of_game" + +- Can be configured through both the `env` file and the CLI interface together (with the CLI interface overriding the `env` file) + +- Is modular and so is readable + +- Can be expanded easily + +- I use it daily + +- Is quite nice + +## Install + +### ⚙ Prerequisites + +- Download the last ULWGL Launcher binary release + + - _NOTE: this software has been tested with 0.1 RC3 release in mind. You may want to download that version if you encounter problems with different versions._ + +- Extract it somewhere and ensure that it contains the executables (ULWGL specifically) + + - NOTE: ulwgl-run is not needed, as the script uses its own runner + + - Not needed of my machine but: ensure to read the README.md to understand ULWGL Launcher prerequisites + +### ⏬ Get UWINE + +#### ⚙ Download and Configuration + +- `git clone https://github.com/thecookingsenpai/UWINE` + +- `cd UWINE` + +- Now take a moment to have a look at the .env file. It contains some useful tips. You can add custom paths to it, for example, Steam usually install its instances in subfolders of `$HOME/.steam/steam/compatibilitytools.d` + +- The .env file works as a launcher that helps you run your programs in the easiest way possible. While is not really needed (see below), the provided example shows how UWINE can run with almost 0 configuration + +#### 🕮 A brief explanation: skip if you can't be bothered + +It is important to understand how UWINE works. The 'uwine' executable has to fill the following variables: + +- PROTONPATH + + - string pointing to your custom Proton installation or to the Steam one (see above) + - defaults to [script_dir]/protons + +- WINEPREFIX + + - string pointing to a custom prefix for wine (aka where savegames and programs are installed) + - defaults to [script_dir]/PREFIX and creates a new one if needed + +- GAMEID + + - integer representing the GameID in the ULWLG Database + - defaults to 0 which should work for the majority of the apps + +- IDS + + - string pointing to a .json file containing mappings of game names to game ids + - defaults to [script_dir]/ids.json + - if it doesn't exist, the mapping will be empty + +- ULWLGDIR + + - string pointing to your ULWLGDIR installation + - defaults to [script_dir]/launcher which is perfectly fine if extract ULWLG_Launcher there (not in a subfoldr) + +- FILEPATH + + - string pointing to the file to execute + - if not specified, it must be specified from the CLI (see below) + +- CUSTOMVARS + + - dictionary-like string (JSON compatible) defining any environmental variable you want to set + - can be omitted if you don't need it + +- PREDIRECTIVES + + - string containing parameters and arguments to be *prepended* to the command (e.g. gamescope) + - defaults to "" + +- POSTDIRECTIVES + + - string containing parameters and arguments to be *appended* to the command (e.g. -dx11) + - defaults to "" + - can be set using the `-a` flag + +**_TIP: You should consider using ProtonUp (or ProtonUp-QT for KDE users) to help you easily install Proton instances in the 'protons' folder or wherever you like the most_** + +#### 🧧 Bonus: CUSTOMVARS + +While adding custom environmental variables both in your shell or in the env file is supported, UWINE has a mechanism that is designed to help you in setting custom system variables. + +By editing the `env` file you want to use, you can add: + +```json + CUSTOMVARS="{ + "YOUR_VAR":"YOUR_VALUE" + }" +``` + +The above syntax allows UWINE to present you in a nicer and more organized way your final command. + +### 🚀 Launch + +Once launched, UWINE will quickly sets the above variables based on either the `.env` file (or the one provided) or/and the command line arguments + +After some basic checks (like if the file exists, if PROTONPATH is set...), UWINE will look for the `ulwgl-run` binary and use it to launch the program with the above mentioned variables. + +Thats why the next section is really important. + +## Command Line Arguments & Launchers + +The preferred way to use UWINE is by creating `env` files following the above format and then just run `uwine` to load them. + +### Load the .env file + +`uwine` + +This command will try to load the `.env` file in the script directory. + +### Load a custom env file + +`uwine -l [your_env_file]` + +This command will try to load the env file specified. + +### Specify your variables (or mix the two) + +`uwine -h` + +This command will show you the following: + +```bash +usage: uwine [-h] [-l ENVFILE] [-g GAMEID] [-p PROTONPATH] [-i IDS] [-w WINEPREFIX] [-u ULWLGDIR] [-a ADDITIONALARGS] [-v] [filepath] + +ULWGL Launcher Wrapper for human beings + +positional arguments: + filepath Path to the file to be launched + +options: + -h, --help show this help message and exit + -l ENVFILE, --load ENVFILE + Load a specific env file + -g GAMEID, --game-id GAMEID + Game ID to be used + -p PROTONPATH, --proton-path PROTONPATH + Path to the Proton installation + -i IDS, --ids-json IDS + Path to the ids.json file + -w WINEPREFIX, --wine-prefix WINEPREFIX + Path to the Wine prefix + -u ULWLGDIR, --ulwgl ULWLGDIR + Path to the ULWGL installation + -a ADDITIONALARGS, --additionalargs ADDITIONALARGS + Additional arguments to be passed to the software (as a string) + -v, --version show program's version number and exit + +``` + +The help file is self explanatory. + +For example, a possible usage is: + +`uwine winecfg -u "/your/hdd/data/ulwgl" -w "/your/hdd/data/ulwgl/PREFIX/" -p "/your/hdd/data/ulwgl/protons/GE-Proton8-32/"` + +This will launch the built-in `winecfg` program using the specified variables. + +Another example could be: + +`uwine -l mygame` + +Where `mygame` must be a valid env file as described above. + +#### About ADDITIONALARGS + +It is often necessary to add additional arguments to your programs (e.g. `-dx11`) to modify their behavior. To help in this, you can simply use the `-a` option specifying a string that will be appended to your launched program. + +For example, to launch Palworld with `-dx11`: + +`uwine Palworld.exe -a "-dx11"` + +Multiple arguments can be chained together in the string such as: + +`uwine game.exe -a "-dx11 --skip-launcher"` + +In following updates this feature will be also available in the `env` file. + +### Optional: the ids.json file + +As the excellent tutorial on the ULWGL Launcher repository page explains, ULWGL aims to build a complete, open database of games with the relative protonfixes that should be applied automatically by the launcher itself. + +As UWINE's intent is to simplify things, you can skip this part and us the launcher with the default game id `0`. Anyway, experimental game id support is included. + +At the moment the `ids.json` contains just an example of how it works. + +`{ "gamename": gameid }` + +The syntax is simple as the above example. Baldur's Gate 3 has been included as an example. Launching UWINE with: + +`uwine game.exe bg3` + +Will instruct UWINE to look for "bg3" in the ids.json file. If not found, it will default to `0`. You can also do: + +`uwine game.exe 12345` + +Where 12345 is a custom game id that you can either invent (but why) or look up on the ULWGL database (https://ulwgl.openwinecomponents.org/). + +Howeaver, this is considered quite experimental for both the projects. + +### Optional: add this directory to $PATH + +To quickly use UWINE, is recommended to add the directory you are using (the ULWGL Launcher directory containing uwine) to the $PATH variable. + +You can add this: + +`PATH="$PATH:/your/path/to/the/ULWGL/launcher/directory"` + +To either your `.profile`, `.bashrc` or equivalent configuration file. + +### ??? Profit + +Done! You should be able to run + +`uwine any_program.exe` from anywhere. + +### ✋ Handy stuff: Open With + +This repository offers also a preconfigured launcher (`uwine.desktop`) that should work on every modern linux DM/WM. You can put it anywhere (e.g. in your applications menu) and you can configure .exe and .msi (and any other) files to "Open with..." uwine. + +You can also set `.uwine` files to open with `uwine -l ` or use the given launcher (`uwine_launcher.desktop`) to launch env files. + +If this does not work, you can simply "Open with..." and add a custom command called 'uwine' / 'uwine -l' that launches in the terminal. + +### ✋ Handy stuff 2: AIO Package + +An All-in-One package is on the way and contains the official ULWGL release tested with uwine and uwine installed in it. + +Is not as funny but it should be even more straightforward. + +## Issues, bugs, dragons + +This is even more experimental than ULWGL itself. Bugs are to be expected. + +Please open issues or pr if you encounter some. + +Before doing that, though, please ensure that the problem is with UWINE and not with ULWGL or Proton. + +For example: some Steam games may fail to launch complaining about the Steam Client not being found. That's an ULWGL / Proton problem. Surely is not an UWINE problem. Probably. Anyway.... + +### Testing environment and compatibility + +This software has been tested on the following system (a laptop): + +- KUbuntu 23.10 with Plasma on Wayland + +- Python 3.11 + +- Kernel 6.5, Kernel 6.7.4 Xanmod x64v3 and 6.7.5 Xanmod x64v3 + +- ProtonGE-8.32 both in protons local folder and in .steam default folder + +- ProtonUp 2.8.2 + +- CPU AMD 7 7730U (Zen 3) with integrated RADEON Graphics (RADV Renoir) + +- 16GB RAM (14GB + 2GB VRAM) + +- Both _bash_ and _xonsh_ shells + +- Both external and internal SSDs + +## Roadmap + +- Testing, testing, testing + +- Finding bugs, fixing bugs + +- Full support to all ULWGL features + +- Testing again for safety measure + +I will use (as I am doing) UWINE for my daily gaming experience, so I will update it accordingly. Feel free (please) to do the same. + +## Call to test + +We need you! Everybody needs you! Linux itself needs you! + +Yes, you. + +And by that I mean: please stress test and help me improve this software so that we can help the ULWGL team focusing on the important stuff. + +An era of gaming, an era of universal Proton compatibility is coming. + +Be part of it! + +## Disclaimer + +Sorry for any mistake. It wasn't inentional. + +## License + +MIT License. diff --git a/env.example b/env.example new file mode 100644 index 0000000..6451061 --- /dev/null +++ b/env.example @@ -0,0 +1,13 @@ +# You can either specify everything in here (obtainin a sort of launcher) +# or you can specify the variables in the command line. +# You can also specify the variables in a file and source it. +# Anyway there are default values for all the variables, so you can just run +# the script without any arguments and it will show you how to do stuff. +PROTONPATH="/home//uwine/protons/GE-Proton8-32" +WINEPREFIX="/home//uwine/PREFIX" +GAMEID="0" +ULWLGDIR="/home//uwine/launcher" +FILEPATH="regedit" +CUSTOMVARS='{ +"DVXK": "1" +}' diff --git a/ids.json b/ids.json new file mode 100644 index 0000000..c74724a --- /dev/null +++ b/ids.json @@ -0,0 +1,3 @@ +{ + "bg3": "1086940" +} diff --git a/launcher/README.md b/launcher/README.md deleted file mode 100755 index a3a4f82..0000000 --- a/launcher/README.md +++ /dev/null @@ -1,139 +0,0 @@ -# ULWGL -Unified Linux Wine Game Launcher - - -# WHAT IS THIS? - -This is a work in progress POC (proof of concept) for a unified launcher for windows games on linux. It is essentially a copy of the Steam Linux Runtime/Steam Runtime Tools (https://gitlab.steamos.cloud/steamrt/steam-runtime-tools) that Valve uses for proton, with some modifications made so that it can be used outside of Steam. - -# WHAT DOES IT DO? - -When steam launches a proton game, it launches it like this: - -``` -/home/tcrider/.local/share/Steam/ubuntu12_32/reaper SteamLaunch AppId=348550 -- /home/tcrider/.local/share/Steam/ubuntu12_32/steam-launch-wrapper -- /home/tcrider/.local/share/Steam/steamapps/common/SteamLinuxRuntime_sniper/_v2-entry-point --verb=waitforexitandrun -- /home/tcrider/.local/share/Steam/compatibilitytools.d/GE-Proton8-27/proton waitforexitandrun /home/tcrider/.local/share/Steam/steamapps/common/Guilty Gear XX Accent Core Plus R/GGXXACPR_Win.exe -``` - -We can ignore this `/home/tcrider/.local/share/Steam/ubuntu12_32/steam-launch-wrapper`, it's just a process runner with no real value other than forwarding environment variables (more on that later). - -I managed to pull the envvars it uses by making steam run printenv for the games command line. We needed these envvars because proton expects them in order to function. With them we can essentially make proton run without needing steam at all. - -Next this part `/home/tcrider/.local/share/Steam/steamapps/common/SteamLinuxRuntime_sniper/_v2-entry-point` - -The first part `/home/tcrider/.local/share/Steam/steamapps/common/SteamLinuxRuntime_sniper/` is steam-runtime-tools compiled https://gitlab.steamos.cloud/steamrt/steam-runtime-tools and is used alongside the sniper runtime container used during proton builds. - -The second part `_v2-entry-point` is just a bash script which loads proton into the container and runs the game. - -So, ULWGL is basically a copy paste of SteamLinuxRuntime_sniper, which is a compiled version of steam-runtime-tools. We've renamed _v2-entry-point to ULWGL and added `ulwgl-run` to replace steam-launch-wrapper. - -When you use `ulwgl-run` to run a game, it uses the specified WINEPREFIX, proton version, executable, and arguements passed to it to run the game in proton, inside steam's runtime container JUST like if you were running the game through Steam, except now you're no longer limited to Steam's game library or forced to add the game to Steam's library, in fact, you don't even have to have steam installed. - -# HOW DO I USE IT? - -Usage: - - `WINEPREFIX= GAMEID= PROTONPATH= ./ulwgl-run ` - -Ex: - - `WINEPREFIX=$HOME/Games/epic-games-store GAMEID=ulwgl-dauntless PROTONPATH="$HOME/.steam/steam/compatibilitytools.d/GE-Proton8-28" ./ulwgl-run "$HOME/Games/epic-games-store/drive_c/Program Files (x86)/Epic Games/Launcher/Portal/Binaries/Win32/EpicGamesLauncher.exe" "-opengl -SkipBuildPatchPrereq"` - -Optional (used mainly for protonfixes): `STORE` - - `WINEPREFIX=$HOME/Games/epic-games-store GAMEID=ulwgl-dauntless STORE=egs PROTONPATH="$HOME/.steam/steam/compatibilitytools.d/GE-Proton8-28" ./ulwgl-run "$HOME/Games/epic-games-store/drive_c/Program Files (x86)/Epic Games/Launcher/Portal/Binaries/Win32/EpicGamesLauncher.exe" "-opengl -SkipBuildPatchPrereq"` - -# WHAT DOES THIS MEAN FOR OTHER LAUNCHERS (lutris/bottles/heroic/legendary,etc): - -- everyone can use + contribute to the same protonfixes, no more managing individual install scripts per launcher -- everyone can run their games through proton just like a native steam game -- no steam or steam binaries required -- a unified online database of game fixes (protonfixes) - -right now protonfixes packages a folder of 'gamefixes' however it could likely be recoded to pull from online quite easily - -The idea is to get all of these tools using this same `ulwgl-run` and just feeding their envvars into it. That way any changes that need to happen can happen in proton-ge and/or protonfixes, or a 'unified proton' build based off GE, or whatever they want. - -# WHAT IS THE BASIC PLAN OF PUTTING THIS INTO ACTION? - -1. We build a database containing various game titles, their IDs from different stores, and their correlating ULWGL ID. -2. Various launchers then search the database to pull the ULWGL ID, and feed it as the game ID to ulwgl-run alongside the store type, proton version, wine prefix, game executable, and launch arguements. -3. When the game gets launched from ulwgl-run, protonfixes picks up the store type and ULWGL ID and finds the appropriate fix script for it, then applies it before running the game. -4. protonfixes has folders separated for each store type. The ULWGL ID for a game remains the exact same across multiple stores, the only difference being it can have store specific scripts OR it can just symlink to another existing script that already has the fixes it needs. - -Example: - -Borderlands 3 from EGS store. -1. Generally a launcher is going to know which store it is using already, so that is easy enough to determine and feed the STORE variable to the launcher. -2. To determine the game title, EGS has various codenames such as 'Catnip'. The launcher would see "ok store is egs and codename is Catnip, let's search the ULWGL database for those" -3. In our ULWGL unified database, we create a 'title' column, 'store' column, 'codename' column, 'ULWGL-ID' column. We add a line for Borderlands 3 and fill in the details for each column. -4. Now the launcher can search 'Catnip' and 'egs' as the codename and store in the database and correlate it with Borderlands 3 and ULWGL-12345. It can then feed ULWGL-12345 to the ulwgl-run script. - - -README notes from Valve's steam-runtime-tools: - -Steam Linux Runtime 3.0 (sniper) -================================ - -This container-based release of the Steam Runtime is used for native -Linux games, and for Proton 8.0+. - -For general information please see - -and - - -Release notes -------------- - -Please see - - -Known issues ------------- - -Please see - - -Reporting bugs --------------- - -Please see - - -Development and debugging -------------------------- - -The runtime's behaviour can be changed by running the Steam client with -environment variables set. - -`STEAM_LINUX_RUNTIME_LOG=1` will enable logging. Log files appear in -`SteamLinuxRuntime_sniper/var/slr-*.log`, with filenames containing the app ID. -`slr-latest.log` is a symbolic link to whichever one was created most -recently. - -`STEAM_LINUX_RUNTIME_VERBOSE=1` produces more detailed log output, -either to a log file (if `STEAM_LINUX_RUNTIME_LOG=1` is also used) or to -the same place as `steam` output (otherwise). - -`PRESSURE_VESSEL_SHELL=instead` runs an interactive shell in the -container instead of running the game. - -Please see - -for details of assumptions made about the host operating system, and some -advice on debugging the container runtime on new Linux distributions. - -Game developers who are interested in targeting this environment should -check the SDK documentation -and general information for game developers -. - -Licensing and copyright ------------------------ - -The Steam Runtime contains many third-party software packages under -various open-source licenses. - -For full source code, please see the version-numbered subdirectory of - -corresponding to the version numbers listed in VERSIONS.txt. diff --git a/launcher/ULWGL-Runner/compatibilitytool.vdf b/launcher/ULWGL-Runner/compatibilitytool.vdf deleted file mode 100644 index 99d8b8e..0000000 --- a/launcher/ULWGL-Runner/compatibilitytool.vdf +++ /dev/null @@ -1,13 +0,0 @@ -"compatibilitytools" -{ - "compat_tools" - { - "ULWGL-Runner" // Internal name of this tool - { - "install_path" "." - "display_name" "ULWGL-Runner" - "from_oslist" "windows" - "to_oslist" "linux" - } - } -} diff --git a/launcher/ULWGL-Runner/toolmanifest.vdf b/launcher/ULWGL-Runner/toolmanifest.vdf deleted file mode 100644 index 088acbf..0000000 --- a/launcher/ULWGL-Runner/toolmanifest.vdf +++ /dev/null @@ -1,8 +0,0 @@ -// Generated file, do not edit -"manifest" -{ - "commandline" "/ulwgl-run %verb%" - "version" "2" - "use_tool_subprocess_reaper" "1" - "compatmanager_layer_name" "ulwgl-runner" -} diff --git a/launcher/ULWGL-Runner/ulwgl-run b/launcher/ULWGL-Runner/ulwgl-run deleted file mode 120000 index 3a2cd7e..0000000 --- a/launcher/ULWGL-Runner/ulwgl-run +++ /dev/null @@ -1 +0,0 @@ -../../../ULWGL/ulwgl-run \ No newline at end of file diff --git a/launcher/mtree.txt.gz b/launcher/mtree.txt.gz deleted file mode 100755 index 78f6196f0e85e97b6f101780e21f86553041d814..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5495 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 GAMEID= PROTONPATH= ./gamelauncher.sh " - echo "Ex:" - echo "WINEPREFIX=$HOME/Games/epic-games-store GAMEID=egs PROTONPATH=\"$HOME/.steam/steam/compatibilitytools.d/GE-Proton8-28\" ./gamelauncher.sh \"$HOME/Games/epic-games-store/drive_c/Program Files (x86)/Epic Games/Launcher/Portal/Binaries/Win32/EpicGamesLauncher.exe\" \"-opengl -SkipBuildPatchPrereq\"" - exit 1 -fi - -me="$(readlink -f "$0")" -here="${me%/*}" - -if [ "$WINEPREFIX" ]; then - if [ ! -d "$WINEPREFIX" ]; then - mkdir -p "$WINEPREFIX" - export PROTON_DLL_COPY="*" - fi - if [ ! -d "$WINEPREFIX"/pfx ]; then - ln -s "$WINEPREFIX" "$WINEPREFIX"/pfx > /dev/null 2>&1 - fi - if [ ! -f "$WINEPREFIX"/tracked_files ]; then - touch "$WINEPREFIX"/tracked_files - fi - if [ ! -f "$WINEPREFIX/dosdevices/" ]; then - mkdir -p "$WINEPREFIX"/dosdevices - ln -s "../drive_c" "$WINEPREFIX/dosdevices/c:" > /dev/null 2>&1 - fi -fi - -if [ -n "$PROTONPATH" ]; then - if [ ! -d "$PROTONPATH" ]; then - echo "ERROR: $PROTONPATH is invalid, aborting!" - exit 1 - fi -fi - -export ULWGL_ID="$GAMEID" -export STEAM_COMPAT_APP_ID="0" -numcheck='^[0-9]+$' -if echo "$ULWGL_ID" | cut -d "-" -f 2 | grep -Eq "$numcheck"; then - STEAM_COMPAT_APP_ID=$(echo "$ULWGL_ID" | cut -d "-" -f 2) - export STEAM_COMPAT_APP_ID -fi -export SteamAppId="$STEAM_COMPAT_APP_ID" -export SteamGameId="$STEAM_COMPAT_APP_ID" - -# TODO: Ideally this should be the main game install path, which is often, but not always the path of the game's executable. -if [ -z "$STEAM_COMPAT_INSTALL_PATH" ]; then - exepath="$(readlink -f "$1")" - gameinstallpath="${exepath%/*}" - export STEAM_COMPAT_INSTALL_PATH="$gameinstallpath" -fi - -compat_lib_path=$(findmnt -T "$STEAM_COMPAT_INSTALL_PATH" | tail -n 1 | awk '{ print $1 }') -if [ "$compat_lib_path" != "/" ]; then - export STEAM_COMPAT_LIBRARY_PATHS="${STEAM_COMPAT_LIBRARY_PATHS:+"${STEAM_COMPAT_LIBRARY_PATHS}:"}$compat_lib_path" -fi - -if [ -z "$STEAM_RUNTIME_LIBRARY_PATH" ]; then - # The following info taken from steam ~/.local/share/ubuntu12_32/steam-runtime/run.sh - host_library_paths= - exit_status=0 - ldconfig_output=$(/sbin/ldconfig -XNv 2> /dev/null; exit $?) || exit_status=$? - if [ $exit_status != 0 ]; then - echo "Warning: An unexpected error occurred while executing \"/sbin/ldconfig -XNv\", the exit status was $exit_status" - fi - - while read -r line; do - # If line starts with a leading / and contains :, it's a new path prefix - case "$line" in /*:*) - library_path_prefix=$(echo "$line" | cut -d: -f1) - host_library_paths=$host_library_paths$library_path_prefix: - esac - done < Union[Dict[str, str]]: - """Attempt to find existing Proton from the system or downloads the latest if PROTONPATH is not set. - - Only fetches the latest if not first found in .local/share/Steam/compatibilitytools.d - .cache/ULWGL is referenced for the latest then as fallback - """ - files: List[Tuple[str, str]] = [] - - try: - files = _fetch_releases() - except HTTPException: - print("Offline.\nContinuing ...", file=stderr) - - cache: Path = Path.home().joinpath(".cache/ULWGL") - steam_compat: Path = Path.home().joinpath(".local/share/Steam/compatibilitytools.d") - - cache.mkdir(exist_ok=True, parents=True) - steam_compat.mkdir(exist_ok=True, parents=True) - - # Prioritize the Steam compat - if _get_from_steamcompat(env, steam_compat, cache, files): - return env - - # Use the latest Proton in the cache if it exists - if _get_from_cache(env, steam_compat, cache, files, True): - return env - - # Download the latest if Proton is not in Steam compat - # If the digests mismatched, refer to the cache in the next block - if _get_latest(env, steam_compat, cache, files): - return env - - # Refer to an old version previously downloaded - # Reached on digest mismatch, user interrupt or download failure/no internet - if _get_from_cache(env, steam_compat, cache, files, False): - return env - - # No internet and cache/compat tool is empty, just return and raise an exception from the caller - return env - - -def _fetch_releases() -> List[Tuple[str, str]]: - """Fetch the latest releases from the Github API.""" - files: List[Tuple[str, str]] = [] - resp: HTTPResponse = None - conn: HTTPConnection = HTTPSConnection( - "api.github.com", timeout=30, context=create_default_context() - ) - - conn.request( - "GET", - "/repos/Open-Wine-Components/ULWGL-Proton/releases", - headers={ - "Accept": "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - "User-Agent": "", - }, - ) - - resp = conn.getresponse() - - if resp and resp.status != 200: - return files - - # Attempt to acquire the tarball and checksum from the JSON data - releases: List[Dict[str, Any]] = loads_json(resp.read().decode("utf-8")) - for release in releases: - if "assets" in release: - assets: List[Dict[str, Any]] = release["assets"] - - for asset in assets: - if ( - "name" in asset - and ( - asset["name"].endswith("sum") - or ( - asset["name"].endswith("tar.gz") - and asset["name"].startswith("ULWGL-Proton") - ) - ) - and "browser_download_url" in asset - ): - if asset["name"].endswith("sum"): - files.append((asset["name"], asset["browser_download_url"])) - else: - files.append((asset["name"], asset["browser_download_url"])) - - if len(files) == 2: - break - break - conn.close() - - return files - - -def _fetch_proton( - env: Dict[str, str], steam_compat: Path, cache: Path, files: List[Tuple[str, str]] -) -> Dict[str, str]: - """Download the latest ULWGL-Proton and set it as PROTONPATH.""" - hash, hash_url = files[0] - proton, proton_url = files[1] - proton_dir: str = proton[: proton.find(".tar.gz")] # Proton dir - - # TODO: Parallelize this - print(f"Downloading {hash} ...", file=stderr) - urlretrieve(hash_url, cache.joinpath(hash).as_posix()) - print(f"Downloading {proton} ...", file=stderr) - urlretrieve(proton_url, cache.joinpath(proton).as_posix()) - - print("Completed.", file=stderr) - - with cache.joinpath(proton).open(mode="rb") as file: - if ( - sha512(file.read()).hexdigest() - != cache.joinpath(hash).read_text().split(" ")[0] - ): - err: str = "Digests mismatched.\nFalling back to cache ..." - raise ValueError(err) - print(f"{proton}: SHA512 is OK", file=stderr) - - _extract_dir(cache.joinpath(proton), steam_compat) - environ["PROTONPATH"] = steam_compat.joinpath(proton_dir).as_posix() - env["PROTONPATH"] = environ["PROTONPATH"] - - return env - - -def _extract_dir(proton: Path, steam_compat: Path) -> None: - """Extract from the cache to another location.""" - with tar_open(proton.as_posix(), "r:gz") as tar: - print(f"Extracting {proton} -> {steam_compat.as_posix()} ...", file=stderr) - tar.extractall(path=steam_compat.as_posix()) - print("Completed.", file=stderr) - - -def _cleanup(tarball: str, proton: str, cache: Path, steam_compat: Path) -> None: - """Remove files that may have been left in an incomplete state to avoid corruption. - - We want to do this when a download for a new release is interrupted - """ - print("Keyboard Interrupt.\nCleaning ...", file=stderr) - - if cache.joinpath(tarball).is_file(): - print(f"Purging {tarball} in {cache} ...", file=stderr) - cache.joinpath(tarball).unlink() - if steam_compat.joinpath(proton).is_dir(): - print(f"Purging {proton} in {steam_compat} ...", file=stderr) - rmtree(steam_compat.joinpath(proton).as_posix()) - - -def _get_from_steamcompat( - env: Dict[str, str], steam_compat: Path, cache: Path, files: List[Tuple[str, str]] -) -> Union[Dict[str, str], None]: - """Refer to Steam compat folder for any existing Proton directories.""" - proton_dir: str = "" # Latest Proton - - if len(files) == 2: - proton_dir: str = files[1][0][: files[1][0].find(".tar.gz")] - - for proton in steam_compat.glob("ULWGL-Proton*"): - print(f"{proton.name} found in: {steam_compat.as_posix()}", file=stderr) - environ["PROTONPATH"] = proton.as_posix() - env["PROTONPATH"] = environ["PROTONPATH"] - - # Notify the user that they're not using the latest - if proton_dir and proton.name != proton_dir: - print( - "ULWGL-Proton is outdated.\nFor latest release, please download " - + files[1][1], - file=stderr, - ) - - return env - - return None - - -def _get_from_cache( - env: Dict[str, str], - steam_compat: Path, - cache: Path, - files: List[Tuple[str, str]], - use_latest=True, -) -> Union[Dict[str, str], None]: - """Refer to ULWGL cache directory. - - Use the latest in the cache when present. When download fails, use an old version - Older Proton versions are only referred to when: digests mismatch, user interrupt, or download failure/no internet - """ - path: Path = None - name: str = "" - - for tarball in cache.glob("ULWGL-Proton*.tar.gz"): - if files and tarball == cache.joinpath(files[1][0]) and use_latest: - path = tarball - name = tarball.name - break - if tarball != cache.joinpath(files[1][0]) and not use_latest: - path = tarball - name = tarball.name - break - - if path: - proton_dir: str = name[: name.find(".tar.gz")] # Proton dir - - print(f"{name} found in: {path}", file=stderr) - try: - _extract_dir(path, steam_compat) - environ["PROTONPATH"] = steam_compat.joinpath(proton_dir).as_posix() - env["PROTONPATH"] = environ["PROTONPATH"] - - return env - except KeyboardInterrupt: - if steam_compat.joinpath(proton_dir).is_dir(): - print(f"Purging {proton_dir} in {steam_compat} ...", file=stderr) - rmtree(steam_compat.joinpath(proton_dir).as_posix()) - raise - - return None - - -def _get_latest( - env: Dict[str, str], steam_compat: Path, cache: Path, files: List[Tuple[str, str]] -) -> Union[Dict[str, str], None]: - """Download the latest Proton for new installs -- empty cache and Steam compat. - - When the digests mismatched or when interrupted, refer to cache for an old version - """ - if files: - print("Fetching latest release ...", file=stderr) - try: - _fetch_proton(env, steam_compat, cache, files) - env["PROTONPATH"] = environ["PROTONPATH"] - except ValueError: - # Digest mismatched or download failed - # Refer to the cache for old version next - return None - except KeyboardInterrupt: - tarball: str = files[1][0] - proton_dir: str = tarball[: tarball.find(".tar.gz")] # Proton dir - - # Exit cleanly - # Clean up extracted data and cache to prevent corruption/errors - # Refer to the cache for old version next - _cleanup(tarball, proton_dir, cache, steam_compat) - return None - - return env diff --git a/launcher/ulwgl_log.py b/launcher/ulwgl_log.py deleted file mode 100644 index 8f2fa76..0000000 --- a/launcher/ulwgl_log.py +++ /dev/null @@ -1,13 +0,0 @@ -import logging -from sys import stderr -from ulwgl_consts import SIMPLE_FORMAT, DEBUG_FORMAT - -simple_formatter = logging.Formatter(SIMPLE_FORMAT) -debug_formatter = logging.Formatter(DEBUG_FORMAT) - -log = logging.getLogger(__name__) - -console_handler = logging.StreamHandler(stream=stderr) -console_handler.setFormatter(simple_formatter) -log.addHandler(console_handler) -log.setLevel(logging.CRITICAL + 1) diff --git a/launcher/ulwgl_plugins.py b/launcher/ulwgl_plugins.py deleted file mode 100644 index a682c2b..0000000 --- a/launcher/ulwgl_plugins.py +++ /dev/null @@ -1,129 +0,0 @@ -import os -from pathlib import Path -from typing import Dict, Set, Any, List -from argparse import Namespace - - -def set_env_toml(env: Dict[str, str], args: Namespace) -> Dict[str, str]: - """Read a TOML file then sets the environment variables for the Steam RT. - - In the TOML file, certain keys map to Steam RT environment variables. For example: - proton -> $PROTONPATH - prefix -> $WINEPREFIX - game_id -> $GAMEID - exe -> $EXE - At the moment we expect the tables: 'ulwgl' - """ - try: - import tomllib - except ModuleNotFoundError: - msg: str = "tomllib requires Python 3.11" - raise ModuleNotFoundError(msg) - - toml: Dict[str, Any] = None - path_config: str = Path(getattr(args, "config", None)).expanduser().as_posix() - - if not Path(path_config).is_file(): - msg: str = "Path to configuration is not a file: " + getattr( - args, "config", None - ) - raise FileNotFoundError(msg) - - with Path(path_config).open(mode="rb") as file: - toml = tomllib.load(file) - - _check_env_toml(env, toml) - - for key, val in toml["ulwgl"].items(): - if key == "prefix": - env["WINEPREFIX"] = val - elif key == "game_id": - env["GAMEID"] = val - elif key == "proton": - env["PROTONPATH"] = val - elif key == "store": - env["STORE"] = val - elif key == "exe": - if toml.get("ulwgl").get("launch_args"): - env["EXE"] = val + " " + " ".join(toml.get("ulwgl").get("launch_args")) - else: - env["EXE"] = val - return env - - -def _check_env_toml(env: Dict[str, str], toml: Dict[str, Any]): - """Check for required or empty key/value pairs when reading a TOML config. - - NOTE: Casing matters in the config and we do not check if the game id is set - """ - table: str = "ulwgl" - required_keys: List[str] = ["proton", "prefix", "exe"] - - if table not in toml: - err: str = f"Table '{table}' in TOML is not defined." - raise ValueError(err) - - for key in required_keys: - if key not in toml[table]: - err: str = f"The following key in table '{table}' is required: {key}" - raise ValueError(err) - - # Raise an error for executables that do not exist - # One case this can happen is when game options are appended at the end of the exe - # Users should use launch_args for that - if key == "exe" and not Path(toml[table][key]).expanduser().is_file(): - val: str = toml[table][key] - err: str = f"Value for key '{key}' in TOML is not a file: {val}" - raise FileNotFoundError(err) - - # The proton and wine prefix need to be folders - if (key == "proton" and not Path(toml[table][key]).expanduser().is_dir()) or ( - key == "prefix" and not Path(toml[table][key]).expanduser().is_dir() - ): - dir: str = Path(toml[table][key]).expanduser().as_posix() - err: str = f"Value for key '{key}' in TOML is not a directory: {dir}" - raise NotADirectoryError(err) - - # Check for empty keys - for key, val in toml[table].items(): - if not val and isinstance(val, str): - err: str = f"Value is empty for '{key}' in TOML.\nPlease specify a value or remove the following entry:\n{key} = {val}" - raise ValueError(err) - - return toml - - -def enable_steam_game_drive(env: Dict[str, str]) -> Dict[str, str]: - """Enable Steam Game Drive functionality. - - Expects STEAM_COMPAT_INSTALL_PATH to be set - STEAM_RUNTIME_LIBRARY_PATH will not be set if the exe directory does not exist - """ - paths: Set[str] = set() - root: Path = Path("/") - - # Check for mount points going up toward the root - # NOTE: Subvolumes can be mount points - for path in Path(env["STEAM_COMPAT_INSTALL_PATH"]).parents: - if path.is_mount() and path != root: - if env["STEAM_COMPAT_LIBRARY_PATHS"]: - env["STEAM_COMPAT_LIBRARY_PATHS"] = ( - env["STEAM_COMPAT_LIBRARY_PATHS"] + ":" + path.as_posix() - ) - else: - env["STEAM_COMPAT_LIBRARY_PATHS"] = path.as_posix() - break - - if "LD_LIBRARY_PATH" in os.environ: - paths.add(Path(os.environ["LD_LIBRARY_PATH"]).as_posix()) - - if env["STEAM_COMPAT_INSTALL_PATH"]: - paths.add(env["STEAM_COMPAT_INSTALL_PATH"]) - - # Hard code for now because these paths seem to be pretty standard - # This way we avoid shelling to ldconfig - paths.add("/usr/lib") - paths.add("/usr/lib32") - env["STEAM_RUNTIME_LIBRARY_PATH"] = ":".join(list(paths)) - - return env diff --git a/launcher/ulwgl_run.py b/launcher/ulwgl_run.py deleted file mode 100755 index 713a98c..0000000 --- a/launcher/ulwgl_run.py +++ /dev/null @@ -1,315 +0,0 @@ -#!/usr/bin/env python3 - -import os -import sys -from traceback import print_exception -from argparse import ArgumentParser, Namespace, RawTextHelpFormatter -from pathlib import Path -from typing import Dict, Any, List, Set, Union, Tuple -from ulwgl_plugins import enable_steam_game_drive, set_env_toml -from re import match -from subprocess import run -from ulwgl_dl_util import get_ulwgl_proton -from ulwgl_consts import Level -from ulwgl_util import msg -from ulwgl_log import log, console_handler, debug_formatter - -verbs: Set[str] = { - "waitforexitandrun", - "run", - "runinprefix", - "destroyprefix", - "getcompatpath", - "getnativepath", -} - - -def parse_args() -> Union[Namespace, Tuple[str, List[str]]]: # noqa: D103 - opt_args: Set[str] = {"--help", "-h", "--config"} - exe: str = Path(__file__).name - usage: str = f""" -example usage: - GAMEID= {exe} /home/foo/example.exe - WINEPREFIX= GAMEID= {exe} /home/foo/example.exe - WINEPREFIX= GAMEID= PROTONPATH= {exe} /home/foo/example.exe - WINEPREFIX= GAMEID= PROTONPATH= {exe} /home/foo/example.exe -opengl - WINEPREFIX= GAMEID= PROTONPATH= {exe} "" - WINEPREFIX= GAMEID= PROTONPATH= PROTON_VERB= {exe} /home/foo/example.exe - WINEPREFIX= GAMEID= PROTONPATH= STORE= {exe} /home/foo/example.exe - ULWGL_LOG= GAMEID= {exe} /home/foo/example.exe - {exe} --config /home/foo/example.toml - """ - parser: ArgumentParser = ArgumentParser( - description="Unified Linux Wine Game Launcher", - epilog=usage, - formatter_class=RawTextHelpFormatter, - ) - parser.add_argument("--config", help="path to TOML file (requires Python 3.11)") - - if not sys.argv[1:]: - err: str = "Please see project README.md for more info and examples.\nhttps://github.com/Open-Wine-Components/ULWGL-launcher" - parser.print_help(sys.stderr) - raise SystemExit(err) - - if sys.argv[1:][0] in opt_args: - return parser.parse_args(sys.argv[1:]) - - if sys.argv[1] in verbs: - if "PROTON_VERB" not in os.environ: - os.environ["PROTON_VERB"] = sys.argv[1] - sys.argv.pop(1) - - return sys.argv[1], sys.argv[2:] - - -def set_log() -> None: - """Adjust the log level for the logger.""" - levels: Set[str] = {"1", "warn", "debug"} - - if os.environ["ULWGL_LOG"] not in levels: - return - - if os.environ["ULWGL_LOG"] == "1": - # Show the envvars and command at this level - log.setLevel(level=Level.INFO.value) - elif os.environ["ULWGL_LOG"] == "warn": - log.setLevel(level=Level.WARNING.value) - elif os.environ["ULWGL_LOG"] == "debug": - # Show all logs - console_handler.setFormatter(debug_formatter) - log.addHandler(console_handler) - log.setLevel(level=Level.DEBUG.value) - - os.environ.pop("ULWGL_LOG") - - -def setup_pfx(path: str) -> None: - """Create a symlink to the WINE prefix and tracked_files file.""" - pfx: Path = Path(path).joinpath("pfx").expanduser() - - if pfx.is_symlink(): - pfx.unlink() - - if not pfx.is_dir(): - pfx.symlink_to(Path(path).expanduser()) - - Path(path).joinpath("tracked_files").expanduser().touch() - - -def check_env( - env: Dict[str, str], toml: Dict[str, Any] = None -) -> Union[Dict[str, str], Dict[str, Any]]: - """Before executing a game, check for environment variables and set them. - - WINEPREFIX, GAMEID and PROTONPATH are strictly required. - """ - if "GAMEID" not in os.environ: - err: str = "Environment variable not set: GAMEID" - raise ValueError(err) - env["GAMEID"] = os.environ["GAMEID"] - - if "WINEPREFIX" not in os.environ: - pfx: Path = Path.home().joinpath("Games/ULWGL/" + env["GAMEID"]) - pfx.mkdir(parents=True, exist_ok=True) - os.environ["WINEPREFIX"] = pfx.as_posix() - if not Path(os.environ["WINEPREFIX"]).expanduser().is_dir(): - pfx: Path = Path(os.environ["WINEPREFIX"]) - pfx.mkdir(parents=True, exist_ok=True) - os.environ["WINEPREFIX"] = pfx.as_posix() - - env["WINEPREFIX"] = os.environ["WINEPREFIX"] - - # Proton Version - if ( - "PROTONPATH" in os.environ - and os.environ["PROTONPATH"] - and Path( - "~/.local/share/Steam/compatibilitytools.d/" + os.environ["PROTONPATH"] - ) - .expanduser() - .is_dir() - ): - log.debug(msg("Proton version selected", Level.DEBUG)) - os.environ["PROTONPATH"] = ( - Path("~/.local/share/Steam/compatibilitytools.d") - .joinpath(os.environ["PROTONPATH"]) - .expanduser() - .as_posix() - ) - - if "PROTONPATH" not in os.environ: - os.environ["PROTONPATH"] = "" - get_ulwgl_proton(env) - - env["PROTONPATH"] = os.environ["PROTONPATH"] - - # If download fails/doesn't exist in the system, raise an error - if not os.environ["PROTONPATH"]: - err: str = "Download failed.\nProton could not be found in cache or compatibilitytools.d\nPlease set $PROTONPATH or visit https://github.com/Open-Wine-Components/ULWGL-Proton/releases" - raise FileNotFoundError(err) - - return env - - -def set_env( - env: Dict[str, str], args: Union[Namespace, Tuple[str, List[str]]] -) -> Dict[str, str]: - """Set various environment variables for the Steam RT. - - Filesystem paths will be formatted and expanded as POSIX - """ - # PROTON_VERB - # For invalid Proton verbs, just assign the waitforexitandrun - if "PROTON_VERB" in os.environ and os.environ["PROTON_VERB"] in verbs: - env["PROTON_VERB"] = os.environ["PROTON_VERB"] - else: - env["PROTON_VERB"] = "waitforexitandrun" - - # EXE - # Empty string for EXE will be used to create a prefix - if isinstance(args, tuple) and isinstance(args[0], str) and not args[0]: - env["EXE"] = "" - env["STEAM_COMPAT_INSTALL_PATH"] = "" - env["PROTON_VERB"] = "waitforexitandrun" - elif isinstance(args, tuple): - env["EXE"] = Path(args[0]).expanduser().as_posix() - env["STEAM_COMPAT_INSTALL_PATH"] = Path(env["EXE"]).parent.as_posix() - else: - # Config branch - env["EXE"] = Path(env["EXE"]).expanduser().as_posix() - env["STEAM_COMPAT_INSTALL_PATH"] = Path(env["EXE"]).parent.as_posix() - - if "STORE" in os.environ: - env["STORE"] = os.environ["STORE"] - - # ULWGL_ID - env["ULWGL_ID"] = env["GAMEID"] - env["STEAM_COMPAT_APP_ID"] = "0" - - if match(r"^ulwgl-[\d\w]+$", env["ULWGL_ID"]): - env["STEAM_COMPAT_APP_ID"] = env["ULWGL_ID"][env["ULWGL_ID"].find("-") + 1 :] - env["SteamAppId"] = env["STEAM_COMPAT_APP_ID"] - env["SteamGameId"] = env["SteamAppId"] - - # PATHS - env["WINEPREFIX"] = Path(env["WINEPREFIX"]).expanduser().as_posix() - env["PROTONPATH"] = Path(env["PROTONPATH"]).expanduser().as_posix() - env["STEAM_COMPAT_DATA_PATH"] = env["WINEPREFIX"] - env["STEAM_COMPAT_SHADER_PATH"] = env["STEAM_COMPAT_DATA_PATH"] + "/shadercache" - env["STEAM_COMPAT_TOOL_PATHS"] = ( - env["PROTONPATH"] + ":" + Path(__file__).parent.as_posix() - ) - env["STEAM_COMPAT_MOUNTS"] = env["STEAM_COMPAT_TOOL_PATHS"] - - return env - - -def build_command( - env: Dict[str, str], command: List[str], opts: List[str] = None -) -> List[str]: - """Build the command to be executed.""" - paths: List[Path] = [ - Path.home().joinpath(".local/share/ULWGL/ULWGL"), - Path(__file__).parent.joinpath("ULWGL"), - ] - entry_point: str = "" - verb: str = env["PROTON_VERB"] - - # Find the ULWGL script in $HOME/.local/share then cwd - for path in paths: - if path.is_file(): - entry_point = path.as_posix() - break - - # Raise an error if the _v2-entry-point cannot be found - if not entry_point: - home: str = Path.home().as_posix() - dir: str = Path(__file__).parent.as_posix() - msg: str = ( - f"Path to _v2-entry-point cannot be found in: {home}/.local/share or {dir}" - ) - raise FileNotFoundError(msg) - - if not Path(env.get("PROTONPATH")).joinpath("proton").is_file(): - err: str = "The following file was not found in PROTONPATH: proton" - raise FileNotFoundError(err) - - command.extend([entry_point, "--verb", verb, "--"]) - command.extend( - [ - Path(env.get("PROTONPATH")).joinpath("proton").as_posix(), - verb, - env.get("EXE"), - ] - ) - - if opts: - command.extend([*opts]) - - return command - - -def main() -> int: # noqa: D103 - env: Dict[str, str] = { - "WINEPREFIX": "", - "GAMEID": "", - "PROTON_CRASH_REPORT_DIR": "/tmp/ULWGL_crashreports", - "PROTONPATH": "", - "STEAM_COMPAT_APP_ID": "", - "STEAM_COMPAT_TOOL_PATHS": "", - "STEAM_COMPAT_LIBRARY_PATHS": "", - "STEAM_COMPAT_MOUNTS": "", - "STEAM_COMPAT_INSTALL_PATH": "", - "STEAM_COMPAT_CLIENT_INSTALL_PATH": "", - "STEAM_COMPAT_DATA_PATH": "", - "STEAM_COMPAT_SHADER_PATH": "", - "FONTCONFIG_PATH": "", - "EXE": "", - "SteamAppId": "", - "SteamGameId": "", - "STEAM_RUNTIME_LIBRARY_PATH": "", - "STORE": "", - "PROTON_VERB": "", - "ULWGL_ID": "", - } - command: List[str] = [] - args: Union[Namespace, Tuple[str, List[str]]] = parse_args() - opts: List[str] = None - - if "ULWGL_LOG" in os.environ: - set_log() - - if isinstance(args, Namespace) and getattr(args, "config", None): - set_env_toml(env, args) - else: - # Reference the game options - opts = args[1] - check_env(env) - - setup_pfx(env["WINEPREFIX"]) - set_env(env, args) - - # Game Drive - enable_steam_game_drive(env) - - # Set all environment variables - # NOTE: `env` after this block should be read only - for key, val in env.items(): - log.info(msg(f"{key}={val}", Level.INFO)) - os.environ[key] = val - - build_command(env, command, opts) - log.debug(msg(command, Level.DEBUG)) - return run(command).returncode - - -if __name__ == "__main__": - try: - sys.exit(main()) - except KeyboardInterrupt: - # Until Reaper is part of the command sequence, spawned process may still be alive afterwards - log.warning(msg("Keyboard Interrupt", Level.WARNING)) - sys.exit(1) - except Exception as e: # noqa: BLE001 - print_exception(e) - sys.exit(1) diff --git a/launcher/ulwgl_test.py b/launcher/ulwgl_test.py deleted file mode 100644 index 930f6ea..0000000 --- a/launcher/ulwgl_test.py +++ /dev/null @@ -1,1111 +0,0 @@ -import unittest -import ulwgl_run -import os -import argparse -from argparse import Namespace -from unittest.mock import patch -from pathlib import Path -from shutil import rmtree -import re -import ulwgl_plugins -import ulwgl_dl_util -import tarfile - - -class TestGameLauncher(unittest.TestCase): - """Test suite for ulwgl_run.py.""" - - def setUp(self): - """Create the test directory, exe and environment variables.""" - self.env = { - "WINEPREFIX": "", - "GAMEID": "", - "PROTON_CRASH_REPORT_DIR": "/tmp/ULWGL_crashreports", - "PROTONPATH": "", - "STEAM_COMPAT_APP_ID": "", - "STEAM_COMPAT_TOOL_PATHS": "", - "STEAM_COMPAT_LIBRARY_PATHS": "", - "STEAM_COMPAT_MOUNTS": "", - "STEAM_COMPAT_INSTALL_PATH": "", - "STEAM_COMPAT_CLIENT_INSTALL_PATH": "", - "STEAM_COMPAT_DATA_PATH": "", - "STEAM_COMPAT_SHADER_PATH": "", - "FONTCONFIG_PATH": "", - "EXE": "", - "SteamAppId": "", - "SteamGameId": "", - "STEAM_RUNTIME_LIBRARY_PATH": "", - "ULWGL_ID": "", - "STORE": "", - "PROTON_VERB": "", - } - self.test_opts = "-foo -bar" - # Proton verb - # Used when testing build_command - self.test_verb = "waitforexitandrun" - # Test directory - self.test_file = "./tmp.WMYQiPb9A" - # Executable - self.test_exe = self.test_file + "/" + "foo" - # Cache - self.test_cache = Path("./tmp.5HYdpddgvs") - # Steam compat dir - self.test_compat = Path("./tmp.ZssGZoiNod") - # ULWGL-Proton dir - self.test_proton_dir = Path("ULWGL-Proton-5HYdpddgvs") - # ULWGL-Proton release - self.test_archive = Path(self.test_cache).joinpath( - f"{self.test_proton_dir}.tar.gz" - ) - - self.test_cache.mkdir(exist_ok=True) - self.test_compat.mkdir(exist_ok=True) - self.test_proton_dir.mkdir(exist_ok=True) - - # Mock the proton file in the dir - self.test_proton_dir.joinpath("proton").touch(exist_ok=True) - - # Mock the release downloaded in the cache: tmp.5HYdpddgvs/ULWGL-Proton-5HYdpddgvs.tar.gz - # Expected directory structure within the archive: - # - # +-- ULWGL-Proton-5HYdpddgvs (root directory) - # | +-- proton (normal file) - with tarfile.open(self.test_archive.as_posix(), "w:gz") as tar: - tar.add( - self.test_proton_dir.as_posix(), arcname=self.test_proton_dir.as_posix() - ) - - Path(self.test_file).mkdir(exist_ok=True) - Path(self.test_exe).touch() - - def tearDown(self): - """Unset environment variables and delete test files after each test.""" - for key, val in self.env.items(): - if key in os.environ: - os.environ.pop(key) - - if Path(self.test_file).exists(): - rmtree(self.test_file) - - if self.test_cache.exists(): - rmtree(self.test_cache.as_posix()) - - if self.test_compat.exists(): - rmtree(self.test_compat.as_posix()) - - if self.test_proton_dir.exists(): - rmtree(self.test_proton_dir.as_posix()) - - def test_latest_interrupt(self): - """Test _get_latest in the event the user interrupts the download/extraction process. - - Assumes a file is being downloaded or extracted in this case. - A KeyboardInterrupt should be raised, and the cache/compat dir should be cleaned afterwards. - """ - result = None - # In the real usage, should be populated after successful callout for latest Proton releases - # In this case, assume the test variable will be downloaded - files = [("", ""), (self.test_archive.name, "")] - - # In the event of an interrupt, both the cache/compat dir will be checked for the latest release for removal - # We do this since the extraction process can be interrupted as well - ulwgl_dl_util._extract_dir(self.test_archive, self.test_compat) - - with patch("ulwgl_dl_util._fetch_proton") as mock_function: - # Mock the interrupt - # We want the dir we tried to extract to be cleaned - mock_function.side_effect = KeyboardInterrupt - result = ulwgl_dl_util._get_latest( - self.env, self.test_compat, self.test_cache, files - ) - self.assertFalse(self.env["PROTONPATH"], "Expected PROTONPATH to be empty") - self.assertFalse(result, "Expected None when a ValueError occurs") - - # Verify the state of the compat dir/cache - self.assertFalse( - self.test_compat.joinpath( - self.test_archive.name[: self.test_archive.name.find(".tar.gz")] - ).exists(), - "Expected Proton dir in compat to be cleaned", - ) - self.assertFalse( - self.test_cache.joinpath(self.test_archive.name).exists(), - "Expected Proton dir in compat to be cleaned", - ) - - def test_latest_val_err(self): - """Test _get_latest in the event something goes wrong in the download process for the latest Proton. - - Assumes a file is being downloaded in this case. - A ValueError should be raised, and one case it can happen is if the digests mismatched for some reason - """ - result = None - # In the real usage, should be populated after successful callout for latest Proton releases - # When empty, it means the callout failed for some reason (e.g. no internet) - files = [("", ""), (self.test_archive.name, "")] - - with patch("ulwgl_dl_util._fetch_proton") as mock_function: - # Mock the interrupt - mock_function.side_effect = ValueError - result = ulwgl_dl_util._get_latest( - self.env, self.test_compat, self.test_cache, files - ) - self.assertFalse(self.env["PROTONPATH"], "Expected PROTONPATH to be empty") - self.assertFalse(result, "Expected None when a ValueError occurs") - - def test_latest_offline(self): - """Test _get_latest when the user doesn't have internet.""" - result = None - # In the real usage, should be populated after successful callout for latest Proton releases - # When empty, it means the callout failed for some reason (e.g. no internet) - files = [] - - os.environ["PROTONPATH"] = "" - - with patch("ulwgl_dl_util._fetch_proton"): - result = ulwgl_dl_util._get_latest( - self.env, self.test_compat, self.test_cache, files - ) - self.assertFalse(self.env["PROTONPATH"], "Expected PROTONPATH to be empty") - self.assertTrue(result is self.env, "Expected the same reference") - - def test_cache_interrupt(self): - """Test _get_from_cache on keyboard interrupt on extraction from the cache to the compat dir.""" - # In the real usage, should be populated after successful callout for latest Proton releases - # Just mock it and assumes its the latest - files = [("", ""), (self.test_archive.name, "")] - - ulwgl_dl_util._extract_dir(self.test_archive, self.test_compat) - - self.assertTrue( - self.test_compat.joinpath( - self.test_archive.name[: self.test_archive.name.find(".tar.gz")] - ).exists(), - "Expected Proton dir to exist in compat", - ) - - with patch("ulwgl_dl_util._extract_dir") as mock_function: - with self.assertRaisesRegex(KeyboardInterrupt, ""): - # Mock the interrupt - # We want to simulate an interrupt mid-extraction in this case - # We want the dir we tried to extract to be cleaned - mock_function.side_effect = KeyboardInterrupt - ulwgl_dl_util._get_from_cache( - self.env, self.test_compat, self.test_cache, files, True - ) - - # After interrupt, we attempt to clean the compat dir for the file we tried to extract because it could be in an incomplete state - # Verify that the dir we tried to extract from cache is removed to avoid corruption on next launch - self.assertFalse( - self.test_compat.joinpath( - self.test_archive.name[: self.test_archive.name.find(".tar.gz")] - ).exists(), - "Expected Proton dir in compat to be cleaned", - ) - - def test_cache_old(self): - """Test _get_from_cache when the cache is empty. - - In real usage, this only happens as a last resort when: download fails, digests mismatched, etc. - """ - result = None - # In the real usage, should be populated after successful callout for latest Proton releases - # Just mock it and assumes its the latest - files = [("", ""), (self.test_archive.name, "")] - - # Mock old Proton versions in the cache - test_proton_dir = Path("ULWGL-Proton-foo") - test_proton_dir.mkdir(exist_ok=True) - test_archive = Path(self.test_cache).joinpath( - f"{test_proton_dir.as_posix()}.tar.gz" - ) - - with tarfile.open(test_archive.as_posix(), "w:gz") as tar: - tar.add(test_proton_dir.as_posix(), arcname=test_proton_dir.as_posix()) - - result = ulwgl_dl_util._get_from_cache( - self.env, self.test_compat, self.test_cache, files, False - ) - - # Verify that the old Proton was assigned - self.assertTrue(result is self.env, "Expected the same reference") - self.assertEqual( - self.env["PROTONPATH"], - self.test_compat.joinpath( - test_archive.name[: test_archive.name.find(".tar.gz")] - ).as_posix(), - "Expected PROTONPATH to be proton dir in compat", - ) - - test_archive.unlink() - test_proton_dir.rmdir() - - def test_cache_empty(self): - """Test _get_from_cache when the cache is empty.""" - result = None - # In the real usage, should be populated after successful callout for latest Proton releases - # Just mock it and assumes its the latest - files = [("", ""), (self.test_archive.name, "")] - - self.test_archive.unlink() - - result = ulwgl_dl_util._get_from_cache( - self.env, self.test_compat, self.test_cache, files, True - ) - self.assertFalse(result, "Expected None when calling _get_from_cache") - self.assertFalse( - self.env["PROTONPATH"], - "Expected PROTONPATH to be empty when the cache is empty", - ) - - def test_cache(self): - """Test _get_from_cache. - - Tests the case when the latest Proton already exists in the cache - """ - result = None - # In the real usage, should be populated after successful callout for latest Proton releases - # Just mock it and assumes its the latest - files = [("", ""), (self.test_archive.name, "")] - - result = ulwgl_dl_util._get_from_cache( - self.env, self.test_compat, self.test_cache, files, True - ) - self.assertTrue(result is self.env, "Expected the same reference") - self.assertEqual( - self.env["PROTONPATH"], - self.test_compat.joinpath( - self.test_archive.name[: self.test_archive.name.find(".tar.gz")] - ).as_posix(), - "Expected PROTONPATH to be proton dir in compat", - ) - - def test_steamcompat_nodir(self): - """Test _get_from_steamcompat when a Proton doesn't exist in the Steam compat dir. - - In this case, the None should be returned to signal that we should continue with downloading the latest Proton - """ - result = None - files = [("", ""), (self.test_archive.name, "")] - - result = ulwgl_dl_util._get_from_steamcompat( - self.env, self.test_compat, self.test_cache, files - ) - - self.assertFalse(result, "Expected None after calling _get_from_steamcompat") - self.assertFalse(self.env["PROTONPATH"], "Expected PROTONPATH to not be set") - - def test_steamcompat(self): - """Test _get_from_steamcompat. - - When a Proton exist in .local/share/Steam/compatibilitytools.d, use it when PROTONPATH is unset - """ - result = None - files = [("", ""), (self.test_archive.name, "")] - - ulwgl_dl_util._extract_dir(self.test_archive, self.test_compat) - - result = ulwgl_dl_util._get_from_steamcompat( - self.env, self.test_compat, self.test_cache, files - ) - - self.assertTrue(result is self.env, "Expected the same reference") - self.assertEqual( - self.env["PROTONPATH"], - self.test_compat.joinpath( - self.test_archive.name[: self.test_archive.name.find(".tar.gz")] - ).as_posix(), - "Expected PROTONPATH to be proton dir in compat", - ) - - def test_cleanup_no_exists(self): - """Test _cleanup when passed files that do not exist. - - In the event of an interrupt during the download/extract process, we only want to clean the files that exist - NOTE: This is **extremely** important, as we do **not** want to delete anything else but the files we downloaded/extracted -- the incomplete tarball/extracted dir - """ - result = None - - ulwgl_dl_util._extract_dir(self.test_archive, self.test_compat) - - # Create a file in the cache and compat - self.test_cache.joinpath("foo").touch() - self.test_compat.joinpath("foo").touch() - - # Before cleaning - # On setUp, an archive is created and a dir should exist in compat after extraction - self.assertTrue( - self.test_compat.joinpath("foo").exists(), - "Expected test file to exist in compat before cleaning", - ) - self.assertTrue( - self.test_cache.joinpath("foo").exists(), - "Expected test file to exist in cache before cleaning", - ) - self.assertTrue( - self.test_archive.exists(), - "Expected archive to exist in cache before cleaning", - ) - self.assertTrue( - self.test_compat.joinpath(self.test_proton_dir).joinpath("proton").exists(), - "Expected 'proton' to exist before cleaning", - ) - - # Pass files that do not exist - result = ulwgl_dl_util._cleanup( - "foo.tar.gz", - "foo", - self.test_cache, - self.test_compat, - ) - - # Verify state of cache and compat after cleaning - self.assertFalse(result, "Expected None after cleaning") - self.assertTrue( - self.test_compat.joinpath("foo").exists(), - "Expected test file to exist in compat after cleaning", - ) - self.assertTrue( - self.test_cache.joinpath("foo").exists(), - "Expected test file to exist in cache after cleaning", - ) - self.assertTrue( - self.test_compat.joinpath(self.test_proton_dir).exists(), - "Expected proton dir to still exist after cleaning", - ) - self.assertTrue( - self.test_archive.exists(), - "Expected archive to still exist after cleaning", - ) - self.assertTrue( - self.test_compat.joinpath(self.test_proton_dir).joinpath("proton").exists(), - "Expected 'proton' to still exist after cleaning", - ) - - def test_cleanup(self): - """Test _cleanup. - - In the event of an interrupt during the download/extract process, we want to clean the cache or the extracted dir in Steam compat to avoid incomplete files - """ - result = None - - ulwgl_dl_util._extract_dir(self.test_archive, self.test_compat) - result = ulwgl_dl_util._cleanup( - self.test_proton_dir.as_posix() + ".tar.gz", - self.test_proton_dir.as_posix(), - self.test_cache, - self.test_compat, - ) - self.assertFalse(result, "Expected None after cleaning") - self.assertFalse( - self.test_compat.joinpath(self.test_proton_dir).exists(), - "Expected proton dir to be cleaned in compat", - ) - self.assertFalse( - self.test_archive.exists(), - "Expected archive to be cleaned in cache", - ) - self.assertFalse( - self.test_compat.joinpath(self.test_proton_dir).joinpath("proton").exists(), - "Expected 'proton' to not exist after cleaned", - ) - - def test_extract_err(self): - """Test _extract_dir when passed a non-gzip compressed archive. - - An error should be raised as we only expect .tar.gz releases - """ - test_archive = self.test_cache.joinpath(f"{self.test_proton_dir}.tar") - # Do not apply compression - with tarfile.open(test_archive.as_posix(), "w") as tar: - tar.add( - self.test_proton_dir.as_posix(), arcname=self.test_proton_dir.as_posix() - ) - - with self.assertRaisesRegex(tarfile.ReadError, "gzip"): - ulwgl_dl_util._extract_dir(test_archive, self.test_compat) - - if test_archive.exists(): - test_archive.unlink() - - def test_extract(self): - """Test _extract_dir. - - An error should not be raised when the Proton release is extracted to the Steam compat dir - """ - result = None - - result = ulwgl_dl_util._extract_dir(self.test_archive, self.test_compat) - self.assertFalse(result, "Expected None after extracting") - self.assertTrue( - self.test_compat.joinpath(self.test_proton_dir).exists(), - "Expected proton dir to exists in compat", - ) - self.assertTrue( - self.test_compat.joinpath(self.test_proton_dir).joinpath("proton").exists(), - "Expected 'proton' file to exists in the proton dir", - ) - - def test_game_drive_empty(self): - """Test enable_steam_game_drive. - - WINE prefixes can be created by passing an empty string - Example: - WINEPREFIX= PROTONPATH= GAMEID= ulwgl-run "" - - During this process, we attempt to prepare setting up game drive and set the values for STEAM_RUNTIME_LIBRARY_PATH and STEAM_COMPAT_INSTALL_PATHS - The resulting value of those variables should be colon delimited string with no leading colons and contain only /usr/lib or /usr/lib32 - - Ignores LD_LIBRARY_PATH, relevant to Game Drive, which is sourced in Ubuntu and maybe its derivatives - """ - args = None - result_gamedrive = None - Path(self.test_file + "/proton").touch() - - # Replicate main's execution and test up until enable_steam_game_drive - with patch("sys.argv", ["", ""]): - os.environ["WINEPREFIX"] = self.test_file - os.environ["PROTONPATH"] = self.test_file - os.environ["GAMEID"] = self.test_file - os.environ["STORE"] = self.test_file - # Args - args = ulwgl_run.parse_args() - # Config - ulwgl_run.check_env(self.env) - # Prefix - ulwgl_run.setup_pfx(self.env["WINEPREFIX"]) - # Env - ulwgl_run.set_env(self.env, args) - - # Some distributions source this variable (e.g. Ubuntu) and will be added to the result of STEAM_RUNTIME_LIBRARY_PATH - # Only test the case without it set - if "LD_LIBRARY_PATH" in os.environ: - os.environ.pop("LD_LIBRARY_PATH") - - # Game drive - result_gamedrive = ulwgl_plugins.enable_steam_game_drive(self.env) - - # Ubuntu sources this variable and will be added once Game Drive is enabled - # Just test the case without it - if "LD_LIBRARY_PATH" in os.environ: - os.environ.pop("LD_LIBRARY_PATH") - - for key, val in self.env.items(): - os.environ[key] = val - - # Game drive - self.assertTrue(result_gamedrive is self.env, "Expected the same reference") - self.assertTrue( - self.env["STEAM_RUNTIME_LIBRARY_PATH"], - "Expected two elements in STEAM_RUNTIME_LIBRARY_PATHS", - ) - - # We just expect /usr/lib and /usr/lib32 since LD_LIBRARY_PATH is unset - self.assertEqual( - len(self.env["STEAM_RUNTIME_LIBRARY_PATH"].split(":")), - 2, - "Expected two values in STEAM_RUNTIME_LIBRARY_PATH", - ) - - # We need to sort the elements because the values were originally in a set - str1, str2 = [*sorted(self.env["STEAM_RUNTIME_LIBRARY_PATH"].split(":"))] - - # Check that there are no trailing colons or unexpected characters - self.assertEqual(str1, "/usr/lib", "Expected /usr/lib") - self.assertEqual(str2, "/usr/lib32", "Expected /usr/lib32") - - # Both of these values should be empty still after calling enable_steam_game_drive - self.assertFalse( - self.env["STEAM_COMPAT_INSTALL_PATH"], - "Expected STEAM_COMPAT_INSTALL_PATH to be empty when passing an empty EXE", - ) - self.assertFalse(self.env["EXE"], "Expected EXE to be empty on empty string") - - def test_build_command(self): - """Test build_command. - - After parsing valid environment variables set by the user, be sure we do not raise a FileNotFoundError - NOTE: Also, FileNotFoundError will be raised if the _v2-entry-point (ULWGL) is not in $HOME/.local/share/ULWGL or in cwd - """ - result_args = None - test_command = [] - - # Mock the /proton file - Path(self.test_file + "/proton").touch() - - with patch("sys.argv", ["", self.test_exe]): - os.environ["WINEPREFIX"] = self.test_file - os.environ["PROTONPATH"] = self.test_file - os.environ["GAMEID"] = self.test_file - os.environ["STORE"] = self.test_file - # Args - result_args = ulwgl_run.parse_args() - # Config - ulwgl_run.check_env(self.env) - # Prefix - ulwgl_run.setup_pfx(self.env["WINEPREFIX"]) - # Env - ulwgl_run.set_env(self.env, result_args) - # Game drive - ulwgl_plugins.enable_steam_game_drive(self.env) - - for key, val in self.env.items(): - os.environ[key] = val - - # Build - test_command = ulwgl_run.build_command(self.env, test_command) - self.assertIsInstance(test_command, list, "Expected a List from build_command") - self.assertEqual( - len(test_command), 7, "Expected 7 elements in the list from build_command" - ) - # Verify contents - entry_point, opt1, verb, opt2, proton, verb2, exe = [*test_command] - # The entry point dest could change. Just check if there's a value - self.assertTrue(entry_point, "Expected an entry point") - self.assertEqual(opt1, "--verb", "Expected --verb") - self.assertEqual(verb, self.test_verb, "Expected a verb") - self.assertEqual(opt2, "--", "Expected --") - self.assertEqual( - proton, - Path(self.env.get("PROTONPATH") + "/proton").as_posix(), - "Expected the proton file", - ) - self.assertEqual(verb2, self.test_verb, "Expected a verb") - self.assertEqual(exe, self.env["EXE"], "Expected the EXE") - - def test_set_env_opts(self): - """Test set_env. - - Ensure no failures and verify that an option is passed to the executable - """ - result = None - test_str = "foo" - - # Replicate the usage WINEPREFIX= PROTONPATH= GAMEID= STORE= PROTON_VERB= ulwgl_run foo.exe -foo - with patch("sys.argv", ["", self.test_exe, test_str]): - os.environ["WINEPREFIX"] = self.test_file - os.environ["PROTONPATH"] = self.test_file - os.environ["GAMEID"] = test_str - os.environ["STORE"] = test_str - os.environ["PROTON_VERB"] = self.test_verb - # Args - result = ulwgl_run.parse_args() - self.assertIsInstance(result, tuple, "Expected a tuple") - self.assertIsInstance(result[0], str, "Expected a string") - self.assertIsInstance(result[1], list, "Expected a list as options") - self.assertEqual( - result[0], "./tmp.WMYQiPb9A/foo", "Expected EXE to be unexpanded" - ) - self.assertEqual( - *result[1], - test_str, - "Expected the test string when passed as an option", - ) - # Check - ulwgl_run.check_env(self.env) - # Prefix - ulwgl_run.setup_pfx(self.env["WINEPREFIX"]) - # Env - result = ulwgl_run.set_env(self.env, result[0:]) - self.assertTrue(result is self.env, "Expected the same reference") - - path_exe = Path(self.test_exe).expanduser().as_posix() - path_file = Path(self.test_file).expanduser().as_posix() - - # After calling set_env all paths should be expanded POSIX form - self.assertEqual(self.env["EXE"], path_exe, "Expected EXE to be expanded") - self.assertEqual(self.env["STORE"], test_str, "Expected STORE to be set") - self.assertEqual( - self.env["PROTONPATH"], path_file, "Expected PROTONPATH to be set" - ) - self.assertEqual( - self.env["WINEPREFIX"], path_file, "Expected WINEPREFIX to be set" - ) - self.assertEqual(self.env["GAMEID"], test_str, "Expected GAMEID to be set") - self.assertEqual( - self.env["PROTON_VERB"], - self.test_verb, - "Expected PROTON_VERB to be set", - ) - - def test_set_env_id(self): - """Test set_env. - - Verify that environment variables (dictionary) are set after calling set_env when passing a valid ULWGL_ID - When a valid ULWGL_ID is set, the STEAM_COMPAT_APP_ID variables should be the stripped ULWGL_ID - """ - result = None - test_str = "foo" - ulwgl_id = "ulwgl-271590" - - # Replicate the usage WINEPREFIX= PROTONPATH= GAMEID= STORE= PROTON_VERB= ulwgl_run foo.exe - with patch("sys.argv", ["", self.test_exe]): - os.environ["WINEPREFIX"] = self.test_file - os.environ["PROTONPATH"] = self.test_file - os.environ["GAMEID"] = ulwgl_id - os.environ["STORE"] = test_str - os.environ["PROTON_VERB"] = self.test_verb - # Args - result = ulwgl_run.parse_args() - self.assertIsInstance(result, tuple, "Expected a tuple") - self.assertIsInstance(result[0], str, "Expected a string") - self.assertIsInstance(result[1], list, "Expected a list as options") - self.assertEqual( - result[0], "./tmp.WMYQiPb9A/foo", "Expected EXE to be unexpanded" - ) - self.assertFalse( - result[1], "Expected an empty list when passing no options" - ) - # Check - ulwgl_run.check_env(self.env) - # Prefix - ulwgl_run.setup_pfx(self.env["WINEPREFIX"]) - # Env - result = ulwgl_run.set_env(self.env, result[0:]) - self.assertTrue(result is self.env, "Expected the same reference") - - path_exe = Path(self.test_exe).expanduser().as_posix() - path_file = Path(self.test_file).expanduser().as_posix() - - # After calling set_env all paths should be expanded POSIX form - self.assertEqual(self.env["EXE"], path_exe, "Expected EXE to be expanded") - self.assertEqual(self.env["STORE"], test_str, "Expected STORE to be set") - self.assertEqual( - self.env["PROTONPATH"], path_file, "Expected PROTONPATH to be set" - ) - self.assertEqual( - self.env["WINEPREFIX"], path_file, "Expected WINEPREFIX to be set" - ) - self.assertEqual(self.env["GAMEID"], ulwgl_id, "Expected GAMEID to be set") - self.assertEqual( - self.env["PROTON_VERB"], - self.test_verb, - "Expected PROTON_VERB to be set", - ) - # ULWGL - self.assertEqual( - self.env["ULWGL_ID"], - self.env["GAMEID"], - "Expected ULWGL_ID to be GAMEID", - ) - self.assertEqual(self.env["ULWGL_ID"], ulwgl_id, "Expected ULWGL_ID") - # Should be stripped -- everything after the hyphen - self.assertEqual( - self.env["STEAM_COMPAT_APP_ID"], - ulwgl_id[ulwgl_id.find("-") + 1 :], - "Expected STEAM_COMPAT_APP_ID to be the stripped ULWGL_ID", - ) - self.assertEqual( - self.env["SteamAppId"], - self.env["STEAM_COMPAT_APP_ID"], - "Expected SteamAppId to be STEAM_COMPAT_APP_ID", - ) - self.assertEqual( - self.env["SteamGameId"], - self.env["SteamAppId"], - "Expected SteamGameId to be STEAM_COMPAT_APP_ID", - ) - - # PATHS - self.assertEqual( - self.env["STEAM_COMPAT_SHADER_PATH"], - self.env["STEAM_COMPAT_DATA_PATH"] + "/shadercache", - "Expected STEAM_COMPAT_SHADER_PATH to be set", - ) - self.assertEqual( - self.env["STEAM_COMPAT_TOOL_PATHS"], - self.env["PROTONPATH"] + ":" + Path(__file__).parent.as_posix(), - "Expected STEAM_COMPAT_TOOL_PATHS to be set", - ) - self.assertEqual( - self.env["STEAM_COMPAT_MOUNTS"], - self.env["STEAM_COMPAT_TOOL_PATHS"], - "Expected STEAM_COMPAT_MOUNTS to be set", - ) - - def test_set_env(self): - """Test set_env. - - Verify that environment variables (dictionary) are set after calling set_env - """ - result = None - test_str = "foo" - - # Replicate the usage WINEPREFIX= PROTONPATH= GAMEID= STORE= PROTON_VERB= ulwgl_run foo.exe - with patch("sys.argv", ["", self.test_exe]): - os.environ["WINEPREFIX"] = self.test_file - os.environ["PROTONPATH"] = self.test_file - os.environ["GAMEID"] = test_str - os.environ["STORE"] = test_str - os.environ["PROTON_VERB"] = self.test_verb - # Args - result = ulwgl_run.parse_args() - self.assertIsInstance(result, tuple, "Expected a tuple") - self.assertIsInstance(result[0], str, "Expected a string") - self.assertIsInstance(result[1], list, "Expected a list as options") - self.assertEqual( - result[0], "./tmp.WMYQiPb9A/foo", "Expected EXE to be unexpanded" - ) - self.assertFalse( - result[1], "Expected an empty list when passing no options" - ) - # Check - ulwgl_run.check_env(self.env) - # Prefix - ulwgl_run.setup_pfx(self.env["WINEPREFIX"]) - # Env - result = ulwgl_run.set_env(self.env, result[0:]) - self.assertTrue(result is self.env, "Expected the same reference") - - path_exe = Path(self.test_exe).expanduser().as_posix() - path_file = Path(self.test_file).expanduser().as_posix() - - # After calling set_env all paths should be expanded POSIX form - self.assertEqual(self.env["EXE"], path_exe, "Expected EXE to be expanded") - self.assertEqual(self.env["STORE"], test_str, "Expected STORE to be set") - self.assertEqual( - self.env["PROTONPATH"], path_file, "Expected PROTONPATH to be set" - ) - self.assertEqual( - self.env["WINEPREFIX"], path_file, "Expected WINEPREFIX to be set" - ) - self.assertEqual(self.env["GAMEID"], test_str, "Expected GAMEID to be set") - self.assertEqual( - self.env["PROTON_VERB"], - self.test_verb, - "Expected PROTON_VERB to be set", - ) - # ULWGL - self.assertEqual( - self.env["ULWGL_ID"], - self.env["GAMEID"], - "Expected ULWGL_ID to be GAMEID", - ) - self.assertEqual( - self.env["STEAM_COMPAT_APP_ID"], - "0", - "Expected STEAM_COMPAT_APP_ID to be 0", - ) - self.assertEqual( - self.env["SteamAppId"], - self.env["STEAM_COMPAT_APP_ID"], - "Expected SteamAppId to be STEAM_COMPAT_APP_ID", - ) - self.assertEqual( - self.env["SteamGameId"], - self.env["SteamAppId"], - "Expected SteamGameId to be STEAM_COMPAT_APP_ID", - ) - - # PATHS - self.assertEqual( - self.env["STEAM_COMPAT_SHADER_PATH"], - self.env["STEAM_COMPAT_DATA_PATH"] + "/shadercache", - "Expected STEAM_COMPAT_SHADER_PATH to be set", - ) - self.assertEqual( - self.env["STEAM_COMPAT_TOOL_PATHS"], - self.env["PROTONPATH"] + ":" + Path(__file__).parent.as_posix(), - "Expected STEAM_COMPAT_TOOL_PATHS to be set", - ) - self.assertEqual( - self.env["STEAM_COMPAT_MOUNTS"], - self.env["STEAM_COMPAT_TOOL_PATHS"], - "Expected STEAM_COMPAT_MOUNTS to be set", - ) - - def test_setup_pfx_mv(self): - """Test setup_pfx when moving the WINEPREFIX after creating it. - - After setting up the prefix then moving it to a different path, ensure that the symbolic link points to that new location - """ - result = None - pattern = r"^/home/[\w\d]+" # Expects only unicode decimals and alphanumerics - unexpanded_path = re.sub( - pattern, - "~", - Path(self.test_file).cwd().joinpath(self.test_file).as_posix(), - ) - result = ulwgl_run.setup_pfx(unexpanded_path) - - # Replaces the expanded path to unexpanded - # Example: ~/some/path/to/this/file -> /home/foo/path/to/this/file - self.assertIsNone( - result, - "Expected None when creating symbolic link to WINE prefix and tracked_files file", - ) - self.assertTrue( - Path(self.test_file + "/pfx").is_symlink(), "Expected pfx to be a symlink" - ) - self.assertTrue( - Path(self.test_file + "/tracked_files").is_file(), - "Expected tracked_files to be a file", - ) - self.assertTrue( - Path(self.test_file + "/pfx").is_symlink(), "Expected pfx to be a symlink" - ) - - # Check if the symlink is in its unexpanded form - self.assertEqual( - Path(self.test_file + "/pfx").readlink().as_posix(), - Path(unexpanded_path).expanduser().as_posix(), - ) - - old_link = Path(self.test_file + "/pfx").resolve() - - # Rename the dir and replicate passing a new WINEPREFIX - new_dir = Path(unexpanded_path).expanduser().rename("foo") - new_unexpanded_path = re.sub( - pattern, - "~", - new_dir.cwd().joinpath("foo").as_posix(), - ) - - ulwgl_run.setup_pfx(new_unexpanded_path) - - new_link = Path("foo/pfx").resolve() - self.assertTrue( - old_link is not new_link, - "Expected the symbolic link to change after moving the WINEPREFIX", - ) - - if new_link.exists(): - rmtree(new_link.as_posix()) - - def test_setup_pfx_symlinks(self): - """Test _setup_pfx for valid symlinks. - - Ensure that symbolic links to the WINE prefix (pfx) are always in expanded form when passed an unexpanded path. - For example: - if WINEPREFIX is /home/foo/.wine - pfx -> /home/foo/.wine - - We do not want the symbolic link such as: - pfx -> ~/.wine - """ - result = None - pattern = r"^/home/[\w\d]+" # Expects only unicode decimals and alphanumerics - unexpanded_path = re.sub( - pattern, - "~", - Path(self.test_file).cwd().joinpath(self.test_file).as_posix(), - ) - result = ulwgl_run.setup_pfx(unexpanded_path) - - # Replaces the expanded path to unexpanded - # Example: ~/some/path/to/this/file -> /home/foo/path/to/this/file - self.assertIsNone( - result, - "Expected None when creating symbolic link to WINE prefix and tracked_files file", - ) - self.assertTrue( - Path(self.test_file + "/pfx").is_symlink(), "Expected pfx to be a symlink" - ) - self.assertTrue( - Path(self.test_file + "/tracked_files").is_file(), - "Expected tracked_files to be a file", - ) - self.assertTrue( - Path(self.test_file + "/pfx").is_symlink(), "Expected pfx to be a symlink" - ) - - # Check if the symlink is in its unexpanded form - self.assertEqual( - Path(self.test_file + "/pfx").readlink().as_posix(), - Path(unexpanded_path).expanduser().as_posix(), - ) - - def test_setup_pfx_paths(self): - """Test setup_pfx on unexpanded paths. - - An error should not be raised when passing paths such as ~/path/to/prefix. - """ - result = None - pattern = r"^/home/[\w\d]+" # Expects only unicode decimals and alphanumerics - unexpanded_path = re.sub( - pattern, - "~", - Path(self.test_file).as_posix(), - ) - result = ulwgl_run.setup_pfx(unexpanded_path) - - # Replaces the expanded path to unexpanded - # Example: ~/some/path/to/this/file -> /home/foo/path/to/this/file - self.assertIsNone( - result, - "Expected None when creating symbolic link to WINE prefix and tracked_files file", - ) - self.assertTrue( - Path(self.test_file + "/pfx").is_symlink(), "Expected pfx to be a symlink" - ) - self.assertTrue( - Path(self.test_file + "/tracked_files").is_file(), - "Expected tracked_files to be a file", - ) - - def test_setup_pfx(self): - """Test setup_pfx.""" - result = None - result = ulwgl_run.setup_pfx(self.test_file) - self.assertIsNone( - result, - "Expected None when creating symbolic link to WINE prefix and tracked_files file", - ) - self.assertTrue( - Path(self.test_file + "/pfx").is_symlink(), "Expected pfx to be a symlink" - ) - self.assertTrue( - Path(self.test_file + "/tracked_files").is_file(), - "Expected tracked_files to be a file", - ) - - def test_parse_args(self): - """Test parse_args with no options. - - There's a requirement to create an empty prefix - A SystemExit should be raised in this case: - ./ulwgl_run.py - """ - with self.assertRaises(SystemExit): - ulwgl_run.parse_args() - - def test_parse_args_config(self): - """Test parse_args --config.""" - with patch.object( - ulwgl_run, - "parse_args", - return_value=argparse.Namespace(config=self.test_file), - ): - result = ulwgl_run.parse_args() - self.assertIsInstance( - result, Namespace, "Expected a Namespace from parse_arg" - ) - - def test_env_proton_nodir(self): - """Test check_env when $PROTONPATH in the case we failed to set it. - - An FileNotFoundError should be raised when we fail to set PROTONPATH - """ - # Mock getting the Proton - with self.assertRaises(FileNotFoundError): - with patch.object( - ulwgl_run, - "get_ulwgl_proton", - return_value=self.env, - ): - os.environ["WINEPREFIX"] = self.test_file - os.environ["GAMEID"] = self.test_file - ulwgl_run.check_env(self.env) - - def test_env_wine_dir(self): - """Test check_env when $WINEPREFIX is not a directory. - - When the user specifies a WINEPREFIX that doesn't exist, make the dirs on their behalf and set it - An error should not be raised in the process - """ - # Set a path does not exist - os.environ["WINEPREFIX"] = "./foo" - os.environ["GAMEID"] = self.test_file - os.environ["PROTONPATH"] = self.test_file - - self.assertFalse( - Path(os.environ["WINEPREFIX"]).exists(), - "Expected WINEPREFIX to not exist before check_env", - ) - - ulwgl_run.check_env(self.env) - - # After this, the WINEPREFIX and new dirs should be created for the user - self.assertTrue( - Path(self.env["WINEPREFIX"]).exists(), - "Expected WINEPREFIX to exist after check_env", - ) - self.assertEqual( - self.env["WINEPREFIX"], - os.environ["WINEPREFIX"], - "Expected the WINEPREFIX to be set", - ) - - if Path(self.env["WINEPREFIX"]).is_dir(): - Path(self.env["WINEPREFIX"]).rmdir() - - def test_env_vars_paths(self): - """Test check_env when setting unexpanded paths for $WINEPREFIX and $PROTONPATH.""" - pattern = r"^/home/[\w\d]+" # Expects only unicode decimals and alphanumerics - path_to_tmp = Path(__file__).cwd().joinpath(self.test_file).as_posix() - - # Replace /home/[a-zA-Z]+ substring in path with tilda - unexpanded_path = re.sub( - pattern, - "~", - path_to_tmp, - ) - - result = None - os.environ["WINEPREFIX"] = unexpanded_path - os.environ["GAMEID"] = self.test_file - os.environ["PROTONPATH"] = unexpanded_path - result = ulwgl_run.check_env(self.env) - self.assertTrue(result is self.env, "Expected the same reference") - self.assertEqual( - self.env["WINEPREFIX"], unexpanded_path, "Expected WINEPREFIX to be set" - ) - self.assertEqual( - self.env["GAMEID"], self.test_file, "Expected GAMEID to be set" - ) - self.assertEqual( - self.env["PROTONPATH"], unexpanded_path, "Expected PROTONPATH to be set" - ) - - def test_env_vars(self): - """Test check_env when setting $WINEPREFIX, $GAMEID and $PROTONPATH.""" - result = None - os.environ["WINEPREFIX"] = self.test_file - os.environ["GAMEID"] = self.test_file - os.environ["PROTONPATH"] = self.test_file - result = ulwgl_run.check_env(self.env) - self.assertTrue(result is self.env, "Expected the same reference") - self.assertEqual( - self.env["WINEPREFIX"], self.test_file, "Expected WINEPREFIX to be set" - ) - self.assertEqual( - self.env["GAMEID"], self.test_file, "Expected GAMEID to be set" - ) - self.assertEqual( - self.env["PROTONPATH"], self.test_file, "Expected PROTONPATH to be set" - ) - - def test_env_vars_proton(self): - """Test check_env when setting only $WINEPREFIX and $GAMEID.""" - with self.assertRaisesRegex(FileNotFoundError, "Proton"): - os.environ["WINEPREFIX"] = self.test_file - os.environ["GAMEID"] = self.test_file - # Mock getting the Proton - with patch.object( - ulwgl_run, - "get_ulwgl_proton", - return_value=self.env, - ): - os.environ["WINEPREFIX"] = self.test_file - os.environ["GAMEID"] = self.test_file - result = ulwgl_run.check_env(self.env) - self.assertTrue(result is self.env, "Expected the same reference") - self.assertFalse(os.environ["PROTONPATH"]) - - def test_env_vars_wine(self): - """Test check_env when setting only $WINEPREFIX.""" - with self.assertRaisesRegex(ValueError, "GAMEID"): - os.environ["WINEPREFIX"] = self.test_file - ulwgl_run.check_env(self.env) - - def test_env_vars_none(self): - """Tests check_env when setting no env vars. - - GAMEID should be the only strictly required env var - """ - with self.assertRaisesRegex(ValueError, "GAMEID"): - ulwgl_run.check_env(self.env) - - -if __name__ == "__main__": - unittest.main() diff --git a/launcher/ulwgl_test_plugins.py b/launcher/ulwgl_test_plugins.py deleted file mode 100644 index 9ad84a8..0000000 --- a/launcher/ulwgl_test_plugins.py +++ /dev/null @@ -1,533 +0,0 @@ -import unittest -import ulwgl_run -import os -import argparse -from argparse import Namespace -from unittest.mock import patch -from pathlib import Path -from tomllib import TOMLDecodeError -from shutil import rmtree -import re -import ulwgl_plugins -import tarfile - - -class TestGameLauncherPlugins(unittest.TestCase): - """Test suite ulwgl_run.py plugins.""" - - def setUp(self): - """Create the test directory, exe and environment variables.""" - self.env = { - "WINEPREFIX": "", - "GAMEID": "", - "PROTON_CRASH_REPORT_DIR": "/tmp/ULWGL_crashreports", - "PROTONPATH": "", - "STEAM_COMPAT_APP_ID": "", - "STEAM_COMPAT_TOOL_PATHS": "", - "STEAM_COMPAT_LIBRARY_PATHS": "", - "STEAM_COMPAT_MOUNTS": "", - "STEAM_COMPAT_INSTALL_PATH": "", - "STEAM_COMPAT_CLIENT_INSTALL_PATH": "", - "STEAM_COMPAT_DATA_PATH": "", - "STEAM_COMPAT_SHADER_PATH": "", - "FONTCONFIG_PATH": "", - "EXE": "", - "SteamAppId": "", - "SteamGameId": "", - "STEAM_RUNTIME_LIBRARY_PATH": "", - "ULWGL_ID": "", - "STORE": "", - "PROTON_VERB": "", - } - self.test_opts = "-foo -bar" - # Proton verb - # Used when testing build_command - self.test_verb = "waitforexitandrun" - # Test directory - self.test_file = "./tmp.AKN6tnueyO" - # Executable - self.test_exe = self.test_file + "/" + "foo" - # Cache - self.test_cache = Path("./tmp.ND7tcK5m3K") - # Steam compat dir - self.test_compat = Path("./tmp.1A5cflhwQa") - # ULWGL-Proton dir - self.test_proton_dir = Path("ULWGL-Proton-jPTxUsKDdn") - # ULWGL-Proton release - self.test_archive = Path(self.test_cache).joinpath( - f"{self.test_proton_dir}.tar.gz" - ) - - self.test_cache.mkdir(exist_ok=True) - self.test_compat.mkdir(exist_ok=True) - self.test_proton_dir.mkdir(exist_ok=True) - - # Mock the proton file in the dir - self.test_proton_dir.joinpath("proton").touch(exist_ok=True) - - # Mock the release downloaded in the cache: tmp.5HYdpddgvs/ULWGL-Proton-jPTxUsKDdn.tar.gz - # Expected directory structure within the archive: - # - # +-- ULWGL-Proton-5HYdpddgvs (root directory) - # | +-- proton (normal file) - with tarfile.open(self.test_archive.as_posix(), "w:gz") as tar: - tar.add( - self.test_proton_dir.as_posix(), arcname=self.test_proton_dir.as_posix() - ) - - Path(self.test_file).mkdir(exist_ok=True) - Path(self.test_exe).touch() - - def tearDown(self): - """Unset environment variables and delete test files after each test.""" - for key, val in self.env.items(): - if key in os.environ: - os.environ.pop(key) - - if Path(self.test_file).exists(): - rmtree(self.test_file) - - if self.test_cache.exists(): - rmtree(self.test_cache.as_posix()) - - if self.test_compat.exists(): - rmtree(self.test_compat.as_posix()) - - if self.test_proton_dir.exists(): - rmtree(self.test_proton_dir.as_posix()) - - def test_build_command_nofile(self): - """Test build_command. - - A FileNotFoundError should be raised if $PROTONPATH/proton does not exist - NOTE: Also, FileNotFoundError will be raised if the _v2-entry-point (ULWGL) is not in $HOME/.local/share/ULWGL or in cwd - """ - test_toml = "foo.toml" - toml_str = f""" - [ulwgl] - prefix = "{self.test_file}" - proton = "{self.test_file}" - game_id = "{self.test_file}" - launch_args = ["{self.test_file}"] - exe = "{self.test_exe}" - """ - toml_path = self.test_file + "/" + test_toml - result = None - test_command = [] - Path(toml_path).touch() - - with Path(toml_path).open(mode="w") as file: - file.write(toml_str) - - with patch.object( - ulwgl_run, - "parse_args", - return_value=argparse.Namespace(config=toml_path), - ): - # Args - result = ulwgl_run.parse_args() - # Config - ulwgl_plugins.set_env_toml(self.env, result) - # Prefix - ulwgl_run.setup_pfx(self.env["WINEPREFIX"]) - # Env - ulwgl_run.set_env(self.env, result) - # Game drive - ulwgl_plugins.enable_steam_game_drive(self.env) - - for key, val in self.env.items(): - os.environ[key] = val - - # Build - with self.assertRaisesRegex(FileNotFoundError, "proton"): - ulwgl_run.build_command(self.env, test_command) - - def test_build_command_toml(self): - """Test build_command. - - After parsing a valid TOML file, be sure we do not raise a FileNotFoundError - """ - test_toml = "foo.toml" - toml_str = f""" - [ulwgl] - prefix = "{self.test_file}" - proton = "{self.test_file}" - game_id = "{self.test_file}" - launch_args = ["{self.test_file}", "{self.test_file}"] - exe = "{self.test_exe}" - """ - toml_path = self.test_file + "/" + test_toml - result = None - test_command = [] - test_command_result = None - - Path(self.test_file + "/proton").touch() - Path(toml_path).touch() - - with Path(toml_path).open(mode="w") as file: - file.write(toml_str) - - with patch.object( - ulwgl_run, - "parse_args", - return_value=argparse.Namespace(config=toml_path), - ): - # Args - result = ulwgl_run.parse_args() - # Config - ulwgl_plugins.set_env_toml(self.env, result) - # Prefix - ulwgl_run.setup_pfx(self.env["WINEPREFIX"]) - # Env - ulwgl_run.set_env(self.env, result) - # Game drive - ulwgl_plugins.enable_steam_game_drive(self.env) - - for key, val in self.env.items(): - os.environ[key] = val - - # Build - test_command_result = ulwgl_run.build_command(self.env, test_command) - self.assertTrue( - test_command_result is test_command, "Expected the same reference" - ) - - # Verify contents of the command - entry_point, opt1, verb, opt2, proton, verb2, exe = [*test_command] - # The entry point dest could change. Just check if there's a value - self.assertTrue(entry_point, "Expected an entry point") - self.assertEqual(opt1, "--verb", "Expected --verb") - self.assertEqual(verb, self.test_verb, "Expected a verb") - self.assertEqual(opt2, "--", "Expected --") - self.assertEqual( - proton, - Path(self.env.get("PROTONPATH") + "/proton").as_posix(), - "Expected the proton file", - ) - self.assertEqual(verb2, self.test_verb, "Expected a verb") - self.assertEqual(exe, self.env["EXE"], "Expected the EXE") - - def test_set_env_toml_opts_nofile(self): - """Test set_env_toml for options that are a file. - - An error should not be raised if a launch argument is a file - We allow this behavior to give users flexibility at the cost of security - """ - test_toml = "foo.toml" - toml_path = self.test_file + "/" + test_toml - toml_str = f""" - [ulwgl] - prefix = "{self.test_file}" - proton = "{self.test_file}" - game_id = "{self.test_file}" - launch_args = ["{toml_path}"] - exe = "{self.test_exe}" - """ - result = None - - Path(toml_path).touch() - - with Path(toml_path).open(mode="w") as file: - file.write(toml_str) - - with patch.object( - ulwgl_run, - "parse_args", - return_value=argparse.Namespace(config=toml_path), - ): - # Args - result = ulwgl_run.parse_args() - self.assertIsInstance( - result, Namespace, "Expected a Namespace from parse_arg" - ) - self.assertTrue(vars(result).get("config"), "Expected a value for --config") - # Env - ulwgl_plugins.set_env_toml(self.env, result) - - # Check if its the TOML file we just created - self.assertTrue( - Path(self.env["EXE"].split(" ")[1]).is_file(), - "Expected a file to be appended to the executable", - ) - - def test_set_env_toml_nofile(self): - """Test set_env_toml for values that are not a file. - - A FileNotFoundError should be raised if the 'exe' is not a file - """ - test_toml = "foo.toml" - toml_str = f""" - [ulwgl] - prefix = "{self.test_file}" - proton = "{self.test_file}" - game_id = "{self.test_file}" - launch_args = ["{self.test_file}", "{self.test_file}"] - exe = "./bar" - """ - toml_path = self.test_file + "/" + test_toml - result = None - - Path(toml_path).touch() - - with Path(toml_path).open(mode="w") as file: - file.write(toml_str) - - with patch.object( - ulwgl_run, - "parse_args", - return_value=argparse.Namespace(config=toml_path), - ): - # Args - result = ulwgl_run.parse_args() - self.assertIsInstance( - result, Namespace, "Expected a Namespace from parse_arg" - ) - self.assertTrue(vars(result).get("config"), "Expected a value for --config") - # Env - with self.assertRaisesRegex(FileNotFoundError, "exe"): - ulwgl_plugins.set_env_toml(self.env, result) - - def test_set_env_toml_err(self): - """Test set_env_toml for valid TOML. - - A TOMLDecodeError should be raised for invalid values - """ - test_toml = "foo.toml" - toml_str = f""" - [ulwgl] - prefix = [[ - proton = "{self.test_file}" - game_id = "{self.test_file}" - launch_args = ["{self.test_file}", "{self.test_file}"] - """ - toml_path = self.test_file + "/" + test_toml - result = None - - Path(toml_path).touch() - - with Path(toml_path).open(mode="w") as file: - file.write(toml_str) - - with patch.object( - ulwgl_run, - "parse_args", - return_value=argparse.Namespace(config=toml_path), - ): - # Args - result = ulwgl_run.parse_args() - self.assertIsInstance( - result, Namespace, "Expected a Namespace from parse_arg" - ) - # Env - with self.assertRaisesRegex(TOMLDecodeError, "Invalid"): - ulwgl_plugins.set_env_toml(self.env, result) - - def test_set_env_toml_nodir(self): - """Test set_env_toml if certain key/value are not a dir. - - An IsDirectoryError should be raised if the following keys are not dir: proton, prefix - """ - test_toml = "foo.toml" - toml_str = f""" - [ulwgl] - prefix = "foo" - proton = "foo" - game_id = "{self.test_file}" - launch_args = ["{self.test_file}", "{self.test_file}"] - """ - toml_path = self.test_file + "/" + test_toml - result = None - - Path(toml_path).touch() - - with Path(toml_path).open(mode="w") as file: - file.write(toml_str) - - with patch.object( - ulwgl_run, - "parse_args", - return_value=argparse.Namespace(config=toml_path), - ): - # Args - result = ulwgl_run.parse_args() - self.assertIsInstance( - result, Namespace, "Expected a Namespace from parse_arg" - ) - self.assertTrue(vars(result).get("config"), "Expected a value for --config") - # Env - with self.assertRaisesRegex(NotADirectoryError, "proton"): - ulwgl_plugins.set_env_toml(self.env, result) - - def test_set_env_toml_tables(self): - """Test set_env_toml for expected tables. - - A ValueError should be raised if the following tables are absent: ulwgl - """ - test_toml = "foo.toml" - toml_str = f""" - [foo] - prefix = "{self.test_file}" - proton = "{self.test_file}" - game_id = "{self.test_file}" - launch_args = ["{self.test_file}", "{self.test_file}"] - """ - toml_path = self.test_file + "/" + test_toml - result = None - - Path(toml_path).touch() - - with Path(toml_path).open(mode="w") as file: - file.write(toml_str) - - with patch.object( - ulwgl_run, - "parse_args", - return_value=argparse.Namespace(config=toml_path), - ): - # Args - result = ulwgl_run.parse_args() - self.assertIsInstance( - result, Namespace, "Expected a Namespace from parse_arg" - ) - self.assertTrue(vars(result).get("config"), "Expected a value for --config") - # Env - with self.assertRaisesRegex(ValueError, "ulwgl"): - ulwgl_plugins.set_env_toml(self.env, result) - - def test_set_env_toml_paths(self): - """Test set_env_toml when specifying unexpanded file path values in the config file. - - Example: ~/Games/foo.exe - An error should not be raised when passing unexpanded paths to the config file as well as the prefix, proton and exe keys - """ - test_toml = "foo.toml" - pattern = r"^/home/[\w\d]+" # Expects only unicode decimals and alphanumerics - - # Replaces the expanded path to unexpanded - # Example: ~/some/path/to/this/file -> /home/foo/path/to/this/file - path_to_tmp = Path( - Path(__file__).cwd().as_posix() + "/" + self.test_file - ).as_posix() - path_to_exe = Path( - Path(__file__).cwd().as_posix() + "/" + self.test_exe - ).as_posix() - - # Replace /home/[a-zA-Z]+ substring in path with tilda - unexpanded_path = re.sub( - pattern, - "~", - path_to_tmp, - ) - unexpanded_exe = re.sub( - pattern, - "~", - path_to_exe, - ) - toml_str = f""" - [ulwgl] - prefix = "{unexpanded_path}" - proton = "{unexpanded_path}" - game_id = "{unexpanded_path}" - exe = "{unexpanded_exe}" - """ - # Path to TOML in unexpanded form - toml_path = unexpanded_path + "/" + test_toml - result = None - result_set_env = None - - Path(toml_path).expanduser().touch() - - with Path(toml_path).expanduser().open(mode="w") as file: - file.write(toml_str) - - with patch.object( - ulwgl_run, - "parse_args", - return_value=argparse.Namespace(config=toml_path), - ): - # Args - result = ulwgl_run.parse_args() - self.assertIsInstance( - result, Namespace, "Expected a Namespace from parse_arg" - ) - self.assertTrue(vars(result).get("config"), "Expected a value for --config") - # Env - result_set_env = ulwgl_plugins.set_env_toml(self.env, result) - self.assertTrue(result_set_env is self.env, "Expected the same reference") - - # Check that the paths are still in the unexpanded form after setting the env - # In main, we only expand them after this function exits to prepare for building the command - self.assertEqual( - self.env["EXE"], unexpanded_exe, "Expected path not to be expanded" - ) - self.assertEqual( - self.env["PROTONPATH"], - unexpanded_path, - "Expected path not to be expanded", - ) - self.assertEqual( - self.env["WINEPREFIX"], - unexpanded_path, - "Expected path not to be expanded", - ) - self.assertEqual( - self.env["GAMEID"], unexpanded_path, "Expectd path not to be expanded" - ) - - def test_set_env_toml(self): - """Test set_env_toml.""" - test_toml = "foo.toml" - toml_str = f""" - [ulwgl] - prefix = "{self.test_file}" - proton = "{self.test_file}" - game_id = "{self.test_file}" - launch_args = ["{self.test_file}", "{self.test_file}"] - exe = "{self.test_exe}" - """ - toml_path = self.test_file + "/" + test_toml - result = None - result_set_env = None - - Path(toml_path).touch() - - with Path(toml_path).open(mode="w") as file: - file.write(toml_str) - - with patch.object( - ulwgl_run, - "parse_args", - return_value=argparse.Namespace(config=toml_path), - ): - # Args - result = ulwgl_run.parse_args() - self.assertIsInstance( - result, Namespace, "Expected a Namespace from parse_arg" - ) - self.assertTrue(vars(result).get("config"), "Expected a value for --config") - # Env - result_set_env = ulwgl_plugins.set_env_toml(self.env, result) - self.assertTrue(result_set_env is self.env, "Expected the same reference") - self.assertTrue(self.env["EXE"], "Expected EXE to be set") - self.assertEqual( - self.env["EXE"], - self.test_exe + " " + " ".join([self.test_file, self.test_file]), - "Expectd GAMEID to be set", - ) - self.assertEqual( - self.env["PROTONPATH"], - self.test_file, - "Expected PROTONPATH to be set", - ) - self.assertEqual( - self.env["WINEPREFIX"], - self.test_file, - "Expected WINEPREFIX to be set", - ) - self.assertEqual( - self.env["GAMEID"], self.test_file, "Expectd GAMEID to be set" - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/launcher/ulwgl_util.py b/launcher/ulwgl_util.py deleted file mode 100644 index b096059..0000000 --- a/launcher/ulwgl_util.py +++ /dev/null @@ -1,20 +0,0 @@ -from ulwgl_consts import Color, Level -from typing import Any - - -def msg(msg: Any, level: Level): - """Return a log message depending on the log level. - - The message will bolden the typeface and apply a color. - Expects the first parameter to be a string or implement __str__ - """ - log: str = "" - - if level == Level.INFO: - log = f"{Color.BOLD.value}{Color.INFO.value}{msg}{Color.RESET.value}" - elif level == Level.WARNING: - log = f"{Color.BOLD.value}{Color.WARNING.value}{msg}{Color.RESET.value}" - elif level == Level.DEBUG: - log = f"{Color.BOLD.value}{Color.DEBUG.value}{msg}{Color.RESET.value}" - - return log diff --git a/libs/customvars_loader.py b/libs/customvars_loader.py new file mode 100644 index 0000000..a64099d --- /dev/null +++ b/libs/customvars_loader.py @@ -0,0 +1,22 @@ +import os +import json + +def set_customvars(provided_customvars, default_customvars): + customvars = default_customvars + # Support for the argument (overrides the env var) + if provided_customvars: + try: + provided_customvars = json.loads(provided_customvars) + except json.JSONDecodeError: + print(f"[ERROR] [CUSTOMVARS] Provided CUSTOMVARS={provided_customvars} is not a valid JSON") + print("[ERROR] [CUSTOMVARS] Defaulting to " + str(customvars)) + provided_customvars = customvars + print(f"[INFO] [CUSTOMVARS] Provided CUSTOMVARS={provided_customvars}") + customvars = provided_customvars + else: + print("[WARNING] [CUSTOMVARS] CUSTOMVARS is not set. Using default value: '" + str(customvars) + "'") + # Setting the env vars + for key, value in customvars.items(): + os.environ[key] = value + # Force check for the launcher at least + return customvars \ No newline at end of file diff --git a/libs/filepath_loader.py b/libs/filepath_loader.py new file mode 100644 index 0000000..f7fbe52 --- /dev/null +++ b/libs/filepath_loader.py @@ -0,0 +1,41 @@ +#import dotenv +import os +import libs.mustExist as sanity +mustExist = sanity.mustExist + +# SECTION Loading the .env file +#dotenv.load_dotenv() + +# NOTE Sanity check for the game path +def set_filepath(provided_filepath, LAUNCHDIR): + # Support for the argument (overrides the env var) + if not provided_filepath: + if "FILEPATH" not in os.environ: + print("[ERROR] [FILEPATH] FILEPATH is not set. Exiting...") + exit(1) + else: + provided_filepath = os.environ["FILEPATH"] + # Get the path to the file + # Distinguish between absolute and relative paths + if os.path.isabs(provided_filepath): + filepath = provided_filepath + else: + filepath = os.path.join(LAUNCHDIR, provided_filepath) + + # Check if the file exists + if not mustExist(filepath, fatal=False): + print(f"[WARNING] [FILEPATH] File not found: {filepath}") + print("[WARNING] [FILEPATH] ulwgl will be launched with the argument provided but it may not work.") + print("[INFO] [FILEPATH] Disregard this message if you are using an internal or custom binary (e.g. winecfg...)") + filepath=provided_filepath + print("[OK] [FILEPATH] " + filepath + "\n") + + # Quick space sanity check + if " " in filepath: + print(f"[WARNING] [FILEPATH] {filepath} contains spaces") + print("[WARNING] [FILEPATH] This may cause issues with the launcher") + print("[WARNING] [FILEPATH] Consider renaming the file or moving it to a different location") + print("[QUICK FIX] [FILEPATH] Trying to escape the spaces") + filepath = filepath.replace(" ", "\ ") + print(f"[QUICK FIX] [FILEPATH] {filepath}\n") + return filepath diff --git a/libs/gameid_loader.py b/libs/gameid_loader.py new file mode 100644 index 0000000..1d00792 --- /dev/null +++ b/libs/gameid_loader.py @@ -0,0 +1,33 @@ +#import dotenv +import os + +# SECTION Loading the .env file +#dotenv.load_dotenv() + +# NOTE Support for game_id +def load_gameid(provided_game_id, default_game_id, ids): + game_id = default_game_id + if provided_game_id: + game_id = provided_game_id + print(f"[INFO] [GAMEID] Provided GAMEID={game_id}") + else: + # We need a valid GAMEID in the .env file in this case + if "GAMEID" not in os.environ: + print("[WARNING] [GAMEID] GAMEID is not set. Using default value: '" + str(game_id) + "'") + else: + game_id = os.environ["GAMEID"] + print(f"[INFO] [GAMEID] GAMEID={game_id}") + + # If is not a digit, we will try to load from the ids object + if not str(game_id).isdigit(): + print(f"[INFO] [GAMEID] {game_id} is not a digit: trying to load from ids.json file") + if str(game_id) in ids: + game_id = ids[str(game_id)] + print(f"[OK] [GAMEID] GAMEID={game_id}") + else: + print(f"[WARNING] [GAMEID] {game_id} is not in the ids.json file") + print("[WARNING] [GAMEID] Defaulting to 0") + game_id = 0 + print(f"[OK] [GAMEID] GAMEID={game_id}") + return game_id + diff --git a/libs/ids_loader.py b/libs/ids_loader.py new file mode 100644 index 0000000..d9d3580 --- /dev/null +++ b/libs/ids_loader.py @@ -0,0 +1,33 @@ +#import dotenv +import os +import json +import libs.mustExist as sanity +mustExist = sanity.mustExist + +# SECTION Loading the .env file +# dotenv.load_dotenv() + +# NOTE Loading ids.json file +def load_ids(provided_ids, default_ids, default_ids_json_path): + ids_json_path = default_ids_json_path + ids = default_ids + # Support for the argument (overrides the env var) + if not provided_ids: + if "IDS_JSON" in os.environ: + ids_json_path = os.environ["IDS_JSON"] + print(f"[INFO] [IDS] IDS_JSON={ids_json_path}") + else: + print(f"[WARNING] [IDS] IDS_JSON is not set. Using default value: {default_ids_json_path}") + if provided_ids: + ids_json_path = provided_ids + if not mustExist(ids_json_path, fatal=False): + print(f"[WARNING] [IDS] Using default value: {default_ids_json_path}") + ids_json_path = default_ids_json_path + print(f"[OK] [IDS] IDS_JSON={ids_json_path}") + ids = json.loads(open(ids_json_path, "r").read()) + # Support for non existing ids.json file + if not mustExist(ids_json_path, fatal=False): + print(f"[WARNING] [IDS] {ids_json_path} does not exist") + print("[WARNING] [IDS] Defaulting to 0 will be used in case of non digit game_id") + ids = {} + return ids, ids_json_path \ No newline at end of file diff --git a/libs/mustExist.py b/libs/mustExist.py new file mode 100644 index 0000000..1241633 --- /dev/null +++ b/libs/mustExist.py @@ -0,0 +1,36 @@ +import os +import sys + +# Helper +def mustExist(path, fatal=True, is_dir=False): + # Sanitize the path + path = os.path.expanduser(path) + path = path.strip() + # Determine if the path is absolute or relative + if not os.path.isabs(path): + print(f"[INFO] [FILECHECK] {path} is a relative path") + path = os.path.abspath(path) + print(f"[INFO] [FILECHECK] Now it is an absolute path: {path}") + else: + print(f"[INFO] [FILECHECK] {path} is an absolute path") + if is_dir: + print(f"[INFO] [FILECHECK] Checking if '{path}' is a directory") + if not os.path.isdir(path): + print(f"[ERROR] [FILECHECK] '{path}' directory does not exist") + if fatal: + sys.exit(1) + else: + return False + else: + print(f"[OK] [FILECHECK] '{path}' is a directory") + return True + else: + if not os.path.exists(path): + print(f"[ERROR] [FILECHECK] '{path}' does not exist") + if fatal: + sys.exit(1) + else: + return False + else: + print(f"[OK] [FILECHECK] '{path}' exists") + return True \ No newline at end of file diff --git a/libs/postdirectives_loader.py b/libs/postdirectives_loader.py new file mode 100644 index 0000000..2ef27e0 --- /dev/null +++ b/libs/postdirectives_loader.py @@ -0,0 +1,22 @@ +import os +#import dotenv +import libs.mustExist as sanity +mustExist = sanity.mustExist + +# SECTION Loading the .env file +#dotenv.load_dotenv() + +def set_postdirectives(provided_postdirectives): + postdirectives = "" + # Support for the argument (overrides the env var) + if provided_postdirectives is not None and provided_postdirectives != "": + print(f"[INFO] [POSTDIRECTIVES] Provided ULWGLDIR={provided_postdirectives}") + postdirectives = provided_postdirectives + else: + print("[INFO] [POSTDIRECTIVES] POSTDIRECTIVES is not set. Looking for the env var...") + if "POSTDIRECTIVES" not in os.environ: + print("[WARNING] [POSTDIRECTIVES] POSTDIRECTIVES is not set. Using default value: '" + postdirectives + "'") + else: + postdirectives = os.environ["POSTDIRECTIVES"] + print(f"[INFO] [POSTDIRECTIVES] POSTDIRECTIVES={postdirectives}") + return postdirectives \ No newline at end of file diff --git a/libs/predirectives_loader.py b/libs/predirectives_loader.py new file mode 100644 index 0000000..c592ebd --- /dev/null +++ b/libs/predirectives_loader.py @@ -0,0 +1,22 @@ +import os +#import dotenv +import libs.mustExist as sanity +mustExist = sanity.mustExist + +# SECTION Loading the .env file +#dotenv.load_dotenv() + +def set_predirectives(provided_predirectives): + predirectives = "" + # Support for the argument (overrides the env var) + if provided_predirectives is not None and provided_predirectives != "": + print(f"[INFO] [PREDIRECTIVES] Provided ULWGLDIR={provided_predirectives}") + predirectives = provided_predirectives + else: + print("[INFO] [PREDIRECTIVES] PREDIRECTIVES is not set. Looking for the env var...") + if "PREDIRECTIVES" not in os.environ: + print("[WARNING] [PREDIRECTIVES] PREDIRECTIVES is not set. Using default value: '" + predirectives + "'") + else: + predirectives = os.environ["PREDIRECTIVES"] + print(f"[INFO] [PREDIRECTIVES] PREDIRECTIVES={predirectives}") + return predirectives \ No newline at end of file diff --git a/libs/protonpath_loader.py b/libs/protonpath_loader.py new file mode 100644 index 0000000..377d916 --- /dev/null +++ b/libs/protonpath_loader.py @@ -0,0 +1,36 @@ +import os +#import dotenv +import libs.mustExist as sanity +mustExist = sanity.mustExist + +# SECTION Loading the .env file +#dotenv.load_dotenv() + +# NOTE Setting the Proton path with a fallback +def set_protonpath(provided_protonpath, default_proton_path, UWINEDIR): + proton_path = default_proton_path + # Support for the argument (overrides the env var) + if provided_protonpath: + proton_path = provided_protonpath + print(f"[INFO] Provided PROTONPATH={proton_path}") + # Distinguish between absolute and relative paths to support versioning + if not os.path.isabs(proton_path): + print(f"[INFO] {proton_path} is a relative path") + print(f"[INFO] Appending {UWINEDIR}/protons/ to {proton_path}") + proton_path = os.path.join(UWINEDIR, "protons", proton_path) + print("[INFO] Now it is an absolute path: " + proton_path) + if not mustExist(proton_path, fatal=False): + print(f"[WARNING] {proton_path} does not exist") + print("[WARNING] Defaulting to " + default_proton_path) + proton_path = default_proton_path + else: + # We need a valid PROTONPATH in the .env file in this case + if "PROTONPATH" not in os.environ: + print("[WARNING] PROTONPATH is not set. Using default value: '" + proton_path + "'") + else: + proton_path = os.environ["PROTONPATH"] + print(f"[INFO] PROTONPATH={proton_path}") + # We need this to exist + mustExist(proton_path) + print(f"[OK] PROTONPATH={proton_path}") + return proton_path \ No newline at end of file diff --git a/libs/ulwgl_loader.py b/libs/ulwgl_loader.py new file mode 100644 index 0000000..255fbbf --- /dev/null +++ b/libs/ulwgl_loader.py @@ -0,0 +1,30 @@ +import os +#import dotenv +import libs.mustExist as sanity +mustExist = sanity.mustExist + +# SECTION Loading the .env file +#dotenv.load_dotenv() + +def set_ulwgldir(provided_ulwgldir, default_ulwgl_dir): + ulwgl_dir = default_ulwgl_dir + # Support for the argument (overrides the env var) + if provided_ulwgldir: + print(f"[INFO] [ULWGLDIR] Provided ULWGLDIR={provided_ulwgldir}") + if not mustExist(provided_ulwgldir, fatal=False, is_dir=True): + print(f"[WARNING] [ULWGLDIR] {provided_ulwgldir} does not exist") + print("[WARNING] [ULWGLDIR] Defaulting to " + default_ulwgl_dir) + ulwgl_dir = default_ulwgl_dir + else: + ulwgl_dir = provided_ulwgldir + else: + # We need a valid UWINEDIR in the .env file in this case + if "ULWLGDIR" not in os.environ: + print("[WARNING] [ULWGLDIR] ULWGLDIR is not set. Using default value: '" + ulwgl_dir + "'") + else: + ulwgl_dir = os.environ["ULWLGDIR"] + print(f"[INFO] [ULWGLDIR] ULWGLDIR={ulwgl_dir}") + + # Force check for the launcher at least + mustExist(ulwgl_dir + "/ULWGL") + return ulwgl_dir \ No newline at end of file diff --git a/libs/ulwlg_runner.py b/libs/ulwlg_runner.py new file mode 100644 index 0000000..1e43c32 --- /dev/null +++ b/libs/ulwlg_runner.py @@ -0,0 +1,42 @@ +import os + +def ulwlg_run(executable_path, ulwlg_dir, proton_dir, wineprefix, game_id): + + executable_dir = os.path.dirname(executable_path) + + os.environ["ULWGL_ID"] = str(game_id) + os.environ["STEAM_COMPAT_APP_ID"] = "0" # REVIEW Is this ok? + os.environ["SteamAppId"] = os.environ["STEAM_COMPAT_APP_ID"] + os.environ["SteamGameId"] = os.environ["STEAM_COMPAT_APP_ID"] + + os.environ["PROTON_VERB"] = "waitforexitandrun" + + os.environ["STEAM_COMPAT_CLIENT_INSTALL_PATH"] = "" + os.environ["STEAM_COMPAT_DATA_PATH"] = wineprefix + os.environ["STEAM_COMPAT_SHADER_PATH"] = wineprefix + "/shadercache" + + os.environ["PROTON_CRASH_REPORT_DIR"] = "/tmp/ULWGL_crashreports" + os.environ["FONTCONFIG_PATH"] = "" + + os.environ["STEAM_COMPAT_TOOL_PATHS"] = proton_dir + ":" + ulwlg_dir + os.environ["STEAM_COMPAT_MOUNTS"] = proton_dir + ":" + ulwlg_dir + + if os.environ.get("STEAM_COMPAT_INSTALL_PATH") is None: + os.environ["STEAM_COMPAT_INSTALL_PATH"] = executable_dir + + + # Composing the command + composed_command = ( + ulwlg_dir + + "/ULWGL --verb=waitforexitandrun" + + " -- " + + proton_dir + + "/proton waitforexitandrun " + + executable_path + + "$@" + ) + + print("[ULWGL_RUNNER] Composed command: " + composed_command) + + + os.system(composed_command) diff --git a/libs/wineprefix_loader.py b/libs/wineprefix_loader.py new file mode 100644 index 0000000..fd3f6bb --- /dev/null +++ b/libs/wineprefix_loader.py @@ -0,0 +1,30 @@ +#import dotenv +import os +import libs.mustExist as sanity +mustExist = sanity.mustExist + +# SECTION Loading the .env file +#dotenv.load_dotenv() + +# NOTE Setting the Wine prefix with a fallback +def set_wineprefix(provided_wineprefix, default_wine_prefix): + wine_prefix = default_wine_prefix + # Support for the argument (overrides the env var) + if provided_wineprefix: + wine_prefix = provided_wineprefix + print(f"[INFO] [WINEPREFIX] Provided WINEPREFIX={wine_prefix}") + if not mustExist(wine_prefix, fatal=False): + print(f"[WARNING] [WINEPREFIX] {wine_prefix} does not exist") + print("[WARNING] [WINEPREFIX] Defaulting to " + default_wine_prefix) + wine_prefix = default_wine_prefix + else: + # We need a valid WINEPREFIX in the .env file in this case + if "WINEPREFIX" not in os.environ: + print("[WARNING] [WINEPREFIX] WINEPREFIX is not set. Using default value: '" + wine_prefix + "'") + else: + wine_prefix = os.environ["WINEPREFIX"] + print(f"[INFO] [WINEPREFIX] WINEPREFIX={wine_prefix}") + # We need this to exist + mustExist(wine_prefix) + print(f"[OK] [WINEPREFIX] WINEPREFIX={wine_prefix}") + return wine_prefix diff --git a/protons/this_folder_will_contain_proton_instances b/protons/this_folder_will_contain_proton_instances new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b6599c7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +dotenv +tabulate diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..828c268de71b5a573fd6e718fe70520c80d78000 GIT binary patch literal 157620 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