forked from CastroFidel/PortWINE
		
	
		
			
				
	
	
		
			709 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			709 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python3
 | |
| 
 | |
| #script to launch Wine with the correct environment
 | |
| 
 | |
| import fcntl
 | |
| import array
 | |
| import filecmp
 | |
| import json
 | |
| import os
 | |
| import shutil
 | |
| import errno
 | |
| import subprocess
 | |
| import sys
 | |
| import tarfile
 | |
| 
 | |
| from filelock import FileLock
 | |
| 
 | |
| #To enable debug logging, copy "user_settings.sample.py" to "user_settings.py"
 | |
| #and edit it if needed.
 | |
| 
 | |
| CURRENT_PREFIX_VERSION="5.6-GE-2"
 | |
| 
 | |
| 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 file_is_wine_fake_dll(path):
 | |
|     if not os.path.exists(path):
 | |
|         return False
 | |
|     try:
 | |
|         sfile = open(path, "rb")
 | |
|         sfile.seek(0x40)
 | |
|         tag = sfile.read(20)
 | |
|         return tag == b"Wine placeholder DLL"
 | |
|     except IOError:
 | |
|         return False
 | |
| 
 | |
| def makedirs(path):
 | |
|     try:
 | |
|         os.makedirs(path)
 | |
|     except OSError:
 | |
|         #already exists
 | |
|         pass
 | |
| 
 | |
| def try_copy(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.copy(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 real_copy(src, dst):
 | |
|     if os.path.islink(src):
 | |
|         os.symlink(os.readlink(src), dst)
 | |
|     else:
 | |
|         try_copy(src, dst)
 | |
| 
 | |
| EXT2_IOC_GETFLAGS = 0x80086601
 | |
| EXT2_IOC_SETFLAGS = 0x40086602
 | |
| 
 | |
| EXT4_CASEFOLD_FL = 0x40000000
 | |
| 
 | |
| def set_dir_casefold_bit(dir_path):
 | |
|     dr = os.open(dir_path, 0o644)
 | |
|     if dr < 0:
 | |
|         return
 | |
|     try:
 | |
|         dat = array.array('I', [0])
 | |
|         if fcntl.ioctl(dr, EXT2_IOC_GETFLAGS, dat, True) >= 0:
 | |
|             dat[0] = dat[0] | EXT4_CASEFOLD_FL
 | |
|             fcntl.ioctl(dr, EXT2_IOC_SETFLAGS, dat, False)
 | |
|     except OSError:
 | |
|         #no problem
 | |
|         pass
 | |
|     os.close(dr)
 | |
| 
 | |
| class Proton:
 | |
|     def __init__(self, base_dir):
 | |
|         self.base_dir = os.environ["STEAM_COMPAT_DATA_PATH"]
 | |
|         self.dist_dir = self.path("dist/")
 | |
|         self.bin_dir = self.path("dist/bin/")
 | |
|         self.lib_dir = self.path("dist/lib/")
 | |
|         self.lib64_dir = self.path("dist/lib64/")
 | |
|         self.fonts_dir = self.path("dist/share/fonts/")
 | |
|         self.version_file = self.path("version")
 | |
|         self.default_pfx_dir = self.path("dist/share/default_pfx/")
 | |
|         self.user_settings_file = self.path("user_settings.py")
 | |
|         self.wine_bin = self.bin_dir + "wine"
 | |
|         self.wineserver_bin = self.bin_dir + "wineserver"
 | |
|         self.dist_lock = FileLock(self.path("dist.lock"), timeout=-1)
 | |
| 
 | |
|     def path(self, d):
 | |
|         return self.base_dir + d
 | |
| 
 | |
|     def extract_tarball(self):
 | |
|         with self.dist_lock:
 | |
|             if not os.path.exists(self.dist_dir) or \
 | |
|                     not os.path.exists(self.path("dist/version")) or \
 | |
|                     not filecmp.cmp(self.version_file, self.path("dist/version")):
 | |
|                 if os.path.exists(self.dist_dir):
 | |
|                     shutil.rmtree(self.dist_dir)
 | |
|                 tar = None
 | |
|                 for sf in ["", ".xz", ".bz2", ".gz"]:
 | |
|                     if os.path.exists(self.path("proton_dist.tar" + sf)):
 | |
|                         tar = tarfile.open(self.path("proton_dist.tar" + sf), mode="r:*")
 | |
|                         break
 | |
|                 if not tar:
 | |
|                     log("No proton_dist tarball??")
 | |
|                     sys.exit(1)
 | |
|                 tar.extractall(path=self.dist_dir)
 | |
|                 tar.close()
 | |
|                 try_copy(self.version_file, self.dist_dir)
 | |
| 
 | |
|     def make_default_prefix(self):
 | |
|         with self.dist_lock:
 | |
|             local_env = dict(g_session.env)
 | |
|             if not os.path.isdir(self.default_pfx_dir):
 | |
|                 #make default prefix
 | |
|                 local_env["WINEPREFIX"] = self.default_pfx_dir
 | |
|                 local_env["WINEDEBUG"] = "-all"
 | |
|                 g_session.run_proc([self.wine_bin, "wineboot"], local_env)
 | |
|                 g_session.run_proc([self.wineserver_bin, "-w"], local_env)
 | |
| 
 | |
| class CompatData:
 | |
|     def __init__(self, compatdata):
 | |
|         self.base_dir = os.environ["STEAM_COMPAT_DATA_PATH"]
 | |
|         self.prefix_dir = self.path("pfx/")
 | |
|         self.version_file = self.path("version")
 | |
|         self.tracked_files_file = self.path("tracked_files")
 | |
|         self.prefix_lock = FileLock(self.path("pfx.lock"), timeout=-1)
 | |
| 
 | |
|     def path(self, d):
 | |
|         return self.base_dir + d
 | |
| 
 | |
|     def remove_tracked_files(self):
 | |
|         if not os.path.exists(self.tracked_files_file):
 | |
|             log("Prefix has no tracked_files??")
 | |
|             return
 | |
| 
 | |
|         with open(self.tracked_files_file, "r") as tracked_files:
 | |
|             dirs = []
 | |
|             for f in tracked_files:
 | |
|                 path = self.prefix_dir + f.strip()
 | |
|                 if os.path.exists(path):
 | |
|                     if os.path.isfile(path) or os.path.islink(path):
 | |
|                         os.remove(path)
 | |
|                     else:
 | |
|                         dirs.append(path)
 | |
|             for d in dirs:
 | |
|                 try:
 | |
|                     os.rmdir(d)
 | |
|                 except OSError:
 | |
|                     #not empty
 | |
|                     pass
 | |
| 
 | |
|         os.remove(self.tracked_files_file)
 | |
|         os.remove(self.version_file)
 | |
| 
 | |
|     def upgrade_pfx(self, old_ver):
 | |
|         if old_ver == CURRENT_PREFIX_VERSION:
 | |
|             return
 | |
| 
 | |
|         log("Upgrading prefix from " + str(old_ver) + " to " + CURRENT_PREFIX_VERSION + " (" + self.base_dir + ")")
 | |
| 
 | |
|         if old_ver is None:
 | |
|             return
 | |
| 
 | |
|         if not '-' in old_ver:
 | |
|             #How can this happen??
 | |
|             log("Prefix has an invalid version?! You may want to back up user files and delete this prefix.")
 | |
|             #If it does, just let the Wine upgrade happen and hope it works...
 | |
|             return
 | |
| 
 | |
|         try:
 | |
|             old_proton_ver, old_prefix_ver = old_ver.split('-')
 | |
|             old_proton_maj, old_proton_min = old_proton_ver.split('.')
 | |
|             new_proton_ver, new_prefix_ver = CURRENT_PREFIX_VERSION.split('-')
 | |
|             new_proton_maj, new_proton_min = new_proton_ver.split('.')
 | |
| 
 | |
|             if int(new_proton_maj) < int(old_proton_maj) or \
 | |
|                     (int(new_proton_maj) == int(old_proton_maj) and \
 | |
|                      int(new_proton_min) < int(old_proton_min)):
 | |
|                 log("Removing newer prefix")
 | |
|                 if old_proton_ver == "3.7" and not os.path.exists(self.tracked_files_file):
 | |
|                     #proton 3.7 did not generate tracked_files, so copy it into place first
 | |
|                     try_copy(g_proton.path("proton_3.7_tracked_files"), self.tracked_files_file)
 | |
|                 self.remove_tracked_files()
 | |
|                 return
 | |
| 
 | |
|             if old_proton_ver == "3.7" and old_prefix_ver == "1":
 | |
|                 if not os.path.exists(self.prefix_dir + "/drive_c/windows/syswow64/kernel32.dll"):
 | |
|                     #shipped a busted 64-bit-only installation on 20180822. detect and wipe clean
 | |
|                     log("Detected broken 64-bit-only installation, re-creating prefix.")
 | |
|                     shutil.rmtree(self.prefix_dir)
 | |
|                     return
 | |
| 
 | |
|             #replace broken .NET installations with wine-mono support
 | |
|             if os.path.exists(self.prefix_dir + "/drive_c/windows/Microsoft.NET/NETFXRepair.exe") and \
 | |
|                     file_is_wine_fake_dll(self.prefix_dir + "/drive_c/windows/system32/mscoree.dll"):
 | |
|                 log("Broken .NET installation detected, switching to wine-mono.")
 | |
|                 #deleting this directory allows wine-mono to work
 | |
|                 shutil.rmtree(self.prefix_dir + "/drive_c/windows/Microsoft.NET")
 | |
| 
 | |
|             #prior to prefix version 4.11-2, all controllers were xbox controllers. wipe out the old registry entries.
 | |
|             if (int(old_proton_maj) < 4 or (int(old_proton_maj) == 4 and int(old_proton_min) == 11)) and \
 | |
|                     int(old_prefix_ver) < 2:
 | |
|                 log("Removing old xinput registry entries.")
 | |
|                 with open(self.prefix_dir + "system.reg", "r") as reg_in:
 | |
|                     with open(self.prefix_dir + "system.reg.new", "w") as reg_out:
 | |
|                         for line in reg_in:
 | |
|                             if line[0] == '[' and "CurrentControlSet" in line and "IG_" in line:
 | |
|                                 if "DeviceClasses" in line:
 | |
|                                     reg_out.write(line.replace("DeviceClasses", "DeviceClasses_old"))
 | |
|                                 elif "Enum" in line:
 | |
|                                     reg_out.write(line.replace("Enum", "Enum_old"))
 | |
|                             else:
 | |
|                                 reg_out.write(line)
 | |
|                 try:
 | |
|                     os.rename(self.prefix_dir + "system.reg", self.prefix_dir + "system.reg.old")
 | |
|                 except OSError:
 | |
|                     os.remove(self.prefix_dir + "system.reg")
 | |
|                     pass
 | |
| 
 | |
|                 try:
 | |
|                     os.rename(self.prefix_dir + "system.reg.new", self.prefix_dir + "system.reg")
 | |
|                 except OSError:
 | |
|                     log("Unable to write new registry file to " + self.prefix_dir + "system.reg")
 | |
|                     pass
 | |
| 
 | |
|         except ValueError:
 | |
|             log("Prefix has an invalid version?! You may want to back up user files and delete this prefix.")
 | |
|             #Just let the Wine upgrade happen and hope it works...
 | |
|             return
 | |
| 
 | |
|     def copy_pfx(self):
 | |
|         with open(self.tracked_files_file, "w") as tracked_files:
 | |
|             for src_dir, dirs, files in os.walk(g_proton.default_pfx_dir):
 | |
|                 rel_dir = src_dir.replace(g_proton.default_pfx_dir, "", 1).lstrip('/')
 | |
|                 if len(rel_dir) > 0:
 | |
|                     rel_dir = rel_dir + "/"
 | |
|                 dst_dir = src_dir.replace(g_proton.default_pfx_dir, self.prefix_dir, 1)
 | |
|                 if not os.path.exists(dst_dir):
 | |
|                     os.makedirs(dst_dir)
 | |
|                     tracked_files.write(rel_dir + "\n")
 | |
|                 for dir_ in dirs:
 | |
|                     src_file = os.path.join(src_dir, dir_)
 | |
|                     dst_file = os.path.join(dst_dir, dir_)
 | |
|                     if os.path.islink(src_file) and not os.path.exists(dst_file):
 | |
|                         real_copy(src_file, dst_file)
 | |
|                 for file_ in files:
 | |
|                     src_file = os.path.join(src_dir, file_)
 | |
|                     dst_file = os.path.join(dst_dir, file_)
 | |
|                     if not os.path.exists(dst_file):
 | |
|                         real_copy(src_file, dst_file)
 | |
|                         tracked_files.write(rel_dir + file_ + "\n")
 | |
| 
 | |
|     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):
 | |
|         with self.prefix_lock:
 | |
|             if os.path.exists(self.version_file):
 | |
|                 with open(self.version_file, "r") as f:
 | |
|                     self.upgrade_pfx(f.readline().strip())
 | |
|             else:
 | |
|                 self.upgrade_pfx(None)
 | |
| 
 | |
|             if not os.path.exists(self.prefix_dir):
 | |
|                 makedirs(self.prefix_dir + "/drive_c")
 | |
|                 set_dir_casefold_bit(self.prefix_dir + "/drive_c")
 | |
| 
 | |
|             if not os.path.exists(self.prefix_dir + "/user.reg"):
 | |
|                 self.copy_pfx()
 | |
| 
 | |
|             with open(self.version_file, "w") as f:
 | |
|                 f.write(CURRENT_PREFIX_VERSION + "\n")
 | |
| 
 | |
|             #create font files symlinks
 | |
|             self.create_fonts_symlinks()
 | |
| 
 | |
|             #copy steam files into place
 | |
|             if "STEAM_COMPAT_CLIENT_INSTALL_PATH" in os.environ:
 | |
|                 #modern steam client sets this
 | |
|                 steamdir = os.environ["STEAM_COMPAT_CLIENT_INSTALL_PATH"]
 | |
|             else:
 | |
|                 #linux-only fallback, really shouldn't get here
 | |
|                 steamdir = os.environ["HOME"] + ".steam/root/"
 | |
|             dst = self.prefix_dir + "/drive_c/Program Files (x86)/"
 | |
|             makedirs(dst + "Steam")
 | |
|             filestocopy = ["steamclient.dll",
 | |
|                     "steamclient64.dll",
 | |
|                     "Steam.dll"]
 | |
|             for f in filestocopy:
 | |
|                 if os.path.isfile(steamdir + "/legacycompat/" + f):
 | |
|                     dstfile = dst + "Steam/" + f
 | |
|                     if os.path.isfile(dstfile):
 | |
|                         os.remove(dstfile)
 | |
|                     try_copy(steamdir + "/legacycompat/" + f, dstfile)
 | |
| 
 | |
|             #copy openvr files into place
 | |
|             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)
 | |
| 
 | |
|             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 "wined3d" in g_session.compat_config:
 | |
|                 dxvkfiles = ["dxvk_config"]
 | |
|                 wined3dfiles = ["d3d11", "d3d10", "d3d10core", "d3d10_1", "d3d9"]
 | |
|             else:
 | |
|                 dxvkfiles = ["dxvk_config", "d3d11", "d3d10", "d3d10core", "d3d10_1", "d3d9"]
 | |
|                 wined3dfiles = []
 | |
| 
 | |
|             #if the user asked for dxvk's dxgi (dxgi=n), then copy it into place
 | |
|             if "WINEDLLOVERRIDES" in os.environ and "dxgi=n" in os.environ["WINEDLLOVERRIDES"]:
 | |
|                 dxvkfiles.append("dxgi")
 | |
|             else:
 | |
|                 wined3dfiles.append("dxgi")
 | |
| 
 | |
|             for f in wined3dfiles:
 | |
|                 try_copy(g_proton.default_pfx_dir + "drive_c/windows/system32/" + f + ".dll",
 | |
|                         self.prefix_dir + "drive_c/windows/system32/" + f + ".dll")
 | |
|                 try_copy(g_proton.default_pfx_dir + "drive_c/windows/syswow64/" + f + ".dll",
 | |
|                         self.prefix_dir + "drive_c/windows/syswow64/" + f + ".dll")
 | |
| 
 | |
|             for f in dxvkfiles:
 | |
|                 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"
 | |
| 
 | |
| 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.log_file = None
 | |
|         self.env = dict(os.environ)
 | |
|         self.dlloverrides = {
 | |
|                 "steam.exe": "b" #always use our special built-in steam.exe
 | |
|         }
 | |
| 
 | |
|         self.compat_config = set()
 | |
|         self.cmdlineappend = []
 | |
| 
 | |
|         if "STEAM_COMPAT_CONFIG" in os.environ:
 | |
|             config = os.environ["STEAM_COMPAT_CONFIG"]
 | |
| 
 | |
|             while config:
 | |
|                 (cur, sep, config) = config.partition(',')
 | |
|                 if cur.startswith("cmdlineappend:"):
 | |
|                     while comma_escaped(cur):
 | |
|                         (a, b, c) = config.partition(',')
 | |
|                         cur = cur[:-1] + ',' + a
 | |
|                         config = c
 | |
|                     self.cmdlineappend.append(cur[14:].replace('\\\\','\\'))
 | |
|                 else:
 | |
|                     self.compat_config.add(cur)
 | |
| 
 | |
|         #turn forcelgadd on by default unless it is disabled in compat config
 | |
|         if not "noforcelgadd" in self.compat_config:
 | |
|             self.compat_config.add("forcelgadd")
 | |
| 
 | |
|     def init_wine(self):
 | |
|         if "HOST_LC_ALL" in self.env and len(self.env["HOST_LC_ALL"]) > 0:
 | |
|             #steam sets LC_ALL=C to help some games, but Wine requires the real value
 | |
|             #in order to do path conversion between win32 and host. steam sets
 | |
|             #HOST_LC_ALL to allow us to use the real value.
 | |
|             self.env["LC_ALL"] = self.env["HOST_LC_ALL"]
 | |
|         else:
 | |
|             self.env.pop("LC_ALL", "")
 | |
| 
 | |
|         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("gstreamer-1.0/")
 | |
| 
 | |
|         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 os.path.exists(g_proton.user_settings_file):
 | |
|             try:
 | |
|                 import user_settings
 | |
|                 for key, value in user_settings.user_settings.items():
 | |
|                     self.env.setdefault(key, value)
 | |
|             except:
 | |
|                 log("************************************************")
 | |
|                 log("THERE IS AN ERROR IN YOUR user_settings.py FILE:")
 | |
|                 log("%s" % sys.exc_info()[1])
 | |
|                 log("************************************************")
 | |
| 
 | |
|         if "PROTON_LOG" in self.env and nonzero(self.env["PROTON_LOG"]):
 | |
|             self.env.setdefault("WINEDEBUG", "+timestamp,+pid,+tid,+seh,+debugstr,+loaddll,+mscoree")
 | |
|             self.env.setdefault("DXVK_LOG_LEVEL", "info")
 | |
|             self.env.setdefault("VKD3D_DEBUG", "warn")
 | |
|             self.env.setdefault("WINE_MONO_TRACE", "E:System.NotImplementedException")
 | |
| 
 | |
|         #for performance, logging is disabled by default; override with user_settings.py
 | |
|         self.env.setdefault("WINEDEBUG", "-all")
 | |
|         self.env.setdefault("DXVK_LOG_LEVEL", "none")
 | |
