From 7a43ca89ff7a70127ac9ca0f10b6eaaa34f2f69c Mon Sep 17 00:00:00 2001 From: Chris Simons Date: Mon, 24 Sep 2018 22:20:19 -0700 Subject: [PATCH] Initial commit --- .gitignore | 111 ++++++++++++++++++++++++++++++ README.md | 58 ++++++++++++++++ protonfixes/__init__.py | 1 + protonfixes/gamefixes/15700.py | 17 +++++ protonfixes/gamefixes/15740.py | 21 ++++++ protonfixes/gamefixes/377840.py | 22 ++++++ protonfixes/gamefixes/__init__.py | 0 protonfixes/protonfix.py | 41 +++++++++++ user_settings.py-example | 10 +++ 9 files changed, 281 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 protonfixes/__init__.py create mode 100755 protonfixes/gamefixes/15700.py create mode 100755 protonfixes/gamefixes/15740.py create mode 100755 protonfixes/gamefixes/377840.py create mode 100755 protonfixes/gamefixes/__init__.py create mode 100755 protonfixes/protonfix.py create mode 100755 user_settings.py-example diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6f7a6d9c --- /dev/null +++ b/.gitignore @@ -0,0 +1,111 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json diff --git a/README.md b/README.md new file mode 100644 index 00000000..947e7f47 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# protonfixes + +A very basic modular method for applying fixes to run unsupported games with Steam Proton. The idea is to include seperate fixes that are only loaded when a game matching that ID is run. This should keep the individual game fixes from affecting any other games. + +## Installation + +1. [Download](/releases/latest) the latest release .zip file and extract it. + +2. Move the `protonfixes` folder to the `Proton 3.7` or `Proton 3.7 Beta` directory. + + This is usually located at `~/.steam/steam/steamapps/common/Proton 3.7` or `~/.local/share/Steam/steamapps/common/Proton 3.7` unless you have Steam installed to another location + +3. If you are already using a customized `user_settings.py` file, skip to step 4. Otherwise, copy the `user_settings.py` file into the same Proton directory as the `protonfixes` folder. + +4. If you are already using a customized `user_settings.py` and do not want to change your current settings, you can just import the protonfixes module in your `user_settings.py` file by adding the following lines: + + ``` + from protonfixes.protonfix import ProtonFix + ProtonFix() + ``` + +## Writing Game Fixes +Game fixes written in python and are named by the Steam game ID with the extension .py. For example, the file `gamefixes/377840.py` will be loaded when the game FINAL FANTASY IX is run. Here are some things to consider when writing fixes: + +- Only import libraries that are part of the Python standard library for portability. +- Use docstrings and comment thoroughly. There will likely be people without python experience making game fixes and good commented examples will help +- Do not use any hard-coded paths, Steam may not always be installed in the same location. +- Check your gamefix with pylint. You can safely disable warning C0103, modules named by Steam ID will never conform to snake_case naming style. + +## Enviornment Variables + +From https://github.com/ValveSoftware/Proton + +PROTON_LOG 1 or 0 +PROTON_DUMP_DEBUG_COMMANDS 1 or 0 +PROTON_DEBUG_DIR 1 or 0 +PROTON_USE_WINE3D11 1 0r 0 +PROTON_NO_D3D11 1 or 0 +PROTON_NO_ESYNC 1 or 0 + +user_settings = { + #logs are saved to $HOME/steam-$STEAM_APP_ID.log, overwriting any previous log with that name + "WINEDEBUG": "+timestamp,+pid,+tid,+seh,+debugstr,+module", + + "DXVK_LOG_LEVEL": "info", + + #Enable DXVK's HUD + "DXVK_HUD": "devinfo,fps", + + #Use wined3d for d3d11 instead of dxvk + #"PROTON_USE_WINED3D11": "1", + + #Disable d3d11 entirely +# "PROTON_NO_D3D11": "1", + + #Disable in-process synchronization primitives + "PROTON_NO_ESYNC": "1", +} diff --git a/protonfixes/__init__.py b/protonfixes/__init__.py new file mode 100755 index 00000000..cca54dc4 --- /dev/null +++ b/protonfixes/__init__.py @@ -0,0 +1 @@ +from . import gamefixes \ No newline at end of file diff --git a/protonfixes/gamefixes/15700.py b/protonfixes/gamefixes/15700.py new file mode 100755 index 00000000..03ce85c3 --- /dev/null +++ b/protonfixes/gamefixes/15700.py @@ -0,0 +1,17 @@ +""" Game fox for Oddworld: Abe's Oddysee +TODO: Fix steam controller input, it is stuck in lizard mode without overlay +""" +#pylint: disable=C0103 + + +import sys + + +def main(): + """ Adds the -interline argument to the game + """ + + print('Applying Oddworld: Abe\'s Oddysee Fixes') + + # Adding -interline fixes slow videos but adds scanlines + sys.argv.append('-interline') diff --git a/protonfixes/gamefixes/15740.py b/protonfixes/gamefixes/15740.py new file mode 100755 index 00000000..0cd70e79 --- /dev/null +++ b/protonfixes/gamefixes/15740.py @@ -0,0 +1,21 @@ +""" Game fox for Oddworld: Munch's Oddysee +Work in progress +""" +#pylint: disable=C0103 + + +import sys + + +def main(): + """ Changes the proton argument from the launcher to the game + """ + + print('Applying Oddworld: Munch\'s Oddysee Game Fixes') + + # Replace launcher with game exe in proton arguments + for idx, env in enumerate(sys.argv): + if 'Launcher' in env: + sys.argv[idx] = env.replace('bin/Launcher.exe', 'Munch.exe') + + print(sys.argv) diff --git a/protonfixes/gamefixes/377840.py b/protonfixes/gamefixes/377840.py new file mode 100755 index 00000000..7cc54441 --- /dev/null +++ b/protonfixes/gamefixes/377840.py @@ -0,0 +1,22 @@ +""" Game fix for FINAL FANTASY IX +""" +#pylint: disable=C0103 + + +import os +import sys + + +def main(): + """ Changes the proton argument from the launcher to the game + """ + + print('Applying FINAL FANTASY IX Game Fixes') + + # Fix crackling audio + os.environ['PULSE_LATENCY_MSEC'] = '60' + + # Replace launcher with game exe in proton arguments + for idx, env in enumerate(sys.argv): + if 'FF9_Launcher' in env: + sys.argv[idx] = env.replace('FF9_Launcher.exe', 'x64/FF9.exe') diff --git a/protonfixes/gamefixes/__init__.py b/protonfixes/gamefixes/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/protonfixes/protonfix.py b/protonfixes/protonfix.py new file mode 100755 index 00000000..eba848d8 --- /dev/null +++ b/protonfixes/protonfix.py @@ -0,0 +1,41 @@ +""" Loads gamefixes from the gamefixes dir based on game id +""" + +from __future__ import print_function +import os +import re +from importlib import import_module + +class ProtonFix(): + """ Loads the specific fix for a game from gamefixes + """ + + #pylint: disable=R0903 + + def __init__(self): + self._get_game_id() + self._import_fix() + + def _import_fix(self): + try: + game_module = import_module(re.sub(r'\..*', '.gamefixes.', __name__) + str(self.game_id)) + game_module.main() + print('Using protonfix for gameid', self.game_id) + except ImportError: + print('No protonfix for gameid', self.game_id, 'found') + + def _get_game_id(self): + """ Try to get the game id from environment variables + """ + + game_id = None + if game_id is None and 'SteamAppId' in os.environ: + game_id = int(os.environ["SteamAppId"]) + if game_id is None and 'SteamGameId' in os.environ: + game_id = int(os.environ["SteamGameId"]) + if game_id is None and 'STEAM_COMPAT_DATA_PATH' in os.environ: + game_id = int(re.findall(r'\d+', os.environ['STEAM_COMPAT_DATA_PATH'])[-1]) + + print('Steam gameid', game_id, 'retrieved from environment variables') + assert isinstance(game_id, int) + self.game_id = game_id diff --git a/user_settings.py-example b/user_settings.py-example new file mode 100755 index 00000000..28b005fe --- /dev/null +++ b/user_settings.py-example @@ -0,0 +1,10 @@ +""" Example user_settings.py file for protonfixes + https://simons-public.github.io/protonfixes + +If the protonfixes folder is not in this directory this will likely prevent +steam from launching any Proton games. Any errors in specific game fixes should +only affect that specific game. +""" + +from protonfixes.protonfix import ProtonFix +ProtonFix()