PortWINE/data_from_portwine/scripts/port_on

445 lines
17 KiB
Python
Executable File

#!/usr/bin/env python3
#script to launch Wine with the correct environment
import fcntl
import array
import filecmp
import fnmatch
import json
import os
import os.path
import shutil
import errno
import stat
import subprocess
import sys
import tarfile
#To enable debug logging, copy "user_settings.sample.py" to "user_settings.py"
#and edit it if needed.
PFX="Proton: "
ld_path_var = "LD_LIBRARY_PATH"
def nonzero(s):
return len(s) > 0 and s != "0"
def log(msg):
sys.stderr.write(PFX + msg + os.linesep)
sys.stderr.flush()
def makedirs(path):
try:
os.makedirs(path)
except OSError:
#already exists
pass
def try_copy(src, dst, add_write_perm=True):
try:
if os.path.isdir(dst):
dstfile = dst + "/" + os.path.basename(src)
if os.path.lexists(dstfile):
os.remove(dstfile)
else:
dstfile = dst
if os.path.lexists(dst):
os.remove(dst)
shutil.copy(src, dst)
if add_write_perm:
new_mode = os.lstat(dstfile).st_mode | stat.S_IWUSR | stat.S_IWGRP
os.chmod(dstfile, new_mode)
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
log('Error while copying to \"' + dst + '\": ' + e.strerror)
else:
raise
def try_copyfile(src, dst):
try:
if os.path.isdir(dst):
dstfile = dst + "/" + os.path.basename(src)
if os.path.lexists(dstfile):
os.remove(dstfile)
elif os.path.lexists(dst):
os.remove(dst)
shutil.copyfile(src, dst)
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
log('Error while copying to \"' + dst + '\": ' + e.strerror)
else:
raise
def getmtimestr(*path_fragments):
path = os.path.join(*path_fragments)
try:
return str(os.path.getmtime(path))
except IOError:
return "0"
class Proton:
def __init__(self, base_dir):
self.base_dir = os.environ["PW_COMPAT_DATA_PATH"]
self.dist_dir = os.environ["WINEDIR"]
self.bin_dir = self.dist_dir + "/bin/"
self.lib_dir = self.dist_dir + "/lib/"
self.lib64_dir = self.dist_dir + "/lib64/"
self.fonts_dir = self.dist_dir + "/share/fonts/"
self.wine_bin = self.bin_dir + "/wine"
self.wineserver_bin = self.bin_dir + "/wineserver"
self.gamemoderun = "gamemoderun"
self.pw_launch_parameters = os.environ["LAUNCH_PARAMETERS"]
def path(self, d):
return self.base_dir + d
class CompatData:
def __init__(self, compatdata):
self.base_dir = os.environ["PW_COMPAT_DATA_PATH"]
self.prefix_dir = self.path("pfx/")
def path(self, d):
return self.base_dir + d
def create_fonts_symlinks(self):
fontsmap = [
( "LiberationSans-Regular.ttf", "arial.ttf" ),
( "LiberationSans-Bold.ttf", "arialbd.ttf" ),
( "LiberationSerif-Regular.ttf", "times.ttf" ),
( "LiberationMono-Regular.ttf", "cour.ttf" ),
( "SourceHanSansSCRegular.otf", "msyh.ttf" ),
]
windowsfonts = self.prefix_dir + "/drive_c/windows/Fonts"
makedirs(windowsfonts)
for p in fontsmap:
lname = os.path.join(windowsfonts, p[1])
fname = os.path.join(g_proton.fonts_dir, p[0])
if os.path.lexists(lname):
if os.path.islink(lname):
os.remove(lname)
os.symlink(fname, lname)
else:
os.symlink(fname, lname)
def setup_prefix(self):
if not os.path.exists(self.prefix_dir):
makedirs(self.prefix_dir + "/drive_c")
set_dir_casefold_bit(self.prefix_dir + "/drive_c")
use_wined3d = "wined3d" in g_session.compat_config
builtin_dll_copy = os.environ.get("PROTON_DLL_COPY",
# #dxsetup redist
# "d3dcompiler_*.dll," +
# "d3dcsx*.dll," +
# "d3dx*.dll," +
# "x3daudio*.dll," +
# "xactengine*.dll," +
# "xapofx*.dll," +
# "xaudio*.dll," +
# "xinput*.dll," +
# #vcruntime redist
"atl1*.dll," +
# "concrt1*.dll," +
# "msvcp1*.dll," +
# "msvcr1*.dll," +
# "vcamp1*.dll," +
# "vcomp1*.dll," +
# "vccorlib1*.dll," +
# "vcruntime1*.dll," +
# "api-ms-win-crt-conio-l1-1-0.dll," +
# "api-ms-win-crt-heap-l1-1-0.dll," +
# "api-ms-win-crt-locale-l1-1-0.dll," +
# "api-ms-win-crt-math-l1-1-0.dll," +
# "api-ms-win-crt-runtime-l1-1-0.dll," +
# "api-ms-win-crt-stdio-l1-1-0.dll," +
"ucrtbase.dll," +
#some games balk at ntdll symlink(?)
"ntdll.dll," +
#some games require official vulkan loader
"vulkan-1.dll"
)
#create font files symlinks
self.create_fonts_symlinks()
if "var_pw_vulkan" in os.environ and nonzero(os.environ["var_pw_vulkan"]):
#copy openvr files into place
if os.path.isfile(g_proton.lib_dir + "wine/fakedlls/vrclient.dll"):
dst = self.prefix_dir + "/drive_c/vrclient/bin/"
makedirs(dst)
try_copy(g_proton.lib_dir + "wine/fakedlls/vrclient.dll", dst)
try_copy(g_proton.lib64_dir + "wine/fakedlls/vrclient_x64.dll", dst)
if os.path.isfile(g_proton.lib_dir + "wine/dxvk/openvr_api_dxvk.dll"):
try_copy(g_proton.lib_dir + "wine/dxvk/openvr_api_dxvk.dll", self.prefix_dir + "/drive_c/windows/syswow64/")
try_copy(g_proton.lib64_dir + "wine/dxvk/openvr_api_dxvk.dll", self.prefix_dir + "/drive_c/windows/system32/")
if use_wined3d:
dxvkfiles = []
wined3dfiles = ["d3d11", "d3d10", "d3d10core", "d3d10_1", "d3d9"]
if os.path.isfile(g_proton.lib64_dir + "wine/dxvk/dxvk_config.dll"):
dxvkfiles.append("dxvk_config")
os.system("echo PW_VULKAN_USE=vkd3d")
else:
dxvkfiles = ["dxvk_config", "d3d11", "d3d10", "d3d10core", "d3d10_1", "d3d9"]
wined3dfiles = []
os.system("echo PW_VULKAN_USE=dxvk")
#if the user asked for dxvk's dxgi (dxgi=n), then copy it into place
if "PW_DXGI_FROM_DXVK" in os.environ and nonzero(os.environ["PW_DXGI_FROM_DXVK"]):
dxvkfiles.append("dxgi")
else:
wined3dfiles.append("dxgi")
for f in wined3dfiles:
try_copy(g_proton.lib64_dir + "wine/" + f + ".dll", self.prefix_dir + "drive_c/windows/system32/" + f + ".dll")
try_copy(g_proton.lib_dir + "wine/" + f + ".dll", self.prefix_dir + "drive_c/windows/syswow64/" + f + ".dll")
for f in dxvkfiles:
if os.path.isfile(g_proton.lib64_dir + "wine/dxvk/" + f + ".dll"):
try_copy(g_proton.lib64_dir + "wine/dxvk/" + f + ".dll", self.prefix_dir + "drive_c/windows/system32/" + f + ".dll")
try_copy(g_proton.lib_dir + "wine/dxvk/" + f + ".dll", self.prefix_dir + "drive_c/windows/syswow64/" + f + ".dll")
g_session.dlloverrides[f] = "n"
if os.path.isfile(g_proton.lib64_dir + "wine/vkd3d-proton/d3d12.dll"):
try_copy(g_proton.lib64_dir + "wine/vkd3d-proton/d3d12.dll", self.prefix_dir + "drive_c/windows/system32/d3d12.dll")
if os.path.isfile(g_proton.lib_dir + "wine/vkd3d-proton/d3d12.dll"):
try_copy(g_proton.lib_dir + "wine/vkd3d-proton/d3d12.dll", self.prefix_dir + "drive_c/windows/syswow64/d3d12.dll")
else:
os.system("echo PW_VULKAN_USE=0 - Vulkan is disabled")
def comma_escaped(s):
escaped = False
idx = -1
while s[idx] == '\\':
escaped = not escaped
idx = idx - 1
return escaped
class Session:
def __init__(self):
self.env = dict(os.environ)
self.dlloverrides = {
"winemenubuilder.exe": "",
"dotnetfx35.exe": "b", #replace the broken installer, as does Windows
"mfplay": "n", #disable built-in mfplay
"steam_api": "n", #disable built-in steam dll
"steam_api64": "n", #disable built-in steam dll
"steamclient": "n", #disable built-in steam dll
"steamclient64": "n", #disable built-in steam dll
"steamworks.net": "n" #disable built-in steam dll
}
self.compat_config = set()
self.cmdlineappend = []
def init_wine(self):
self.env.pop("WINEARCH", "")
if 'ORIG_'+ld_path_var not in os.environ:
# Allow wine to restore this when calling an external app.
self.env['ORIG_'+ld_path_var] = os.environ.get(ld_path_var, '')
if ld_path_var in os.environ:
self.env[ld_path_var] = g_proton.lib64_dir + ":" + g_proton.lib_dir + ":" + os.environ[ld_path_var]
else:
self.env[ld_path_var] = g_proton.lib64_dir + ":" + g_proton.lib_dir
self.env["WINEDLLPATH"] = g_proton.lib64_dir + "/wine:" + g_proton.lib_dir + "/wine"
self.env["GST_PLUGIN_SYSTEM_PATH_1_0"] = g_proton.lib64_dir + "gstreamer-1.0" + ":" + g_proton.lib_dir + "gstreamer-1.0"
self.env["WINE_GST_REGISTRY_DIR"] = g_compatdata.path("/tmp/gstreamer-1.0/")
if "PW_COMPAT_MEDIA_PATH" in os.environ:
self.env["MEDIACONV_AUDIO_DUMP_FILE"] = os.environ["PW_COMPAT_MEDIA_PATH"] + "/audio.foz"
self.env["MEDIACONV_AUDIO_TRANSCODED_FILE"] = os.environ["PW_COMPAT_MEDIA_PATH"] + "/transcoded_audio.foz"
self.env["MEDIACONV_VIDEO_DUMP_FILE"] = os.environ["PW_COMPAT_MEDIA_PATH"] + "/video.foz"
self.env["MEDIACONV_VIDEO_TRANSCODED_FILE"] = os.environ["PW_COMPAT_MEDIA_PATH"] + "/transcoded_video.foz"
if "PATH" in os.environ:
self.env["PATH"] = g_proton.bin_dir + ":" + os.environ["PATH"]
else:
self.env["PATH"] = g_proton.bin_dir
def check_environment(self, env_name, config_name):
if not env_name in self.env:
return False
if nonzero(self.env[env_name]):
self.compat_config.add(config_name)
else:
self.compat_config.discard(config_name)
return True
def init_session(self):
self.env["WINEPREFIX"] = g_compatdata.prefix_dir
#load environment overrides
if "PW_LOG" in os.environ and nonzero(os.environ["PW_LOG"]):
self.env.setdefault("WINEDEBUG", "+timestamp,+pid,+tid,+seh,+debugstr,+mscoree")
self.env.setdefault("DXVK_LOG_LEVEL", "info")
self.env.setdefault("VKD3D_DEBUG", "warn")
self.env.setdefault("WINE_MONO_TRACE", "E:System.NotImplementedException")
else:
self.env.setdefault("WINEDEBUG", "-all")
self.env.setdefault("DXVK_LOG_LEVEL", "none")
self.env.setdefault("VKD3D_DEBUG", "none")
self.env.setdefault("DXVK_LOG_PATH","none")
#default wine-mono override for FNA games
self.env.setdefault("WINE_MONO_OVERRIDES", "Microsoft.Xna.Framework.*,Gac=n")
if "wined3d11" in self.compat_config:
self.compat_config.add("wined3d")
if not self.check_environment("PW_USE_WINED3D", "wined3d"):
self.check_environment("PW_USE_WINED3D11", "wined3d")
self.check_environment("PW_NO_ESYNC", "noesync")
self.check_environment("PW_NO_FSYNC", "nofsync")
self.check_environment("PW_FORCE_LARGE_ADDRESS_AWARE", "forcelgadd")
self.check_environment("PW_OLD_GL_STRING", "oldglstr")
self.check_environment("PW_NO_WINEMFPLAY", "nomfplay")
self.check_environment("PW_NO_WRITE_WATCH", "nowritewatch")
self.check_environment("PW_DXVK_NO_ASYNC", "dxvknoasync")
self.check_environment("PW_NVAPI_DISABLE", "nonvapi")
self.check_environment("PW_WINEDBG_DISABLE", "nowinedbg")
self.check_environment("PW_HIDE_NVIDIA_GPU", "hidenvgpu")
self.check_environment("PW_VKD3D_FEATURE_LEVEL", "vkd3dfl12")
self.check_environment("PW_DX12_DISABLED", "nod3d12")
if "noesync" in self.compat_config:
self.env.pop("WINEESYNC", "")
else:
self.env["WINEESYNC"] = "1"
if "nofsync" in self.compat_config:
self.env.pop("WINEFSYNC", "")
else:
self.env["WINEFSYNC"] = "1"
if "dxvknoasync" in self.compat_config:
self.env["RADV_DEBUG"] = "llvm"
if "nowritewatch" in self.compat_config:
self.env["WINE_DISABLE_WRITE_WATCH"] = "1"
if "oldglstr" in self.compat_config:
#mesa override
self.env["MESA_EXTENSION_MAX_YEAR"] = "2003"
#nvidia override
self.env["__GL_ExtensionStringVersion"] = "17700"
if "forcelgadd" in self.compat_config:
self.env["WINE_LARGE_ADDRESS_AWARE"] = "1"
if "vkd3dfl12" in self.compat_config:
if not "VKD3D_FEATURE_LEVEL" in self.env:
self.env["VKD3D_FEATURE_LEVEL"] = "12_0"
if "hidenvgpu" in self.compat_config:
self.env["WINE_HIDE_NVIDIA_GPU"] = "1"
g_compatdata.setup_prefix()
if "nowritewatch" in self.compat_config:
self.env["WINE_DISABLE_WRITE_WATCH"] = "1"
if "nonvapi" in self.compat_config:
self.dlloverrides["nvapi"] = ""
self.dlloverrides["nvapi64"] = ""
if "nowinedbg" in self.compat_config:
self.dlloverrides["winedbg.exe"] = ""
if "var_pw_vulkan" in os.environ and nonzero(os.environ["var_pw_vulkan"]):
os.system("echo Use vulkan")
else:
self.dlloverrides["vrclient"] = ""
self.dlloverrides["openvr_api_dxvk"] = ""
self.dlloverrides["dxvk_config"] = ""
self.dlloverrides["dxgi"] = "b"
self.dlloverrides["d3d11"] = "b"
self.dlloverrides["d3d10"] = "b"
self.dlloverrides["d3d10core"] = "b"
self.dlloverrides["d3d10_1"] = "b"
self.dlloverrides["d3d9"] = "b"
self.dlloverrides["d3d12"] = "b"
os.system("echo All d3d dll use as built-in")
if "nod3d12" in self.compat_config:
self.dlloverrides["d3d12"] = ""
s = ""
for dll in self.dlloverrides:
setting = self.dlloverrides[dll]
if len(s) > 0:
s = s + ";" + dll + "=" + setting
else:
s = dll + "=" + setting
if "WINEDLLOVERRIDES" in self.env:
self.env["WINEDLLOVERRIDES"] = self.env["WINEDLLOVERRIDES"] + ";" + s
else:
self.env["WINEDLLOVERRIDES"] = s
def run_proc(self, args, local_env=None):
if local_env is None:
local_env = self.env
subprocess.call(args, env=local_env)
def run(self):
if "PW_GAMEMODERUN" in os.environ and nonzero(os.environ["PW_GAMEMODERUN"]):
self.run_proc([g_proton.gamemoderun] + [g_proton.wine_bin] + sys.argv[2:] + [g_proton.pw_launch_parameters])
else:
self.run_proc([g_proton.wine_bin] + sys.argv[2:] + [g_proton.pw_launch_parameters])
if __name__ == "__main__":
if not "PW_COMPAT_DATA_PATH" in os.environ:
log("No compat data path?")
sys.exit(1)
g_proton = Proton(os.path.dirname(sys.argv[0]))
g_compatdata = CompatData(os.environ["PW_COMPAT_DATA_PATH"])
g_session = Session()
g_session.init_wine()
g_session.init_session()
#determine mode
if sys.argv[1] == "run":
#start target app
g_session.run()
elif sys.argv[1] == "waitforexitandrun":
#wait for wineserver to shut down
g_session.run_proc([g_proton.wineserver_bin, "-w"])
#then run
g_session.run()
elif sys.argv[1] == "getcompatpath":
#linux -> windows path
path = subprocess.check_output([g_proton.wine_bin, "winepath", "-w", sys.argv[2]], env=g_session.env)
sys.stdout.buffer.write(path)
elif sys.argv[1] == "getnativepath":
#windows -> linux path
path = subprocess.check_output([g_proton.wine_bin, "winepath", sys.argv[2]], env=g_session.env)
sys.stdout.buffer.write(path)
else:
log("Need a verb.")
sys.exit(1)
sys.exit(0)
#pylint --disable=C0301,C0326,C0330,C0111,C0103,R0902,C1801,R0914,R0912,R0915
# vim: set syntax=python: