Proton/proton

743 lines
30 KiB
Python
Executable File

#!/usr/bin/env python3
#script to launch Wine with the correct environment
from __future__ import print_function
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="4.11-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 = base_dir + "/"
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 = tarfile.open(self.path("proton_dist.tar.gz"), mode="r:gz")
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 = compatdata + "/"
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/")
#parse linux openvr config and present it in win32 format to the app.
#logic from openvr's CVRPathRegistry_Public::GetPaths
#check environment for overrides
vr_runtime = None
if "VR_OVERRIDE" in g_session.env:
vr_runtime = g_session.env["VR_OVERRIDE"]
g_session.env.pop("VR_OVERRIDE")
vr_config = None
if "VR_CONFIG_PATH" in g_session.env:
vr_config = g_session.env["VR_CONFIG_PATH"]
g_session.env.pop("VR_CONFIG_PATH")
vr_log = None
if "VR_LOG_PATH" in g_session.env:
vr_log = g_session.env["VR_LOG_PATH"]
g_session.env.pop("VR_LOG_PATH")
#load from json if needed
if vr_runtime is None or \
vr_config is None or \
vr_log is None:
try:
path = os.environ.get("XDG_CONFIG_HOME", os.environ["HOME"] + "/.config")
path = path + "/openvr/openvrpaths.vrpath"
with open(path, "r") as jfile:
j = json.load(jfile)
if vr_runtime is None:
vr_runtime = j["runtime"][0]
if vr_config is None:
vr_config = j["config"][0]
if vr_log is None:
vr_log = j["log"][0]
except (TypeError, ValueError, OSError):
log("Missing or invalid openvrpaths.vrpath file! " + str(sys.exc_info()[1]))
makedirs(self.prefix_dir + "/drive_c/users/steamuser/Local Settings/Application Data/openvr")
#remove existing file
vrpaths_name = self.prefix_dir + "/drive_c/users/steamuser/Local Settings/Application Data/openvr/openvrpaths.vrpath"
if os.path.exists(vrpaths_name):
os.remove(vrpaths_name)
#dump new file
if not vr_runtime is None:
try:
g_session.env["PROTON_VR_RUNTIME"] = vr_runtime
j = { "runtime": [ "C:\\vrclient\\", "C:\\vrclient" ] }
if not vr_config is None:
win_vr_config = subprocess.check_output([g_proton.wine_bin, "winepath", "-w", vr_config], env=g_session.env, stderr=g_session.log_file).decode("utf-8")
j["config"] = [ win_vr_config.strip() ]
if not vr_log is None:
win_vr_log = subprocess.check_output([g_proton.wine_bin, "winepath", "-w", vr_log], env=g_session.env, stderr=g_session.log_file).decode("utf-8")
j["log"] = [ win_vr_log.strip() ]
j["version"] = 1
j["jsonid"] = "vrpathreg"
with open(vrpaths_name, "w") as vfile:
json.dump(j, vfile, indent=2)
except (ValueError, OSError):
log("Unable to write VR config! " + str(sys.exc_info()[1]))
if "wined3d" in g_session.compat_config:
dxvkfiles = []
wined3dfiles = ["d3d11", "d3d10", "d3d10core", "d3d10_1", "dxgi", "d3d9"]
else:
dxvkfiles = ["d3d11", "d3d10", "d3d10core", "d3d10_1", "dxgi"]
wined3dfiles = []
if "d9vk" in g_session.compat_config:
dxvkfiles.append("d3d9")
else:
wined3dfiles.append("d3d9")
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"
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
"mfplay": "n" #disable built-in mfplay
}
if "STEAM_COMPAT_CONFIG" in os.environ:
self.compat_config = set(os.environ["STEAM_COMPAT_CONFIG"].split(","))
else:
self.compat_config = set()
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 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"
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("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")
#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_USE_D9VK", "d9vk")
self.check_environment("PROTON_NO_D3D11", "nod3d11")
self.check_environment("PROTON_NO_D3D10", "nod3d10")
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")
if not "noesync" in self.compat_config:
self.env["WINEESYNC"] = "1"
if not "nofsync" in self.compat_config:
self.env["WINEFSYNC"] = "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:
#forcelgadd should be used just for testing whether a game is helped by
#setting LARGE_ADDRESS_AWARE. If it does, then add an AppDefault in the
#registry, so that it doesn't impact every executable in the prefix.
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:]) + "\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"] = ""
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 "SteamGameId" in self.env:
f.write("\tSteamGameId=\"" + self.env["SteamGameId"] + "\" \\\n")
if "SteamAppId" in self.env:
f.write("\tSteamAppId=\"" + self.env["SteamAppId"] + "\" \\\n")
if "PROTON_VR_RUNTIME" in self.env:
f.write("\tPROTON_VR_RUNTIME=\"" + self.env["PROTON_VR_RUNTIME"] + "\" \\\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:])
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()
if sys.version_info[0] == 2:
binary_stdout = sys.stdout
elif sys.version_info[0] == 3:
binary_stdout = sys.stdout.buffer
else:
raise Exception("Unsupported python version")
#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)
binary_stdout.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)
binary_stdout.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: