-
-
Notifications
You must be signed in to change notification settings - Fork 671
/
dxvk.py
184 lines (157 loc) · 7.25 KB
/
dxvk.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
"""DXVK helper module"""
import json
import os
import shutil
from lutris.settings import RUNTIME_DIR
from lutris.util import system
from lutris.util.extract import extract_archive
from lutris.util.http import download_file
from lutris.util.log import logger
DXVK_RELEASES_URL = "https://api.github.com/repos/lutris/dxvk/releases"
def fetch_dxvk_versions():
"""Get DXVK versions from GitHub"""
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")
logger.info("Downloading DXVK releases to %s", versions_path)
return download_file(DXVK_RELEASES_URL, versions_path)
class UnavailableDXVKVersion(RuntimeError):
"""Exception raised when a version of DXVK is not found"""
class DXVKManager:
"""Utility class to install DXVK dlls to a Wine prefix"""
base_dir = os.path.join(RUNTIME_DIR, "dxvk")
dxvk_dlls = ("dxgi", "d3d11", "d3d10core", "d3d9", "d3d12")
def __init__(self, prefix=None, arch="win64", version=None):
self.prefix = prefix
if not os.path.isdir(self.base_dir):
os.makedirs(self.base_dir)
self._versions = []
self._version = version
self.wine_arch = arch
@property
def versions(self):
"""Return available versions"""
self._versions = self.load_dxvk_versions()
if not self._versions:
logger.warning("Loading of DXVK versions failed, defaulting to locally available versions")
self._versions = os.listdir(self.base_dir)
return self._versions
@property
def version(self):
"""Return version of DXVK (latest known version if not provided)"""
if self._version:
return self._version
return self.versions[0]
@property
def dxvk_path(self):
"""Return path to DXVK local cache"""
return os.path.join(self.base_dir, self.version)
def load_dxvk_versions(self):
versions_path = os.path.join(self.base_dir, "dxvk_versions.json")
if not system.path_exists(versions_path):
return []
with open(versions_path, "r") as dxvk_version_file:
dxvk_versions = [v["tag_name"] for v in json.load(dxvk_version_file)]
return dxvk_versions
@staticmethod
def is_dxvk_dll(dll_path):
"""Check if a given DLL path is provided by DXVK
Very basic check to see if a dll contains the string "dxvk".
"""
try:
with open(dll_path, 'rb') as file:
prev_block_end = b''
while True:
block = file.read(2 * 1024 * 1024) # 2 MiB
if not block:
break
if b'dxvk' in prev_block_end + block[:4]:
return True
if b'dxvk' in block:
return True
prev_block_end = block[-4:]
except OSError:
pass
return False
def is_available(self):
"""Return whether DXVK is cached locally"""
return system.path_exists(self.dxvk_path)
def dxvk_dll_exists(self, dll_name):
"""Check if the dll exists as a DXVK variant"""
return system.path_exists(os.path.join(self.dxvk_path, "x64", dll_name + ".dll")
) and system.path_exists(os.path.join(self.dxvk_path, "x32", dll_name + ".dll"))
def get_dxvk_download_url(self):
"""Fetch the download URL for DXVK from the JSON version file"""
versions_path = os.path.join(self.base_dir, "dxvk_versions.json")
with open(versions_path, "r") as dxvk_version_file:
dxvk_releases = json.load(dxvk_version_file)
for release in dxvk_releases:
if release["tag_name"] != self.version:
continue
return release["assets"][0]["browser_download_url"]
def download(self):
"""Download DXVK to the local cache"""
if self.is_available():
logger.warning("DXVK already available at %s", self.dxvk_path)
dxvk_url = self.get_dxvk_download_url()
if not dxvk_url:
logger.warning("Could not find a release for DXVK %s", self.version)
return
dxvk_archive_path = os.path.join(self.base_dir, os.path.basename(dxvk_url))
download_file(dxvk_url, dxvk_archive_path, overwrite=True)
if not system.path_exists(dxvk_archive_path) or not os.stat(dxvk_archive_path).st_size:
logger.error("Failed to download DXVK %s", self.version)
return
extract_archive(dxvk_archive_path, self.dxvk_path, merge_single=True)
os.remove(dxvk_archive_path)
def enable_dxvk_dll(self, system_dir, dxvk_arch, dll):
"""Copies DXVK dlls to the appropriate destination"""
# Copying DXVK's version
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.debug("Replacing %s/%s with DXVK version", system_dir, dll)
if system.path_exists(wine_dll_path):
if not self.is_dxvk_dll(wine_dll_path) and not os.path.islink(wine_dll_path):
# Backing up original version (may not be needed)
shutil.move(wine_dll_path, wine_dll_path + ".orig")
else:
os.remove(wine_dll_path)
try:
os.symlink(dxvk_dll_path, wine_dll_path)
except OSError:
logger.error("Failed linking %s to %s", dxvk_dll_path, wine_dll_path)
else:
self.disable_dxvk_dll(system_dir, dxvk_arch, dll)
def disable_dxvk_dll(self, system_dir, dxvk_arch, dll): # pylint: disable=unused-argument
"""Remove DXVK DLL from Wine prefix"""
wine_dll_path = os.path.join(system_dir, "%s.dll" % dll)
if system.path_exists(wine_dll_path + ".orig"):
if system.path_exists(wine_dll_path):
logger.debug("Removing DXVK dll %s/%s", system_dir, dll)
os.remove(wine_dll_path)
shutil.move(wine_dll_path + ".orig", wine_dll_path)
def _iter_dxvk_dlls(self):
windows_path = os.path.join(self.prefix, "drive_c/windows")
if self.wine_arch == "win64":
system_dirs = {
"x64": os.path.join(windows_path, "system32"),
"x32": os.path.join(windows_path, "syswow64"),
}
elif self.wine_arch == "win32":
system_dirs = {"x32": os.path.join(windows_path, "system32")}
for dxvk_arch, system_dir in system_dirs.items():
for dll in self.dxvk_dlls:
yield system_dir, dxvk_arch, dll
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)
return
for system_dir, dxvk_arch, dll in self._iter_dxvk_dlls():
self.enable_dxvk_dll(system_dir, dxvk_arch, dll)
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)