1
0
Fork 0
mirror of synced 2024-05-18 19:42:54 +12:00

Proton: Add functions to find and integrate compatibilty tools and runtimes

This commit is contained in:
loathingKernel 2023-09-14 19:24:53 +03:00
parent 1f34ad4b13
commit efe9031211
3 changed files with 246 additions and 1 deletions

View file

@ -8,6 +8,7 @@ from PyQt5.QtGui import QFont
from rare.lgndr.core import LegendaryCore
from rare.utils.misc import icon
from rare.utils import proton
class EnvVarsTableModel(QAbstractTableModel):
@ -23,11 +24,11 @@ class EnvVarsTableModel(QAbstractTableModel):
self.__readonly = [
"STEAM_COMPAT_DATA_PATH",
"STEAM_COMPAT_CLIENT_INSTALL_PATH",
"WINEPREFIX",
"DXVK_HUD",
"MANGOHUD_CONFIG",
]
self.__readonly.extend(proton.get_steam_environment(None).keys())
self.__default: str = "default"
self.__appname: str = None

View file

@ -1,8 +1,251 @@
import os
from dataclasses import dataclass
from logging import getLogger
from typing import Optional, Union, List, Dict
import vdf
logger = getLogger("Proton")
steam_compat_client_install_paths = [os.path.expanduser("~/.local/share/Steam")]
def find_steam() -> str:
# return the first valid path
for path in steam_compat_client_install_paths:
if os.path.isdir(path) and os.path.isfile(os.path.join(path, "steam.sh")):
return path
def find_libraries(steam_path: str) -> List[str]:
vdf_path = os.path.join(steam_path, "steamapps", "libraryfolders.vdf")
with open(vdf_path, "r") as f:
libraryfolders = vdf.load(f)["libraryfolders"]
libraries = [os.path.join(folder["path"], "steamapps") for key, folder in libraryfolders.items()]
return libraries
@dataclass
class SteamBase:
steam_path: str
tool_path: str
toolmanifest: dict
def __eq__(self, other):
return self.tool_path == other.tool_path
def __hash__(self):
return hash(self.tool_path)
def commandline(self):
cmd = "".join([f"'{self.tool_path}'", self.toolmanifest["manifest"]["commandline"]])
cmd = os.path.normpath(cmd)
# NOTE: "waitforexitandrun" seems to be the verb used in by steam to execute stuff
cmd = cmd.replace("%verb%", "waitforexitandrun")
return cmd
@dataclass
class SteamRuntime(SteamBase):
steam_library: str
appmanifest: dict
def name(self):
return self.appmanifest["AppState"]["name"]
def appid(self):
return self.appmanifest["AppState"]["appid"]
@dataclass
class ProtonTool(SteamRuntime):
runtime: SteamRuntime = None
def __bool__(self):
if appid := self.toolmanifest.get("require_tool_appid", False):
return self.runtime is not None and self.runtime.appid() == appid
def commandline(self):
runtime_cmd = self.runtime.commandline()
cmd = super().commandline()
return " ".join([runtime_cmd, cmd])
@dataclass
class CompatibilityTool(SteamBase):
compatibilitytool: dict
runtime: SteamRuntime = None
def __bool__(self):
if appid := self.toolmanifest.get("require_tool_appid", False):
return self.runtime is not None and self.runtime.appid() == appid
def name(self):
name, data = list(self.compatibilitytool["compatibilitytools"]["compat_tools"].items())[0]
return data["display_name"]
def commandline(self):
runtime_cmd = self.runtime.commandline() if self.runtime is not None else ""
cmd = super().commandline()
return " ".join([runtime_cmd, cmd])
def find_appmanifests(library: str) -> List[dict]:
appmanifests = []
for entry in os.scandir(library):
if entry.is_file() and entry.name.endswith(".acf"):
with open(os.path.join(library, entry.name), "r") as f:
appmanifest = vdf.load(f)
appmanifests.append(appmanifest)
return appmanifests
def find_protons(steam_path: str, library: str) -> List[ProtonTool]:
protons = []
appmanifests = find_appmanifests(library)
common = os.path.join(library, "common")
for appmanifest in appmanifests:
folder = appmanifest["AppState"]["installdir"]
tool_path = os.path.join(common, folder)
if os.path.isfile(vdf_file := os.path.join(tool_path, "toolmanifest.vdf")):
with open(vdf_file, "r") as f:
toolmanifest = vdf.load(f)
if toolmanifest["manifest"]["compatmanager_layer_name"] == "proton":
protons.append(
ProtonTool(
steam_path=steam_path,
steam_library=library,
appmanifest=appmanifest,
tool_path=tool_path,
toolmanifest=toolmanifest,
)
)
return protons
def find_compatibility_tools(steam_path: str) -> List[CompatibilityTool]:
compatibilitytools_paths = {
"/usr/share/steam/compatibilitytools.d",
os.path.expanduser(os.path.join(steam_path, "compatibilitytools.d")),
os.path.expanduser("~/.steam/compatibilitytools.d"),
os.path.expanduser("~/.steam/root/compatibilitytools.d"),
}
compatibilitytools_paths = {
os.path.realpath(path) for path in compatibilitytools_paths if os.path.isdir(path)
}
tools = []
for path in compatibilitytools_paths:
for entry in os.scandir(path):
if entry.is_dir():
tool_path = os.path.join(path, entry.name)
tool_vdf = os.path.join(tool_path, "compatibilitytool.vdf")
manifest_vdf = os.path.join(tool_path, "toolmanifest.vdf")
if os.path.isfile(tool_vdf) and os.path.isfile(manifest_vdf):
with open(tool_vdf, "r") as f:
compatibilitytool = vdf.load(f)
with open(manifest_vdf, "r") as f:
manifest = vdf.load(f)
tools.append(
CompatibilityTool(
steam_path=steam_path,
tool_path=tool_path,
toolmanifest=manifest,
compatibilitytool=compatibilitytool,
)
)
return tools
def find_runtimes(steam_path: str, library: str) -> Dict[str, SteamRuntime]:
runtimes = {}
appmanifests = find_appmanifests(library)
common = os.path.join(library, "common")
for appmanifest in appmanifests:
folder = appmanifest["AppState"]["installdir"]
tool_path = os.path.join(common, folder)
if os.path.isfile(vdf_file := os.path.join(tool_path, "toolmanifest.vdf")):
with open(vdf_file, "r") as f:
toolmanifest = vdf.load(f)
if toolmanifest["manifest"]["compatmanager_layer_name"] == "container-runtime":
runtimes.update(
{
appmanifest["AppState"]["appid"]: SteamRuntime(
steam_path=steam_path,
steam_library=library,
appmanifest=appmanifest,
tool_path=tool_path,
toolmanifest=toolmanifest,
)
}
)
return runtimes
def find_runtime(
tool: Union[ProtonTool, CompatibilityTool], runtimes: Dict[str, SteamRuntime]
) -> Optional[SteamRuntime]:
required_tool = tool.toolmanifest["manifest"].get("require_tool_appid")
if required_tool is None:
return None
return runtimes[required_tool]
def get_steam_environment(tool: Optional[Union[ProtonTool, CompatibilityTool]], app_name: str = None) -> Dict:
environ = {}
# If the tool is unset, return all affected env variable names
# IMPORTANT: keep this in sync with the code below
if tool is None:
environ["STEAM_COMPAT_CLIENT_INSTALL_PATH"] = ""
environ["STEAM_COMPAT_LIBRARY_PATHS"] = ""
environ["STEAM_COMPAT_MOUNTS"] = ""
environ["STEAM_COMPAT_TOOL_PATHS"] = ""
return environ
environ["STEAM_COMPAT_CLIENT_INSTALL_PATH"] = tool.steam_path
if isinstance(tool, ProtonTool):
environ["STEAM_COMPAT_LIBRARY_PATHS"] = tool.steam_library
if tool.runtime is not None:
compat_mounts = [tool.tool_path, tool.runtime.tool_path]
environ["STEAM_COMPAT_MOUNTS"] = ":".join(compat_mounts)
tool_paths = [tool.tool_path]
if tool.runtime is not None:
tool_paths.append(tool.runtime.tool_path)
environ["STEAM_COMPAT_TOOL_PATHS"] = ":".join(tool_paths)
return environ
def find_tools() -> List[Union[ProtonTool, CompatibilityTool]]:
steam_path = find_steam()
logger.info("Using Steam install in %s", steam_path)
steam_libraries = find_libraries(steam_path)
logger.info("Searching for tools in libraries %s", steam_libraries)
runtimes = {}
for library in steam_libraries:
runtimes.update(find_runtimes(steam_path, library))
tools = []
for library in steam_libraries:
tools.extend(find_protons(steam_path, library))
tools.extend(find_compatibility_tools(steam_path))
for tool in tools:
runtime = find_runtime(tool, runtimes)
tool.runtime = runtime
return tools
if __name__ == "__main__":
from pprint import pprint
_tools = find_tools()
pprint(_tools)
for tool in _tools:
print(get_steam_environment(tool))
print(tool.name(), tool.commandline())
def find_proton_combos():
possible_proton_combos = []

View file

@ -4,4 +4,5 @@ QtAwesome
setuptools
legendary-gl>=0.20.34
orjson
vdf; platform_system != "Windows"
pywin32; platform_system == "Windows"