|         self.env.setdefault("VKD3D_DEBUG", "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("PROTON_USE_WINED3D", "wined3d"):
 | |
|             self.check_environment("PROTON_USE_WINED3D11", "wined3d")
 | |
|         self.check_environment("PROTON_NO_D3D11", "nod3d11")
 | |
|         self.check_environment("PROTON_NO_D3D10", "nod3d10")
 | |
|         self.check_environment("PROTON_NO_D9VK",  "nod3d9")
 | |
|         self.check_environment("PROTON_NO_ESYNC", "noesync")
 | |
|         self.check_environment("PROTON_NO_FSYNC", "nofsync")
 | |
|         self.check_environment("PROTON_FORCE_LARGE_ADDRESS_AWARE", "forcelgadd")
 | |
|         self.check_environment("PROTON_OLD_GL_STRING", "oldglstr")
 | |
|         self.check_environment("PROTON_USE_SECCOMP", "seccomp")
 | |
| 
 | |
|         if not "noesync" in self.compat_config:
 | |
|             self.env["WINEESYNC"] = "1" if "SteamGameId" in self.env else "0"
 | |
| 
 | |
|         if not "nofsync" in self.compat_config:
 | |
|             self.env["WINEFSYNC"] = "1" if "SteamGameId" in self.env else "0"
 | |
| 
 | |
|         if "seccomp" in self.compat_config:
 | |
|             self.env["WINESECCOMP"] = "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 "SteamGameId" in self.env:
 | |
|             if self.env["WINEDEBUG"] != "-all":
 | |
|                 lfile_path = os.environ["HOME"] + "/steam-" + os.environ["SteamGameId"] + ".log"
 | |
|                 if os.path.exists(lfile_path):
 | |
|                     os.remove(lfile_path)
 | |
|                 self.log_file = open(lfile_path, "w+")
 | |
|                 self.log_file.write("======================\n")
 | |
|                 with open(g_proton.version_file, "r") as f:
 | |
|                     self.log_file.write("Proton: " + f.readline().strip() + "\n")
 | |
|                 self.log_file.write("SteamGameId: " + self.env["SteamGameId"] + "\n")
 | |
|                 self.log_file.write("Command: " + str(sys.argv[2:] + self.cmdlineappend) + "\n")
 | |
|                 self.log_file.write("Options: " + str(self.compat_config) + "\n")
 | |
|                 self.log_file.write("======================\n")
 | |
|                 self.log_file.flush()
 | |
|         else:
 | |
|             self.env["WINEDEBUG"] = "-all"
 | |
| 
 | |
|         g_compatdata.setup_prefix()
 | |
| 
 | |
|         if "nod3d11" in self.compat_config:
 | |
|             self.dlloverrides["d3d11"] = ""
 | |
|             if "dxgi" in self.dlloverrides:
 | |
|                 del self.dlloverrides["dxgi"]
 | |
| 
 | |
|         if "nod3d10" in self.compat_config:
 | |
|             self.dlloverrides["d3d10_1"] = ""
 | |
|             self.dlloverrides["d3d10"] = ""
 | |
|             self.dlloverrides["dxgi"] = ""
 | |
| 
 | |
|         if "nod3d9" in self.compat_config:
 | |
|             self.dlloverrides["d3d9"] = ""
 | |
| 
 | |
|         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 os.environ:
 | |
|             self.env["WINEDLLOVERRIDES"] = os.environ["WINEDLLOVERRIDES"] + ";" + s
 | |
|         else:
 | |
|             self.env["WINEDLLOVERRIDES"] = s
 | |
| 
 | |
|     def dump_dbg_env(self, f):
 | |
|         f.write("PATH=\"" + self.env["PATH"] + "\" \\\n")
 | |
|         f.write("\tTERM=\"xterm\" \\\n") #XXX
 | |
|         f.write("\tWINEDEBUG=\"-all\" \\\n")
 | |
|         f.write("\tWINEDLLPATH=\"" + self.env["WINEDLLPATH"] + "\" \\\n")
 | |
|         f.write("\t" + ld_path_var + "=\"" + self.env[ld_path_var] + "\" \\\n")
 | |
|         f.write("\tWINEPREFIX=\"" + self.env["WINEPREFIX"] + "\" \\\n")
 | |
|         if "WINEESYNC" in self.env:
 | |
|             f.write("\tWINEESYNC=\"" + self.env["WINEESYNC"] + "\" \\\n")
 | |
|         if "WINEFSYNC" in self.env:
 | |
|             f.write("\tWINEFSYNC=\"" + self.env["WINEFSYNC"] + "\" \\\n")
 | |
|         if "SteamGameId" in self.env:
 | |
|             f.write("\tSteamGameId=\"" + self.env["SteamGameId"] + "\" \\\n")
 | |
|         if "SteamAppId" in self.env:
 | |
|             f.write("\tSteamAppId=\"" + self.env["SteamAppId"] + "\" \\\n")
 | |
|         if "WINEDLLOVERRIDES" in self.env:
 | |
|             f.write("\tWINEDLLOVERRIDES=\"" + self.env["WINEDLLOVERRIDES"] + "\" \\\n")
 | |
|         if "STEAM_COMPAT_CLIENT_INSTALL_PATH" in self.env:
 | |
|             f.write("\tSTEAM_COMPAT_CLIENT_INSTALL_PATH=\"" + self.env["STEAM_COMPAT_CLIENT_INSTALL_PATH"] + "\" \\\n")
 | |
|         if "WINE_LARGE_ADDRESS_AWARE" in self.env:
 | |
|             f.write("\tWINE_LARGE_ADDRESS_AWARE=\"" + self.env["WINE_LARGE_ADDRESS_AWARE"] + "\" \\\n")
 | |
| 
 | |
|     def dump_dbg_scripts(self):
 | |
|         exe_name = os.path.basename(sys.argv[2])
 | |
| 
 | |
|         tmpdir = self.env.get("PROTON_DEBUG_DIR", "/tmp") + "/proton_" + os.environ["USER"] + "/"
 | |
|         makedirs(tmpdir)
 | |
| 
 | |
|         with open(tmpdir + "winedbg", "w") as f:
 | |
|             f.write("#!/bin/bash\n")
 | |
|             f.write("#Run winedbg with args\n\n")
 | |
|             f.write("cd \"" + os.getcwd() + "\"\n")
 | |
|             self.dump_dbg_env(f)
 | |
|             f.write("\t\"" + g_proton.wine_bin + "\" winedbg \"$@\"\n")
 | |
|         os.chmod(tmpdir + "winedbg", 0o755)
 | |
| 
 | |
|         with open(tmpdir + "winedbg_run", "w") as f:
 | |
|             f.write("#!/bin/bash\n")
 | |
|             f.write("#Run winedbg and prepare to run game or given program\n\n")
 | |
|             f.write("cd \"" + os.getcwd() + "\"\n")
 | |
|             f.write("DEF_CMD=(")
 | |
|             first = True
 | |
|             for arg in sys.argv[2:]:
 | |
|                 if first:
 | |
|                     f.write("\"" + arg + "\"")
 | |
|                     first = False
 | |
|                 else:
 | |
|                     f.write(" \"" + arg + "\"")
 | |
|             f.write(")\n")
 | |
|             self.dump_dbg_env(f)
 | |
|             f.write("\t\"" + g_proton.wine_bin + "\" winedbg \"${@:-${DEF_CMD[@]}}\"\n")
 | |
|         os.chmod(tmpdir + "winedbg_run", 0o755)
 | |
| 
 | |
|         with open(tmpdir + "gdb_attach", "w") as f:
 | |
|             f.write("#!/bin/bash\n")
 | |
|             f.write("#Run winedbg in gdb mode and auto-attach to already-running program\n\n")
 | |
|             f.write("cd \"" + os.getcwd() + "\"\n")
 | |
|             f.write("EXE_NAME=${1:-\"" + exe_name + "\"}\n")
 | |
|             f.write("WPID_HEX=$(\"" + tmpdir + "winedbg\" --command 'info process' | grep -i \"$EXE_NAME\" | cut -f2 -d' ' | tr -d '0')\n")
 | |
|             f.write("if [ -z \"$WPID_HEX\" ]; then \n")
 | |
|             f.write("    echo \"Program does not appear to be running: \\\"$EXE_NAME\\\"\"\n")
 | |
|             f.write("    exit 1\n")
 | |
|             f.write("fi\n")
 | |
|             f.write("WPID_DEC=$(printf %d 0x$WPID_HEX)\n")
 | |
|             self.dump_dbg_env(f)
 | |
|             f.write("\t\"" + g_proton.wine_bin + "\" winedbg --gdb $WPID_DEC\n")
 | |
|         os.chmod(tmpdir + "gdb_attach", 0o755)
 | |
| 
 | |
|         with open(tmpdir + "gdb_run", "w") as f:
 | |
|             f.write("#!/bin/bash\n")
 | |
|             f.write("#Run winedbg in gdb mode and prepare to run game or given program\n\n")
 | |
|             f.write("cd \"" + os.getcwd() + "\"\n")
 | |
|             f.write("DEF_CMD=(")
 | |
|             first = True
 | |
|             for arg in sys.argv[2:]:
 | |
|                 if first:
 | |
|                     f.write("\"" + arg + "\"")
 | |
|                     first = False
 | |
|                 else:
 | |
|                     f.write(" \"" + arg + "\"")
 | |
|             f.write(")\n")
 | |
|             self.dump_dbg_env(f)
 | |
|             f.write("\t\"" + g_proton.wine_bin + "\" winedbg --gdb \"${@:-${DEF_CMD[@]}}\"\n")
 | |
|         os.chmod(tmpdir + "gdb_run", 0o755)
 | |
| 
 | |
|         with open(tmpdir + "run", "w") as f:
 | |
|             f.write("#!/bin/bash\n")
 | |
|             f.write("#Run game or given command in environment\n\n")
 | |
|             f.write("cd \"" + os.getcwd() + "\"\n")
 | |
|             f.write("DEF_CMD=(")
 | |
|             first = True
 | |
|             for arg in sys.argv[2:]:
 | |
|                 if first:
 | |
|                     f.write("\"" + arg + "\"")
 | |
|                     first = False
 | |
|                 else:
 | |
|                     f.write(" \"" + arg + "\"")
 | |
|             f.write(")\n")
 | |
|             self.dump_dbg_env(f)
 | |
|             f.write("\t\"" + g_proton.wine_bin + "\" steam.exe \"${@:-${DEF_CMD[@]}}\"\n")
 | |
|         os.chmod(tmpdir + "run", 0o755)
 | |
| 
 | |
|     def run_proc(self, args, local_env=None):
 | |
|         if local_env is None:
 | |
|             local_env = self.env
 | |
|         subprocess.call(args, env=local_env, stderr=self.log_file, stdout=self.log_file)
 | |
| 
 | |
|     def run(self):
 | |
|         if "PROTON_DUMP_DEBUG_COMMANDS" in self.env and nonzero(self.env["PROTON_DUMP_DEBUG_COMMANDS"]):
 | |
|             try:
 | |
|                 self.dump_dbg_scripts()
 | |
|             except OSError:
 | |
|                 log("Unable to write debug scripts! " + str(sys.exc_info()[1]))
 | |
|         self.run_proc([g_proton.wine_bin, "steam"] + sys.argv[2:] + self.cmdlineappend)
 | |
| 
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     if not "STEAM_COMPAT_DATA_PATH" in os.environ:
 | |
|         log("No compat data path?")
 | |
|         sys.exit(1)
 | |
| 
 | |
|     g_proton = Proton(os.path.dirname(sys.argv[0]))
 | |
| 
 | |
| #    g_proton.extract_tarball()
 | |
| 
 | |
|     g_compatdata = CompatData(os.environ["STEAM_COMPAT_DATA_PATH"])
 | |
| 
 | |
|     g_session = Session()
 | |
| 
 | |
|     g_session.init_wine()
 | |
| 
 | |
|     g_proton.make_default_prefix()
 | |
| 
 | |
|     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, stderr=g_session.log_file)
 | |
|         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, stderr=g_session.log_file)
 | |
|         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:
 |