Repository: lutris/lutris

Issue:
Add fetching and installation of d9vk releases



Discussion:

Resolving patch:
diff --git a/lutris/runners/wine.py b/lutris/runners/wine.py
--- a/lutris/runners/wine.py
+++ b/lutris/runners/wine.py
@@ -143,15 +143,21 @@ def get_wine_version_choices():
                 version_choices.append((label, version))
             return version_choices
 
-        def get_dxvk_choices():
+        def dxvk_choices(manager_class):
             version_choices = [
                 ("Manual", "manual"),
-                (dxvk.DXVK_LATEST, dxvk.DXVK_LATEST),
+                (manager_class.DXVK_LATEST, manager_class.DXVK_LATEST),
             ]
-            for version in dxvk.DXVK_PAST_RELEASES:
+            for version in manager_class.DXVK_PAST_RELEASES:
                 version_choices.append((version, version))
             return version_choices
 
+        def get_dxvk_choices():
+            return dxvk_choices(dxvk.DXVKManager)
+
+        def get_d9vk_choices():
+            return dxvk_choices(dxvk.D9VKManager)
+
         def esync_limit_callback(widget, option, config):
             limits_set = is_esync_limit_set()
             wine_path = self.get_path_for_version(config["version"])
@@ -212,7 +218,24 @@ def dxvk_vulkan_callback(widget, option, config):
                 "advanced": True,
                 "type": "choice_with_entry",
                 "choices": get_dxvk_choices,
