From 2d0ec22ba0f4a63794a766f5e5e6cc9638971b1d Mon Sep 17 00:00:00 2001 From: Liam Middlebrook Date: Mon, 10 May 2021 21:53:15 -0700 Subject: proton: Copy DLLs provided by the NVIDIA driver into prefix The upcoming NVIDIA 470 driver series will introduce a DLL (nvngx.dll) for the support of NVIDIA DLSS in Proton. This change adds logic for discovering the location of DLL files provided by the NVIDIA driver, and copies them to C:\Windows\System32\ Reviewed-by: Adam Moss --- proton | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) diff --git a/proton b/proton index a9f58c11..0a8f77a6 100755 --- a/proton +++ b/proton @@ -15,6 +15,15 @@ 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 filelock import FileLock from random import randrange @@ -99,7 +108,7 @@ def merge_user_dir(src, dst): else: extant_dirs += dst_dir -def try_copy(src, dst, add_write_perm=True, copy_metadata=True): +def try_copy(src, dst, add_write_perm=True, copy_metadata=True, optional=False): try: if os.path.isdir(dst): dstfile = dst + "/" + os.path.basename(src) @@ -119,6 +128,12 @@ def try_copy(src, dst, add_write_perm=True, copy_metadata=True): new_mode = os.lstat(dstfile).st_mode | stat.S_IWUSR | stat.S_IWGRP os.chmod(dstfile, new_mode) + except FileNotFoundError as e: + if optional: + log('Error while copying to \"' + dst + '\": ' + e.strerror) + else: + raise + except PermissionError as e: if e.errno == errno.EPERM: #be forgiving about permissions errors; if it's a real problem, things will explode later anyway @@ -162,6 +177,87 @@ 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(): + try: + libdl = CDLL("libdl.so.2") + except (OSError): + return None + + try: + libglx_nvidia = CDLL("libGLX_nvidia.so.0") + except OSError: + 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 @@ -699,6 +795,15 @@ 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, + optional=True) + 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", -- cgit v1.2.3