mirror of
https://github.com/tcsenpai/UWINE.git
synced 2025-06-03 01:50:06 +00:00
Merged UWINE into the repo
This commit is contained in:
parent
4f6f8eeaed
commit
69a50bf7b7
8
.gitignore
vendored
8
.gitignore
vendored
@ -1 +1,7 @@
|
||||
var
|
||||
launcher
|
||||
.git
|
||||
.env
|
||||
__pycache__
|
||||
test_launcher
|
||||
var
|
||||
_not_needed_
|
0
PREFIX/this_folder_will_be_the_wine_prefix
Normal file
0
PREFIX/this_folder_will_be_the_wine_prefix
Normal file
346
README.md
Executable file
346
README.md
Executable file
@ -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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
13
env.example
Normal file
13
env.example
Normal file
@ -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/<youruser>/uwine/protons/GE-Proton8-32"
|
||||
WINEPREFIX="/home/<youruser>/uwine/PREFIX"
|
||||
GAMEID="0"
|
||||
ULWLGDIR="/home/<youruser>/uwine/launcher"
|
||||
FILEPATH="regedit"
|
||||
CUSTOMVARS='{
|
||||
"DVXK": "1"
|
||||
}'
|
@ -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=<wine-prefix-path> GAMEID=<ulwgl-id> PROTONPATH=<proton-version-path> ./ulwgl-run <executable-path> <arguements>`
|
||||
|
||||
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
|
||||
<https://gitlab.steamos.cloud/steamrt/steam-runtime-tools/-/blob/main/docs/container-runtime.md>
|
||||
and
|
||||
<https://gitlab.steamos.cloud/steamrt/steamrt/-/blob/steamrt/sniper/README.md>
|
||||
|
||||
Release notes
|
||||
-------------
|
||||
|
||||
Please see
|
||||
<https://gitlab.steamos.cloud/steamrt/steamrt/-/wikis/Sniper-release-notes>
|
||||
|
||||
Known issues
|
||||
------------
|
||||
|
||||
Please see
|
||||
<https://github.com/ValveSoftware/steam-runtime/blob/master/doc/steamlinuxruntime-known-issues.md>
|
||||
|
||||
Reporting bugs
|
||||
--------------
|
||||
|
||||
Please see
|
||||
<https://github.com/ValveSoftware/steam-runtime/blob/master/doc/reporting-steamlinuxruntime-bugs.md>
|
||||
|
||||
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
|
||||
<https://gitlab.steamos.cloud/steamrt/steam-runtime-tools/-/blob/main/docs/distro-assumptions.md>
|
||||
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 <https://gitlab.steamos.cloud/steamrt/sniper/sdk>
|
||||
and general information for game developers
|
||||
<https://gitlab.steamos.cloud/steamrt/steam-runtime-tools/-/blob/main/docs/slr-for-game-developers.md>.
|
||||
|
||||
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
|
||||
<https://repo.steampowered.com/steamrt-images-sniper/snapshots/>
|
||||
corresponding to the version numbers listed in VERSIONS.txt.
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
@ -1 +0,0 @@
|
||||
../../../ULWGL/ulwgl-run
|
Binary file not shown.
@ -1,183 +0,0 @@
|
||||
id: org.openwinecomponents.ulwgl.launcher
|
||||
runtime: org.freedesktop.Platform
|
||||
runtime-version: &runtime-version '23.08'
|
||||
x-gl-version: &gl-version '1.4'
|
||||
x-gl-versions: &gl-versions 23.08;23.08-extra;1.4
|
||||
x-gl-merge-dirs: &gl-merge-dirs vulkan/icd.d;glvnd/egl_vendor.d;OpenCL/vendors;lib/dri;lib/d3d;vulkan/explicit_layer.d;vulkan/implicit_layer.d
|
||||
sdk: org.freedesktop.Sdk
|
||||
command: ulwgl-run
|
||||
separate-locales: false
|
||||
|
||||
sdk-extensions:
|
||||
- org.freedesktop.Sdk.Compat.i386
|
||||
- org.freedesktop.Sdk.Extension.toolchain-i386
|
||||
|
||||
finish-args:
|
||||
- --allow=devel
|
||||
- --allow=multiarch
|
||||
- --device=all
|
||||
- --allow=bluetooth
|
||||
- --allow=per-app-dev-shm
|
||||
- --env=PATH=/app/bin:/app/utils/bin:/usr/bin:/usr/lib/extensions/vulkan/MangoHud/bin:/usr/lib/extensions/vulkan/gamescope/bin:/usr/lib/extensions/vulkan/OBSVkCapture/bin
|
||||
- --filesystem=xdg-data/lutris:rw
|
||||
- --filesystem=xdg-data/Steam:rw
|
||||
- --filesystem=xdg-data/applications:rw
|
||||
- --filesystem=~/.steam:rw
|
||||
- --filesystem=~/Games:rw
|
||||
- --filesystem=~/.local/share:rw
|
||||
- --filesystem=~/.var/app/com.valvesoftware.Steam:rw
|
||||
- --filesystem=~/.var/app/org.openwinecomponents.ulwgl.launcher:rw
|
||||
- --filesystem=xdg-documents
|
||||
- --filesystem=xdg-desktop
|
||||
- --env=TZ=
|
||||
- --unset-env=TZ
|
||||
- --env=LC_ADDRESS=C
|
||||
- --env=LC_COLLATE=C
|
||||
- --env=LC_MONETARY=C
|
||||
- --env=LC_MEASUREMENT=C
|
||||
- --env=LC_NAME=C
|
||||
- --env=LC_NUMERIC=C
|
||||
- --env=LC_TELEPHONE=C
|
||||
- --env=SDL_VIDEODRIVER=
|
||||
- --unset-env=SDL_VIDEODRIVER
|
||||
- --env=DBUS_FATAL_WARNINGS=0
|
||||
- --env=XDG_CONFIG_DIRS=/etc/xdg:/usr/lib/x86_64-linux-gnu/GL:/usr/lib/i386-linux-gnu/GL
|
||||
# Wine uses UDisks2 to enumerate disk drives
|
||||
- --system-talk-name=org.freedesktop.UDisks2
|
||||
# should fix access to SD card on the deck
|
||||
- --filesystem=/run/media
|
||||
# There are still quite a few users using /mnt/ for external drives
|
||||
- --filesystem=/mnt
|
||||
# should fix steamdeck controler navigation
|
||||
- --filesystem=/run/udev:ro
|
||||
# should fix discord rich presence
|
||||
- --filesystem=xdg-run/app/com.discordapp.Discord:create
|
||||
- --persist=.
|
||||
- --share=ipc
|
||||
- --socket=wayland
|
||||
- --socket=x11
|
||||
- --socket=pulseaudio
|
||||
- --share=network
|
||||
- --talk-name=org.freedesktop.Notifications
|
||||
- --talk-name=org.kde.StatusNotifierWatcher
|
||||
# Required for bwrap to work
|
||||
- --talk-name=org.freedesktop.portal.Background
|
||||
|
||||
add-extensions:
|
||||
org.freedesktop.Platform.Compat.i386:
|
||||
directory: lib/i386-linux-gnu
|
||||
version: *runtime-version
|
||||
|
||||
org.freedesktop.Platform.Compat.i386.Debug:
|
||||
directory: lib/debug/lib/i386-linux-gnu
|
||||
version: *runtime-version
|
||||
no-autodownload: true
|
||||
|
||||
org.freedesktop.Platform.GL32:
|
||||
directory: lib/i386-linux-gnu/GL
|
||||
version: *gl-version
|
||||
versions: *gl-versions
|
||||
subdirectories: true
|
||||
no-autodownload: true
|
||||
autodelete: false
|
||||
add-ld-path: lib
|
||||
merge-dirs: *gl-merge-dirs
|
||||
download-if: active-gl-driver
|
||||
enable-if: active-gl-driver
|
||||
autoprune-unless: active-gl-driver
|
||||
|
||||
org.freedesktop.Platform.GL32.Debug:
|
||||
directory: lib/debug/lib/i386-linux-gnu/GL
|
||||
version: *gl-version
|
||||
versions: *gl-versions
|
||||
subdirectories: true
|
||||
no-autodownload: true
|
||||
merge-dirs: *gl-merge-dirs
|
||||
enable-if: active-gl-driver
|
||||
autoprune-unless: active-gl-driver
|
||||
|
||||
org.freedesktop.Platform.VAAPI.Intel.i386:
|
||||
directory: lib/i386-linux-gnu/dri/intel-vaapi-driver
|
||||
version: *runtime-version
|
||||
versions: *runtime-version
|
||||
autodelete: false
|
||||
no-autodownload: true
|
||||
add-ld-path: lib
|
||||
download-if: have-intel-gpu
|
||||
autoprune-unless: have-intel-gpu
|
||||
|
||||
org.freedesktop.Platform.ffmpeg-full:
|
||||
directory: lib/ffmpeg
|
||||
add-ld-path: .
|
||||
version: *runtime-version
|
||||
no-autodownload: true
|
||||
autodelete: false
|
||||
|
||||
org.freedesktop.Platform.ffmpeg_full.i386:
|
||||
directory: lib32/ffmpeg
|
||||
add-ld-path: .
|
||||
version: *runtime-version
|
||||
no-autodownload: true
|
||||
autodelete: false
|
||||
|
||||
com.valvesoftware.Steam.CompatibilityTool:
|
||||
subdirectories: true
|
||||
directory: share/steam/compatibilitytools.d
|
||||
version: stable
|
||||
versions: stable;beta;test
|
||||
no-autodownload: true
|
||||
autodelete: true
|
||||
|
||||
com.valvesoftware.Steam.Utility:
|
||||
subdirectories: true
|
||||
directory: utils
|
||||
version: stable
|
||||
versions: stable;beta;test
|
||||
add-ld-path: lib
|
||||
merge-dirs: bin;lib/python3.10/site-packages;share/vulkan/explicit_layer.d;share/vulkan/implicit_layer.d;share/steam/compatibilitytools.d;
|
||||
no-autodownload: true
|
||||
autodelete: true
|
||||
|
||||
modules:
|
||||
# --- ULWGL ---
|
||||
- name: ulwgl-run
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- install -D ulwgl-run-cli /app/bin/ulwgl-run
|
||||
- install -D ULWGL-launcher.tar.gz /app/share/ULWGL/ULWGL-launcher.tar.gz
|
||||
sources:
|
||||
- type: file
|
||||
path: ulwgl-run-cli
|
||||
- type: file
|
||||
url: https://github.com/Open-Wine-Components/ULWGL-launcher/releases/download/0.1-RC3/ULWGL-launcher.tar.gz
|
||||
sha256: e25c4dd0636d04e7c8c534cf3c5bbdca5ae0d49f146ee8395306174700899952
|
||||
|
||||
- name: platform-bootstrap
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- |
|
||||
set -e
|
||||
mkdir -p /app/bin
|
||||
mkdir -p /app/lib/i386-linux-gnu
|
||||
mkdir -p /app/lib/i386-linux-gnu/GL
|
||||
mkdir -p /app/lib/i386-linux-gnu/dri/intel-vaapi-driver
|
||||
mkdir -p /app/lib/debug/lib/i386-linux-gnu
|
||||
mkdir -p /app/lib/debug/lib/i386-linux-gnu/GL
|
||||
install -Dm644 -t /app/etc ld.so.conf
|
||||
mkdir -p /app/lib{,32}/ffmpeg
|
||||
mkdir -p /app/share/steam/compatibilitytools.d
|
||||
mkdir -p /app/utils /app/share/vulkan
|
||||
ln -srv /app/{utils/,}share/vulkan/explicit_layer.d
|
||||
ln -srv /app/{utils/,}share/vulkan/implicit_layer.d
|
||||
mkdir -p /app/links/lib
|
||||
ln -srv /app/lib /app/links/lib/x86_64-linux-gnu
|
||||
ln -srv /app/lib32 /app/links/lib/i386-linux-gnu
|
||||
sources:
|
||||
- type: inline
|
||||
dest-filename: ld.so.conf
|
||||
contents: |
|
||||
# We just make any GL32 extension have higher priority
|
||||
include /run/flatpak/ld.so.conf.d/app-*-org.freedesktop.Platform.GL32.*.conf
|
||||
/app/lib32
|
||||
/app/lib/i386-linux-gnu
|
||||
/lib64
|
@ -1,77 +0,0 @@
|
||||
# Exclude a variety of commonly ignored directories.
|
||||
exclude = [
|
||||
".bzr",
|
||||
".direnv",
|
||||
".eggs",
|
||||
".git",
|
||||
".git-rewrite",
|
||||
".hg",
|
||||
".ipynb_checkpoints",
|
||||
".mypy_cache",
|
||||
".nox",
|
||||
".pants.d",
|
||||
".pyenv",
|
||||
".pytest_cache",
|
||||
".pytype",
|
||||
".ruff_cache",
|
||||
".svn",
|
||||
".tox",
|
||||
".venv",
|
||||
".vscode",
|
||||
"__pypackages__",
|
||||
"_build",
|
||||
"buck-out",
|
||||
"build",
|
||||
"dist",
|
||||
"node_modules",
|
||||
"site-packages",
|
||||
"venv",
|
||||
]
|
||||
|
||||
# Same as Black.
|
||||
line-length = 88
|
||||
indent-width = 4
|
||||
|
||||
# Assume Python 3.8
|
||||
target-version = "py38"
|
||||
|
||||
[lint]
|
||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
|
||||
# McCabe complexity (`C901`) by default.
|
||||
select = ["E4", "E7", "E9", "F", "RET", "PTH", "D", "W", "BLE001", "EM"]
|
||||
ignore = ["D100", "D203", "D213"]
|
||||
|
||||
# Allow fix for all enabled rules (when `--fix`) is provided.
|
||||
fixable = ["ALL"]
|
||||
unfixable = []
|
||||
|
||||
# Allow unused variables when underscore-prefixed.
|
||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||
|
||||
[format]
|
||||
# Like Black, use double quotes for strings.
|
||||
quote-style = "double"
|
||||
|
||||
# Like Black, indent with spaces, rather than tabs.
|
||||
indent-style = "space"
|
||||
|
||||
# Like Black, respect magic trailing commas.
|
||||
skip-magic-trailing-comma = false
|
||||
|
||||
# Like Black, automatically detect the appropriate line ending.
|
||||
line-ending = "auto"
|
||||
|
||||
# Enable auto-formatting of code examples in docstrings. Markdown,
|
||||
# reStructuredText code/literal blocks and doctests are all supported.
|
||||
#
|
||||
# This is currently disabled by default, but it is planned for this
|
||||
# to be opt-out in the future.
|
||||
docstring-code-format = false
|
||||
|
||||
# Set the line length limit used when formatting code snippets in
|
||||
# docstrings.
|
||||
#
|
||||
# This only has an effect when the `docstring-code-format` setting is
|
||||
# enabled.
|
||||
docstring-code-line-length = "dynamic"
|
@ -1,23 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Generated file, do not edit
|
||||
|
||||
set -eu
|
||||
|
||||
me="$(readlink -f "$0")"
|
||||
here="${me%/*}"
|
||||
me="${me##*/}"
|
||||
|
||||
dir=sniper_platform_0.20240125.75305
|
||||
pressure_vessel="${PRESSURE_VESSEL_PREFIX:-"${here}/pressure-vessel"}"
|
||||
|
||||
export PRESSURE_VESSEL_COPY_RUNTIME=1
|
||||
export PRESSURE_VESSEL_GC_LEGACY_RUNTIMES=1
|
||||
export PRESSURE_VESSEL_RUNTIME="${dir}"
|
||||
unset PRESSURE_VESSEL_RUNTIME_ARCHIVE
|
||||
export PRESSURE_VESSEL_RUNTIME_BASE="${here}"
|
||||
|
||||
if [ -z "${PRESSURE_VESSEL_VARIABLE_DIR-}" ]; then
|
||||
export PRESSURE_VESSEL_VARIABLE_DIR="${here}/var"
|
||||
fi
|
||||
|
||||
exec "${pressure_vessel}/bin/pressure-vessel-unruntime" "$@"
|
@ -1 +0,0 @@
|
||||
ulwgl_run.py
|
@ -1,72 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# use for debug only.
|
||||
# set -x
|
||||
|
||||
ULWGL_PROTON_VER="ULWGL-Proton-8.0-5-3"
|
||||
ULWGL_LAUNCHER_VER="0.1-RC3"
|
||||
|
||||
me="$(readlink -f "$0")"
|
||||
|
||||
ulwgl_link="https://github.com/Open-Wine-Components/ULWGL-launcher/releases/download/$ULWGL_LAUNCHER_VER/ULWGL-launcher.tar.gz"
|
||||
ulwgl_dir="$HOME"/.local/share/ULWGL
|
||||
|
||||
proton_link="https://github.com/Open-Wine-Components/ULWGL-Proton/releases/download/$ULWGL_PROTON_VER/$ULWGL_PROTON_VER"
|
||||
proton_dir="$HOME"/.local/share/Steam/compatibilitytools.d
|
||||
|
||||
ulwgl_cache="$HOME"/.cache/ULWGL
|
||||
|
||||
if [ ! -d "$ulwgl_cache" ]; then
|
||||
mkdir -p "$ulwgl_cache"
|
||||
fi
|
||||
|
||||
# Self-update
|
||||
# In flatpak it will check for /app/share/ULWGL/ULWGL-launcher.tar.gz and check version
|
||||
# In distro package it will check for /usr/share/ULWGL/ULWGL-launcher.tar.gz and check version
|
||||
# If tarball does not exist it will just download it.
|
||||
if [ ! -d "$ulwgl_dir" ]; then
|
||||
mkdir -p "$ulwgl_dir"
|
||||
if [ -f "${me%/*/*}"/share/ULWGL/ULWGL-launcher.tar.gz ]; then
|
||||
tar -zxvf "${me%/*/*}"/share/ULWGL/ULWGL-launcher.tar.gz --one-top-level="$ulwgl_dir"
|
||||
else
|
||||
wget "$ulwgl_link" -O "$ulwgl_cache/ULWGL-launcher.tar.gz"
|
||||
tar -zxvf "$ulwgl_cache/ULWGL-launcher.tar.gz" --one-top-level="$ulwgl_dir"
|
||||
rm "$ulwgl_cache/ULWGL-launcher.tar.gz"
|
||||
fi
|
||||
else
|
||||
if [ "$ULWGL_LAUNCHER_VER" != "$(cat "$ulwgl_dir"/ULWGL-VERSION)" ]; then
|
||||
rm -Rf "$ulwgl_dir" --preserve-root=all
|
||||
if [ -f "${me%/*/*}"/share/ULWGL/ULWGL-launcher.tar.gz ]; then
|
||||
tar -zxvf "${me%/*/*}"/share/ULWGL/ULWGL-launcher.tar.gz --one-top-level="$ulwgl_dir"
|
||||
else
|
||||
wget "$ulwgl_link" -O "$ulwgl_cache/ULWGL-launcher.tar.gz"
|
||||
tar -zxvf "$ulwgl_cache/ULWGL-launcher.tar.gz" --one-top-level="$ulwgl_dir"
|
||||
rm "$ulwgl_cache/ULWGL-launcher.tar.gz"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$PROTONPATH" ]; then
|
||||
if [ ! -d "$proton_dir"/$ULWGL_PROTON_VER ]; then
|
||||
wget "$proton_link".tar.gz -O "$ulwgl_cache/$ULWGL_PROTON_VER".tar.gz
|
||||
wget "$proton_link".sha512sum -O "$ulwgl_cache/$ULWGL_PROTON_VER".sha512sum
|
||||
cd "$ulwgl_cache" || exit
|
||||
checksum=$(sha512sum "$ULWGL_PROTON_VER".tar.gz)
|
||||
cd - || exit
|
||||
if [ "$checksum" = "$(cat "$ulwgl_cache/$ULWGL_PROTON_VER".sha512sum)" ]; then
|
||||
tar -zxvf "$ulwgl_cache/$ULWGL_PROTON_VER".tar.gz --one-top-level="$proton_dir"
|
||||
rm "$ulwgl_cache/$ULWGL_PROTON_VER".tar.gz
|
||||
rm "$ulwgl_cache/$ULWGL_PROTON_VER".sha512sum
|
||||
else
|
||||
echo "ERROR: $ulwgl_cache/$ULWGL_PROTON_VER.tar.gz checksum does not match $ulwgl_cache/$ULWGL_PROTON_VER.sha512sum, aborting!"
|
||||
rm "$ulwgl_cache/$ULWGL_PROTON_VER".tar.gz
|
||||
rm "$ulwgl_cache/$ULWGL_PROTON_VER".sha512sum
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
export PROTONPATH="$proton_dir/$ULWGL_PROTON_VER"
|
||||
else
|
||||
export PROTONPATH="$PROTONPATH"
|
||||
fi
|
||||
|
||||
"$ulwgl_dir/ulwgl-run" "$@"
|
@ -1,108 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# use for debug only.
|
||||
# set -x
|
||||
|
||||
if [ -z "$1" ] || [ -z "$WINEPREFIX" ] || [ -z "$GAMEID" ] || [ -z "$PROTONPATH" ]; then
|
||||
echo "Usage: WINEPREFIX=<wine-prefix-path> GAMEID=<ulwgl-id> PROTONPATH=<proton-version-path> ./gamelauncher.sh <executable-path> <arguments>"
|
||||
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 <<EOLDCONFIG
|
||||
$ldconfig_output
|
||||
EOLDCONFIG
|
||||
|
||||
host_library_paths="${LD_LIBRARY_PATH:+"${LD_LIBRARY_PATH}:"}$host_library_paths"
|
||||
steam_runtime_library_paths="${STEAM_COMPAT_INSTALL_PATH}:${host_library_paths}"
|
||||
export STEAM_RUNTIME_LIBRARY_PATH="$steam_runtime_library_paths"
|
||||
fi
|
||||
|
||||
if [ -z "$PROTON_VERB" ]; then
|
||||
export PROTON_VERB="waitforexitandrun"
|
||||
fi
|
||||
|
||||
export STEAM_COMPAT_CLIENT_INSTALL_PATH=''
|
||||
export STEAM_COMPAT_DATA_PATH="$WINEPREFIX"
|
||||
export STEAM_COMPAT_SHADER_PATH="$STEAM_COMPAT_DATA_PATH"/shadercache
|
||||
|
||||
export PROTON_CRASH_REPORT_DIR='/tmp/ULWGL_crashreports'
|
||||
export FONTCONFIG_PATH=''
|
||||
|
||||
export EXE="$1"
|
||||
if [ "$EXE" = "createprefix" ]; then
|
||||
# Hack, leave empty.
|
||||
# forces proton to create a prefix without actually running anything.
|
||||
EXE=""
|
||||
fi
|
||||
shift 1
|
||||
|
||||
export STEAM_COMPAT_TOOL_PATHS="$PROTONPATH:$here"
|
||||
export STEAM_COMPAT_MOUNTS="$PROTONPATH:$here"
|
||||
|
||||
"$here"/ULWGL "--verb=$PROTON_VERB" -- "$PROTONPATH"/proton "$PROTON_VERB" "$EXE" "$@"
|
@ -1,26 +0,0 @@
|
||||
from enum import Enum
|
||||
from logging import INFO, WARNING, DEBUG, ERROR
|
||||
|
||||
SIMPLE_FORMAT = "%(levelname)s: %(message)s"
|
||||
|
||||
DEBUG_FORMAT = "%(levelname)s [%(module)s.%(funcName)s:%(lineno)s]:%(message)s"
|
||||
|
||||
|
||||
class Level(Enum):
|
||||
"""Represent the Log level values for the logger module."""
|
||||
|
||||
INFO = INFO
|
||||
WARNING = WARNING
|
||||
DEBUG = DEBUG
|
||||
ERROR = ERROR
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
"""Represent the color to be applied to a string."""
|
||||
|
||||
RESET = "\u001b[0m"
|
||||
INFO = "\u001b[34m"
|
||||
WARNING = "\033[33m"
|
||||
ERROR = "\033[31m"
|
||||
BOLD = "\033[1m"
|
||||
DEBUG = "\u001b[35m"
|
@ -1,261 +0,0 @@
|
||||
from pathlib import Path
|
||||
from os import environ
|
||||
from tarfile import open as tar_open
|
||||
from typing import Dict, List, Tuple, Any, Union
|
||||
from hashlib import sha512
|
||||
from shutil import rmtree
|
||||
from http.client import HTTPSConnection, HTTPResponse, HTTPException, HTTPConnection
|
||||
from ssl import create_default_context
|
||||
from json import loads as loads_json
|
||||
from urllib.request import urlretrieve
|
||||
from sys import stderr
|
||||
|
||||
|
||||
def get_ulwgl_proton(env: Dict[str, str]) -> 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
|
@ -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)
|
@ -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
|
@ -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)
|
File diff suppressed because it is too large
Load Diff
@ -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()
|
@ -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
|
22
libs/customvars_loader.py
Normal file
22
libs/customvars_loader.py
Normal file
@ -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
|
41
libs/filepath_loader.py
Normal file
41
libs/filepath_loader.py
Normal file
@ -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
|
33
libs/gameid_loader.py
Normal file
33
libs/gameid_loader.py
Normal file
@ -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
|
||||
|
33
libs/ids_loader.py
Normal file
33
libs/ids_loader.py
Normal file
@ -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
|
36
libs/mustExist.py
Normal file
36
libs/mustExist.py
Normal file
@ -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
|
22
libs/postdirectives_loader.py
Normal file
22
libs/postdirectives_loader.py
Normal file
@ -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
|
22
libs/predirectives_loader.py
Normal file
22
libs/predirectives_loader.py
Normal file
@ -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
|
36
libs/protonpath_loader.py
Normal file
36
libs/protonpath_loader.py
Normal file
@ -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
|
30
libs/ulwgl_loader.py
Normal file
30
libs/ulwgl_loader.py
Normal file
@ -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
|
42
libs/ulwlg_runner.py
Normal file
42
libs/ulwlg_runner.py
Normal file
@ -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)
|
30
libs/wineprefix_loader.py
Normal file
30
libs/wineprefix_loader.py
Normal file
@ -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
|
0
protons/this_folder_will_contain_proton_instances
Normal file
0
protons/this_folder_will_contain_proton_instances
Normal file
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
dotenv
|
||||
tabulate
|
BIN
screenshot.png
Normal file
BIN
screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 154 KiB |
126
uwine
Executable file
126
uwine
Executable file
@ -0,0 +1,126 @@
|
||||
#!/bin/python
|
||||
|
||||
import os
|
||||
import argparse
|
||||
from tabulate import tabulate
|
||||
import dotenv
|
||||
import json
|
||||
|
||||
import libs.ids_loader as ids_loader
|
||||
import libs.gameid_loader as gameid_loader
|
||||
import libs.protonpath_loader as protonpath_loader
|
||||
import libs.wineprefix_loader as wineprefix_loader
|
||||
import libs.filepath_loader as filepath_loader
|
||||
import libs.ulwgl_loader as ulwgl_loader
|
||||
import libs.customvars_loader as customvars_loader
|
||||
import libs.predirectives_loader as predirectives_loader
|
||||
import libs.postdirectives_loader as postdirectives_loader
|
||||
import libs.ulwlg_runner as ulwlg_runner
|
||||
|
||||
# SECTION Constants
|
||||
LAUNCHDIR = os.getcwd()
|
||||
print("[*] Launching in " + LAUNCHDIR)
|
||||
UWINEDIR = os.path.dirname(os.path.realpath(__file__))
|
||||
print("[*] UWINE is installed in " + UWINEDIR)
|
||||
|
||||
# SECTION Default values
|
||||
ulwgl_dir = UWINEDIR + "/launcher"
|
||||
proton_path = UWINEDIR + "/protons/current"
|
||||
wine_prefix = UWINEDIR + "/PREFIX"
|
||||
ids_json_path = UWINEDIR + "/ids.json"
|
||||
ids = {}
|
||||
game_id = 0
|
||||
filepath = ""
|
||||
|
||||
envfile = ".env"
|
||||
|
||||
# NOTE Parsing the arguments
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="uwine",
|
||||
description="ULWGL Launcher Wrapper for human beings",
|
||||
epilog="https://github.com/thecookingsenpai/UWINE",
|
||||
)
|
||||
parser.add_argument(
|
||||
"filepath", help="Path to the file to be launched", type=str, nargs="?", default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l", "--load", help="Load a specific env file", type=str, dest="envfile"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-g", "--game-id", dest="gameid", help="Game ID to be used", type=int
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p", "--proton-path", dest="protonpath", help="Path to the Proton installation"
|
||||
)
|
||||
parser.add_argument("-i", "--ids-json", dest="ids", help="Path to the ids.json file")
|
||||
parser.add_argument(
|
||||
"-w", "--wine-prefix", dest="wineprefix", help="Path to the Wine prefix"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-u", "--ulwgl", dest="ulwlgdir", help="Path to the ULWGL installation"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-a", "--additionalargs", dest="additionalargs", help="Additional arguments to be passed to the software (at the end, as a string)", type=str, default=""
|
||||
)
|
||||
|
||||
parser.add_argument("-v", "--version", action="version", version="%(prog)s 0.1")
|
||||
|
||||
args = parser.parse_args()
|
||||
print(args.ulwlgdir)
|
||||
|
||||
# Loading the .env file
|
||||
if args.envfile:
|
||||
envfile = args.envfile
|
||||
print("[*] Loading the .env file: " + envfile)
|
||||
dotenv.load_dotenv(dotenv_path=envfile)
|
||||
|
||||
# Ensuring we support either none or some customvars
|
||||
if os.environ["CUSTOMVARS"]:
|
||||
print("[INFO] [CUSTOMVARS] " + os.environ["CUSTOMVARS"])
|
||||
env_defined_customvars = os.environ["CUSTOMVARS"]
|
||||
else:
|
||||
env_defined_customvars = {}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# SECTION Loading methods
|
||||
ulwgl_dir = ulwgl_loader.set_ulwgldir(args.ulwlgdir, UWINEDIR)
|
||||
ids, ids_json_path = ids_loader.load_ids(args.ids, ids, ids_json_path)
|
||||
loaded_customvars = customvars_loader.set_customvars(env_defined_customvars, {})
|
||||
os.environ["GAMEID"] = str(gameid_loader.load_gameid(args.gameid, game_id, ids))
|
||||
os.environ["PROTONPATH"] = protonpath_loader.set_protonpath(
|
||||
args.protonpath, proton_path, UWINEDIR
|
||||
)
|
||||
os.environ["WINEPREFIX"] = wineprefix_loader.set_wineprefix(
|
||||
args.wineprefix, wine_prefix
|
||||
)
|
||||
filepath = filepath_loader.set_filepath(args.filepath, LAUNCHDIR)
|
||||
# Directives support
|
||||
predirectives = predirectives_loader.set_predirectives("") # Future support for postdirectives in cli
|
||||
postdirectives = postdirectives_loader.set_postdirectives(args.additionalargs)
|
||||
# SECTION Launching the game
|
||||
print("\n[*] Launching the game...")
|
||||
|
||||
# ANCHOR Recap
|
||||
# Lets make a nice table to show the user what we are going to do
|
||||
|
||||
print(
|
||||
tabulate(
|
||||
[
|
||||
["ULWGLDIR", ulwgl_dir],
|
||||
["WINEPREFIX", os.environ["WINEPREFIX"]],
|
||||
["PROTONPATH", os.environ["PROTONPATH"]],
|
||||
["IDS_JSON", ids_json_path],
|
||||
["GAMEID", os.environ["GAMEID"]],
|
||||
["PREDIRECTIVES", predirectives],
|
||||
["FILEPATH", filepath],
|
||||
["POSTDIRECTIVES", postdirectives],
|
||||
["CUSTOMVARS", loaded_customvars]
|
||||
],
|
||||
headers=["Variable", "Value"],
|
||||
tablefmt="fancy_grid",
|
||||
)
|
||||
)
|
||||
|
||||
# Launching with ulwlg_runner
|
||||
ulwlg_runner.ulwlg_run(filepath, ulwgl_dir, os.environ["PROTONPATH"], os.environ["WINEPREFIX"], os.environ["GAMEID"])
|
10
uwine.desktop
Normal file
10
uwine.desktop
Normal file
@ -0,0 +1,10 @@
|
||||
[Desktop Entry]
|
||||
Version=1.1
|
||||
Type=Application
|
||||
Name=UWine
|
||||
Comment=Launch with UWine!
|
||||
Icon=q4wine
|
||||
Exec=uwine
|
||||
Terminal=true
|
||||
Actions=
|
||||
Categories=Game;
|
10
uwine_launcher.desktop
Normal file
10
uwine_launcher.desktop
Normal file
@ -0,0 +1,10 @@
|
||||
[Desktop Entry]
|
||||
Version=1.1
|
||||
Type=Application
|
||||
Name=UWine Launcher
|
||||
Comment=Launch .uwine files (and compatible ones)
|
||||
Icon=winetricks
|
||||
Exec=uwine -l
|
||||
Terminal=true
|
||||
Actions=
|
||||
Categories=Game;
|
Loading…
x
Reference in New Issue
Block a user