-                "default": dxvk.DXVK_LATEST,
+                "default": dxvk.DXVKManager.DXVK_LATEST,
+            },
+            {
+                "option": "d9vk",
+                "label": "Enable D9VK",
+                "type": "extended_bool",
+                "help": "Use D9VK to translate DirectX 9 calls to Vulkan",
+                "callback": dxvk_vulkan_callback,
+                "callback_on": True,
+                "active": True,
+            },
+            {
+                "option": "d9vk_version",
+                "label": "D9VK version",
+                "advanced": True,
+                "type": "choice_with_entry",
+                "choices": get_d9vk_choices,
+                "default": dxvk.D9VKManager.DXVK_LATEST,
             },
             {
                 "option": "esync",
@@ -697,11 +720,7 @@ def set_regedit_keys(self):
 
                 prefix_manager.set_registry_key(path, key, value)
 
-    def toggle_dxvk(self, enable, version=None):
-        dxvk_manager = dxvk.DXVKManager(
-            self.prefix_path, arch=self.wine_arch, version=version
-        )
-
+    def toggle_dxvk(self, enable, version=None, dxvk_manager: dxvk.DXVKManager = None):
         # manual version only sets the dlls to native
         if version.lower() != "manual":
             if enable:
@@ -717,6 +736,18 @@ def toggle_dxvk(self, enable, version=None):
                 if dxvk_manager.dxvk_dll_exists(dll):
                     self.dll_overrides[dll] = "n"
 
+    def setup_dxvk(self, base_name, dxvk_manager: dxvk.DXVKManager = None):
+        if not dxvk_manager:
+            return
+        try:
+            self.toggle_dxvk(
+                bool(self.runner_config.get(base_name)),
+                version=dxvk_manager.version,
+                dxvk_manager=dxvk_manager,
+            )
+        except dxvk.UnavailableDXVKVersion:
+            raise GameConfigError("Unable to get "+base_name.upper()+" %s" % dxvk_manager.version)
+
     def prelaunch(self):
         if not system.path_exists(os.path.join(self.prefix_path, "user.reg")):
             create_prefix(self.prefix_path, arch=self.wine_arch)
@@ -726,14 +757,15 @@ def prelaunch(self):
         self.sandbox(prefix_manager)
         self.set_regedit_keys()
         self.setup_x360ce(self.runner_config.get("x360ce-path"))
-        try:
-            dxvk_version = self.runner_config.get("dxvk_version")
-            self.toggle_dxvk(
-                bool(self.runner_config.get("dxvk")),
-                version=dxvk_version,
-            )
-        except dxvk.UnavailableDXVKVersion:
-            raise GameConfigError("Unable to get DXVK %s" % dxvk_version)
+        self.setup_dxvk("dxvk", dxvk_manager=dxvk.DXVKManager(
+                    self.prefix_path, arch=self.wine_arch, version=self.runner_config.get("dxvk_version")
+                ),)
+
+        # we don't want d9vk to restore d3d9.dll, because dxvk could set it already
+        if bool(self.runner_config.get("d9vk")):
+            self.setup_dxvk("d9vk", dxvk_manager=dxvk.D9VKManager(
+                        self.prefix_path, arch=self.wine_arch, version=self.runner_config.get("d9vk_version")
+                    ),)
         return True
 
     def get_dll_overrides(self):
@@ -861,7 +893,7 @@ def sandbox(self, wine_prefix):
     def play(self):
         game_exe = self.game_exe
         arguments = self.game_config.get("args", "")
-        using_dxvk = self.runner_config.get("dxvk")
+        using_dxvk = self.runner_config.get("dxvk") or self.runner_config.get("d9vk")
 
         if using_dxvk:
             if not is_vulkan_supported():
diff --git a/lutris/util/wine/dxvk.py b/lutris/util/wine/dxvk.py
--- a/lutris/util/wine/dxvk.py
+++ b/lutris/util/wine/dxvk.py
@@ -12,39 +12,40 @@
 from lutris.util import system
 
 CACHE_MAX_AGE = 86400  # Re-download DXVK versions every day
-DXVK_TAGS_URL = "https://api.github.com/repos/doitsujin/dxvk/tags"
-DXVK_VERSIONS = [
-    "0.94",
-]
-DXVK_LATEST, DXVK_PAST_RELEASES = DXVK_VERSIONS[0], DXVK_VERSIONS[1:]
-
-
-def get_dxvk_versions():
-    """Get DXVK versions from GitHub"""
-    logger.info("Updating DXVK versions")
-    dxvk_path = os.path.join(RUNTIME_DIR, "dxvk")
-    if not os.path.isdir(dxvk_path):
-        os.mkdir(dxvk_path)
-    versions_path = os.path.join(dxvk_path, "dxvk_versions.json")
-
-    urllib.request.urlretrieve(DXVK_TAGS_URL, versions_path)
-
-    with open(versions_path, "r") as dxvk_tags:
-        dxvk_json = json.load(dxvk_tags)
-        dxvk_versions = [x["name"].replace("v", "") for x in dxvk_json]
-    return dxvk_versions
 
 
 @system.run_once
 def init_dxvk_versions():
-    global DXVK_VERSIONS
-    global DXVK_LATEST
-    global DXVK_PAST_RELEASES
-    try:
-        DXVK_VERSIONS = get_dxvk_versions()
-    except Exception as ex:  # pylint: disable= broad-except
-        logger.error(ex)
-    DXVK_LATEST, DXVK_PAST_RELEASES = DXVK_VERSIONS[0], DXVK_VERSIONS[1:]
+    def get_dxvk_versions(base_name, tags_url):
+        """Get DXVK versions from GitHub"""
+        logger.info("Updating "+base_name.upper()+" versions")
+        dxvk_path = os.path.join(RUNTIME_DIR, base_name)
+        if not os.path.isdir(dxvk_path):
+            os.mkdir(dxvk_path)
+        versions_path = os.path.join(dxvk_path, base_name+"_versions.json")
+
+        urllib.request.urlretrieve(tags_url, versions_path)
+
+        with open(versions_path, "r") as dxvk_tags:
+            dxvk_json = json.load(dxvk_tags)
+            dxvk_versions = list()
+            for x in dxvk_json:
+                version_name = x["name"].replace("v", "")
+                if version_name.startswith('m'):  # ignore master snapshots of d9vk
+                    continue
+                dxvk_versions.append(version_name)
+        return dxvk_versions
+
+    def init_versions(manager):
+        try:
+            manager.DXVK_VERSIONS \
+                = get_dxvk_versions(manager.base_name, manager.DXVK_TAGS_URL)
+        except Exception as ex:  # pylint: disable= broad-except
+            logger.error(ex)
+        manager.DXVK_LATEST, manager.DXVK_PAST_RELEASES = manager.DXVK_VERSIONS[0], manager.DXVK_VERSIONS[1:9]
+
+    init_versions(DXVKManager)
+    init_versions(D9VKManager)
 
 
 class UnavailableDXVKVersion(RuntimeError):
@@ -54,8 +55,15 @@ class UnavailableDXVKVersion(RuntimeError):
 class DXVKManager:
     """Utility class to install DXVK dlls to a Wine prefix"""
 
+    DXVK_TAGS_URL = "https://api.github.com/repos/doitsujin/dxvk/tags"
+    DXVK_VERSIONS = [
+        "0.94",
+    ]
+    DXVK_LATEST, DXVK_PAST_RELEASES = DXVK_VERSIONS[0], DXVK_VERSIONS[1:9]
+
     base_url = "https://github.com/doitsujin/dxvk/releases/download/v{}/dxvk-{}.tar.gz"
-    base_dir = os.path.join(RUNTIME_DIR, "dxvk")
+    base_name = "dxvk"
+    base_dir = os.path.join(RUNTIME_DIR, base_name)
     dxvk_dlls = ("dxgi", "d3d11", "d3d10core", "d3d10_1", "d3d10", "d3d9")
     latest_version = DXVK_LATEST
 
@@ -105,7 +113,7 @@ def download(self):
         """Download DXVK to the local cache"""
         dxvk_url = self.base_url.format(self.version, self.version)
         if self.is_available():
-            logger.warning("DXVK already available at %s", self.dxvk_path)
+            logger.warning(self.base_name.upper()+" already available at %s", self.dxvk_path)
 
         dxvk_archive_path = os.path.join(self.base_dir, os.path.basename(dxvk_url))
 
@@ -114,13 +122,13 @@ def download(self):
         while downloader.check_progress() < 1 and downloader.state != downloader.ERROR:
             time.sleep(0.3)
         if not system.path_exists(dxvk_archive_path):
-            raise UnavailableDXVKVersion("Failed to download DXVK %s" % self.version)
+            raise UnavailableDXVKVersion("Failed to download "+self.base_name.upper()+" %s" % self.version)
         if os.stat(dxvk_archive_path).st_size:
             extract_archive(dxvk_archive_path, self.dxvk_path, merge_single=True)
             os.remove(dxvk_archive_path)
         else:
             os.remove(dxvk_archive_path)
-            raise UnavailableDXVKVersion("Failed to download DXVK %s" % self.version)
+            raise UnavailableDXVKVersion("Failed to download "+self.base_name.upper()+" %s" % self.version)
 
     def enable_dxvk_dll(self, system_dir, dxvk_arch, dll):
         """Copies DXVK dlls to the appropriate destination"""
@@ -128,7 +136,7 @@ def enable_dxvk_dll(self, system_dir, dxvk_arch, dll):
         dxvk_dll_path = os.path.join(self.dxvk_path, dxvk_arch, "%s.dll" % dll)
         if system.path_exists(dxvk_dll_path):
             wine_dll_path = os.path.join(system_dir, "%s.dll" % dll)
-            logger.info("Replacing %s/%s with DXVK version", system_dir, dll)
+            logger.info("Replacing %s/%s with "+self.base_name.upper()+" version", system_dir, dll)
             if not self.is_dxvk_dll(wine_dll_path):
                 # Backing up original version (may not be needed)
                 if system.path_exists(wine_dll_path):
@@ -143,7 +151,7 @@ def disable_dxvk_dll(self, system_dir, dxvk_arch, dll):
         """Remove DXVK DLL from Wine prefix"""
         wine_dll_path = os.path.join(system_dir, "%s.dll" % dll)
         if self.is_dxvk_dll(wine_dll_path):
-            logger.info("Removing DXVK dll %s/%s", system_dir, dll)
+            logger.info("Removing "+self.base_name.upper()+" dll %s/%s", system_dir, dll)
             os.remove(wine_dll_path)
         # Restoring original version (may not be needed)
         if system.path_exists(wine_dll_path + ".orig"):
@@ -166,7 +174,7 @@ def _iter_dxvk_dlls(self):
     def enable(self):
         """Enable DXVK for the current prefix"""
         if not system.path_exists(self.dxvk_path):
-            logger.error("DXVK %s is not available locally", self.version)
+            logger.error(self.base_name.upper()+" %s is not available locally", self.version)
             return
         for system_dir, dxvk_arch, dll in self._iter_dxvk_dlls():
             self.enable_dxvk_dll(system_dir, dxvk_arch, dll)
@@ -175,3 +183,17 @@ def disable(self):
         """Disable DXVK for the current prefix"""
         for system_dir, dxvk_arch, dll in self._iter_dxvk_dlls():
             self.disable_dxvk_dll(system_dir, dxvk_arch, dll)
+
+
+class D9VKManager(DXVKManager):
+    DXVK_TAGS_URL = "https://api.github.com/repos/Joshua-Ashton/d9vk/tags"
+    DXVK_VERSIONS = [
+        "0.10",
+    ]
+    DXVK_LATEST, DXVK_PAST_RELEASES = DXVK_VERSIONS[0], DXVK_VERSIONS[1:9]
+
+    base_url = "https://github.com/Joshua-Ashton/d9vk/releases/download/{}/d9vk-{}.tar.gz"
+    base_name = "d9vk"
+    base_dir = os.path.join(RUNTIME_DIR, base_name)
+    dxvk_dlls = ("d3d9",)
+    latest_version = DXVK_LATEST


Test patch: