diff --git a/proton b/proton index ec61aab6..30a5c7d8 100755 --- a/proton +++ b/proton @@ -15,6 +15,16 @@ import subprocess import sys import tarfile +from ctypes import CDLL +from ctypes import POINTER +from ctypes import Structure +from ctypes import addressof +from ctypes import cast +from ctypes import c_int +from ctypes import c_char_p +from ctypes import c_void_p +from ctypes.util import find_library + from filelock import FileLock #To enable debug logging, copy "user_settings.sample.py" to "user_settings.py" @@ -127,6 +137,83 @@ def try_get_game_library_dir(): return None +# Function to find the installed location of DLL files for use by Wine/Proton +# from the NVIDIA Linux driver +# +# See https://gitlab.steamos.cloud/steamrt/steam-runtime-tools/-/issues/71 for +# background on the chosen method of DLL discovery. +# +# On success, returns a str() of the absolute-path to the directory at which DLL +# files are stored +# +# On failure, returns None +def find_nvidia_wine_dll_dir(): + libdl = CDLL(find_library("libdl")) + libglx_nvidia = CDLL("libGLX_nvidia.so.0") + + if libdl is None or libglx_nvidia is None: + return None + + # from dlinfo(3) + # + # struct link_map { + # ElfW(Addr) l_addr; /* Difference between the + # address in the ELF file and + # the address in memory */ + # char *l_name; /* Absolute pathname where + # object was found */ + # ElfW(Dyn) *l_ld; /* Dynamic section of the + # shared object */ + # struct link_map *l_next, *l_prev; + # /* Chain of loaded objects */ + # + # /* Plus additional fields private to the + # implementation */ + # }; + RTLD_DI_LINKMAP = 2 + class link_map(Structure): + _fields_ = [("l_addr", c_void_p), ("l_name", c_char_p), ("l_ld", c_void_p)] + + # from dlinfo(3) + # + # int dlinfo (void *restrict handle, int request, void *restrict info) + dlinfo_func = libdl.dlinfo + dlinfo_func.argtypes = c_void_p, c_int, c_void_p + dlinfo_func.restype = c_int + + # Allocate a link_map object + glx_nvidia_info_ptr = POINTER(link_map)() + + # Run dlinfo(3) on the handle to libGLX_nvidia.so.0, storing results at the + # address represented by glx_nvidia_info_ptr + if dlinfo_func(libglx_nvidia._handle, + RTLD_DI_LINKMAP, + addressof(glx_nvidia_info_ptr)) != 0: + return None + + # Grab the contents our of our pointer + glx_nvidia_info = cast(glx_nvidia_info_ptr, POINTER(link_map)).contents + + # Decode the path to our library to a str() + if glx_nvidia_info.l_name is None: + return None + try: + libglx_nvidia_path = os.fsdecode(glx_nvidia_info.l_name) + except UnicodeDecodeError: + return None + + # Follow any symlinks to the actual file + libglx_nvidia_realpath = os.path.realpath(libglx_nvidia_path) + + # Go to the relative path ./nvidia/wine from our library + nvidia_wine_dir = os.path.join(os.path.dirname(libglx_nvidia_realpath), "nvidia", "wine") + + # Check that nvngx.dll exists here, or fail + if os.path.exists(os.path.join(nvidia_wine_dir, "nvngx.dll")): + return nvidia_wine_dir + + return None + EXT2_IOC_GETFLAGS = 0x80086601 EXT2_IOC_SETFLAGS = 0x40086602 @@ -605,6 +692,14 @@ class CompatData: if os.path.exists(nvapi32_dll): os.unlink(nvapi32_dll) + # Try to detect known DLLs that ship with the NVIDIA Linux Driver + # and add them into the prefix + nvidia_wine_dll_dir = find_nvidia_wine_dll_dir() + if nvidia_wine_dll_dir: + for dll in ["_nvngx.dll", "nvngx.dll"]: + try_copy(nvidia_wine_dll_dir + "/" + dll, + self.prefix_dir + "drive_c/windows/system32/" + dll) + try_copy(g_proton.lib64_dir + "wine/vkd3d-proton/d3d12.dll", self.prefix_dir + "drive_c/windows/system32/d3d12.dll") try_copy(g_proton.lib_dir + "wine/vkd3d-proton/d3d12.dll",