Use system-wide Wine, DXVK & D9VK for Steam Play/Proton (Windows) games directly from Linux Steam client
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

648 lines
22 KiB

5 years ago
  1. #!/usr/bin/env python3
  2. #script to launch Wine with the correct environment
  3. ####################
  4. # System-wide Wine for Steam client (Proton/Steam Play) games with system-wide DXVK & D9VK support
  5. #
  6. # Put this file into
  7. # $HOME/.local/share/Steam/steamapps/common/Proton <version>/
  8. #
  9. # Select this specific Proton version in Steam client configuration menu
  10. # (Steam Client -> Settings -> Steam Play).
  11. # This means system-wide Wine, DXVK & D9VK are used for your Steam Play games.
  12. ####################
  13. # LICENSE
  14. # https://github.com/ValveSoftware/Proton/blob/proton_4.2/LICENSE
  15. # https://github.com/ValveSoftware/Proton/blob/proton_4.2/LICENSE.proton
  16. #
  17. ####################
  18. # ORIGINAL SOURCE CODE
  19. # https://github.com/ValveSoftware/Proton/blob/proton_4.2/proton
  20. #
  21. from __future__ import print_function
  22. import filecmp
  23. import json
  24. import os
  25. import shutil
  26. import errno
  27. import struct
  28. import subprocess
  29. import sys
  30. import tarfile
  31. from filelock import FileLock
  32. #To enable debug logging, copy "user_settings.sample.py" to "user_settings.py"
  33. #and edit it if needed.
  34. CURRENT_PREFIX_VERSION="4.2-5"
  35. PFX="Proton: "
  36. ld_path_var = "LD_LIBRARY_PATH"
  37. def nonzero(s):
  38. return len(s) > 0 and s != "0"
  39. def log(msg):
  40. sys.stderr.write(PFX + msg + os.linesep)
  41. sys.stderr.flush()
  42. def remove_tracked_files(prefix):
  43. if not os.path.exists(prefix + "/tracked_files"):
  44. log("Prefix has no tracked_files??")
  45. return
  46. with open(prefix + "/tracked_files", "r") as tracked_files:
  47. dirs = []
  48. for f in tracked_files:
  49. path = prefix + "/pfx/" + f.strip()
  50. if os.path.exists(path):
  51. if os.path.isfile(path) or os.path.islink(path):
  52. os.remove(path)
  53. else:
  54. dirs.append(path)
  55. for d in dirs:
  56. try:
  57. os.rmdir(d)
  58. except OSError:
  59. #not empty
  60. pass
  61. os.remove(prefix + "/tracked_files")
  62. os.remove(prefix + "/version")
  63. def file_is_wine_fake_dll(path):
  64. if not os.path.exists(path):
  65. return False
  66. try:
  67. sfile = open(path, "rb")
  68. sfile.seek(0x40)
  69. tag = sfile.read(20)
  70. return tag == b"Wine placeholder DLL"
  71. except IOError:
  72. return False
  73. def upgrade_pfx(old_ver):
  74. if old_ver == CURRENT_PREFIX_VERSION:
  75. return
  76. log("Upgrading prefix from " + str(old_ver) + " to " + CURRENT_PREFIX_VERSION + " (" + os.environ["STEAM_COMPAT_DATA_PATH"] + ")")
  77. if old_ver is None:
  78. return
  79. if not '-' in old_ver:
  80. #How can this happen??
  81. log("Prefix has an invalid version?! You may want to back up user files and delete this prefix.")
  82. #If it does, just let the Wine upgrade happen and hope it works...
  83. return
  84. try:
  85. old_proton_ver, old_prefix_ver = old_ver.split('-')
  86. old_proton_maj, old_proton_min = old_proton_ver.split('.')
  87. new_proton_ver, new_prefix_ver = CURRENT_PREFIX_VERSION.split('-')
  88. new_proton_maj, new_proton_min = new_proton_ver.split('.')
  89. if int(new_proton_maj) < int(old_proton_maj) or \
  90. (int(new_proton_maj) == int(old_proton_maj) and \
  91. int(new_proton_min) < int(old_proton_min)):
  92. log("Removing newer prefix")
  93. if old_proton_ver == "3.7" and not os.path.exists(os.environ["STEAM_COMPAT_DATA_PATH"] + "/tracked_files"):
  94. #proton 3.7 did not generate tracked_files, so copy it into place first
  95. try_copy(proton_basedir + "/proton_3.7_tracked_files", os.environ["STEAM_COMPAT_DATA_PATH"] + "/tracked_files")
  96. remove_tracked_files(os.environ["STEAM_COMPAT_DATA_PATH"])
  97. return
  98. if old_proton_ver == "3.7" and old_prefix_ver == "1":
  99. if not os.path.exists(prefix + "/drive_c/windows/syswow64/kernel32.dll"):
  100. #shipped a busted 64-bit-only installation on 20180822. detect and wipe clean
  101. log("Detected broken 64-bit-only installation, re-creating prefix.")
  102. shutil.rmtree(prefix)
  103. return
  104. #replace broken .NET installations with wine-mono support
  105. if os.path.exists(prefix + "/drive_c/windows/Microsoft.NET/NETFXRepair.exe") and \
  106. file_is_wine_fake_dll(prefix + "/drive_c/windows/system32/mscoree.dll"):
  107. log("Broken .NET installation detected, switching to wine-mono.")
  108. #deleting this directory allows wine-mono to work
  109. shutil.rmtree(prefix + "/drive_c/windows/Microsoft.NET")
  110. except ValueError:
  111. log("Prefix has an invalid version?! You may want to back up user files and delete this prefix.")
  112. #Just let the Wine upgrade happen and hope it works...
  113. return
  114. lfile = None
  115. def run_wine(args):
  116. subprocess.call(args, env=env, stderr=lfile, stdout=lfile)
  117. def makedirs(path):
  118. try:
  119. os.makedirs(path)
  120. except OSError:
  121. #already exists
  122. pass
  123. def try_copy(src, dst):
  124. try:
  125. shutil.copy(src, dst)
  126. except PermissionError as e:
  127. if e.errno == errno.EPERM:
  128. #be forgiving about permissions errors; if it's a real problem, things will explode later anyway
  129. log('Error while copying to \"' + dst + '\": ' + e.strerror)
  130. else:
  131. raise
  132. def real_copy(src, dst):
  133. if os.path.islink(src):
  134. os.symlink(os.readlink(src), dst)
  135. else:
  136. try_copy(src, dst)
  137. def mergedirs(src, dst, tracked_files):
  138. for src_dir, dirs, files in os.walk(src):
  139. rel_dir = src_dir.replace(src, "", 1).lstrip('/')
  140. if len(rel_dir) > 0:
  141. rel_dir = rel_dir + "/"
  142. dst_dir = src_dir.replace(src, dst, 1)
  143. if not os.path.exists(dst_dir):
  144. os.makedirs(dst_dir)
  145. tracked_files.write(rel_dir + "\n")
  146. for dir_ in dirs:
  147. src_file = os.path.join(src_dir, dir_)
  148. dst_file = os.path.join(dst_dir, dir_)
  149. if os.path.islink(src_file) and not os.path.exists(dst_file):
  150. real_copy(src_file, dst_file)
  151. for file_ in files:
  152. src_file = os.path.join(src_dir, file_)
  153. dst_file = os.path.join(dst_dir, file_)
  154. if not os.path.exists(dst_file):
  155. real_copy(src_file, dst_file)
  156. tracked_files.write(rel_dir + file_ + "\n")
  157. if not "STEAM_COMPAT_DATA_PATH" in os.environ:
  158. log("No compat data path?")
  159. sys.exit(1)
  160. basedir = "/"
  161. bindir = "/usr/bin"
  162. libdir = "/usr/lib32"
  163. lib64dir = "/usr/lib"
  164. fontsdir = "/usr/share/fonts/TTF"
  165. wine_path = bindir + "/wine"
  166. proton_basedir = os.path.dirname(sys.argv[0])
  167. default_prefixdir = proton_basedir + "/default_pfx"
  168. dxvkdir = "/usr/share/dxvk/x32"
  169. dxvk64dir = "/usr/share/dxvk/x64"
  170. env = dict(os.environ)
  171. dlloverrides = {"steam.exe": "b"} #always use our special built-in steam.exe
  172. if "HOST_LC_ALL" in env and len(env["HOST_LC_ALL"]) > 0:
  173. #steam sets LC_ALL=C to help some games, but Wine requires the real value
  174. #in order to do path conversion between win32 and host. steam sets
  175. #HOST_LC_ALL to allow us to use the real value.
  176. env["LC_ALL"] = env["HOST_LC_ALL"]
  177. else:
  178. env.pop("LC_ALL", "")
  179. #for performance, logging is disabled by default; override with user_settings.py
  180. env["DXVK_LOG_LEVEL"] = "none"
  181. env["WINEDEBUG"] = "-all"
  182. env.pop("WINEARCH", "")
  183. if ld_path_var in os.environ:
  184. env[ld_path_var] = lib64dir + ":" + libdir + ":" + os.environ[ld_path_var]
  185. else:
  186. env[ld_path_var] = lib64dir + ":" + libdir
  187. env["WINEDLLPATH"] = lib64dir + "/wine:" + libdir + "/wine"
  188. if "PATH" in os.environ:
  189. env["PATH"] = bindir + ":" + os.environ["PATH"]
  190. else:
  191. env["PATH"] = bindir
  192. if not os.path.isdir(default_prefixdir):
  193. #make default prefix
  194. env["WINEPREFIX"] = default_prefixdir
  195. run_wine([wine_path, "wineboot"])
  196. run_wine([bindir + "/wineserver", "-w"])
  197. prefix = os.environ["STEAM_COMPAT_DATA_PATH"] + "/pfx/"
  198. env["WINEPREFIX"] = prefix
  199. if "PROTON_LOG" in env and nonzero(env["PROTON_LOG"]):
  200. env["WINEDEBUG"] = "+timestamp,+pid,+tid,+seh,+debugstr,+loaddll,+mscoree"
  201. env["DXVK_LOG_LEVEL"] = "info"
  202. env["WINE_MONO_TRACE"] = "E:System.NotImplementedException"
  203. #default wine-mono override for FNA games
  204. env["WINE_MONO_OVERRIDES"] = "Microsoft.Xna.Framework.*,Gac=n"
  205. #load environment overrides
  206. if os.path.exists(proton_basedir + "/user_settings.py"):
  207. try:
  208. import user_settings
  209. env.update(user_settings.user_settings)
  210. except:
  211. log("************************************************")
  212. log("THERE IS AN ERROR IN YOUR user_settings.py FILE:")
  213. log("%s" % sys.exc_info()[1])
  214. log("************************************************")
  215. def check_environment(env_name, config_name):
  216. if not env_name in env:
  217. return False
  218. if nonzero(env[env_name]):
  219. config_opts.add(config_name)
  220. else:
  221. config_opts.discard(config_name)
  222. return True
  223. if "STEAM_COMPAT_CONFIG" in os.environ:
  224. config_opts = set(os.environ["STEAM_COMPAT_CONFIG"].split(","))
  225. else:
  226. config_opts = set()
  227. if "wined3d11" in config_opts:
  228. config_opts.add("wined3d")
  229. if not check_environment("PROTON_USE_WINED3D", "wined3d"):
  230. check_environment("PROTON_USE_WINED3D11", "wined3d")
  231. check_environment("PROTON_NO_D3D11", "nod3d11")
  232. check_environment("PROTON_NO_D3D10", "nod3d10")
  233. check_environment("PROTON_NO_D3D9", "nod3d9")
  234. check_environment("PROTON_NO_ESYNC", "noesync")
  235. check_environment("PROTON_FORCE_LARGE_ADDRESS_AWARE", "forcelgadd")
  236. check_environment("PROTON_OLD_GL_STRING", "oldglstr")
  237. if not "noesync" in config_opts:
  238. env["WINEESYNC"] = "1"
  239. if "oldglstr" in config_opts:
  240. #mesa override
  241. env["MESA_EXTENSION_MAX_YEAR"] = "2003"
  242. #nvidia override
  243. env["__GL_ExtensionStringVersion"] = "17700"
  244. if "forcelgadd" in config_opts:
  245. #forcelgadd should be used just for testing whether a game is helped by
  246. #setting LARGE_ADDRESS_AWARE. If it does, then add an AppDefault in the
  247. #registry, so that it doesn't impact every executable in the prefix.
  248. env["WINE_LARGE_ADDRESS_AWARE"] = "1"
  249. if "SteamGameId" in env:
  250. if env["WINEDEBUG"] != "-all":
  251. lfile_path = os.environ["HOME"] + "/steam-" + os.environ["SteamGameId"] + ".log"
  252. if os.path.exists(lfile_path):
  253. os.remove(lfile_path)
  254. lfile = open(lfile_path, "w+")
  255. lfile.write("======================\n")
  256. with open(basedir + "/version", "r") as f:
  257. lfile.write("Proton: " + f.readline().strip() + "\n")
  258. lfile.write("SteamGameId: " + env["SteamGameId"] + "\n")
  259. lfile.write("Command: " + str(sys.argv[2:]) + "\n")
  260. lfile.write("======================\n")
  261. lfile.flush()
  262. else:
  263. env["WINEDEBUG"] = "-all"
  264. def create_fonts_symlinks(prefix_path):
  265. fontsmap = [
  266. ( "LiberationSans-Regular.ttf", "arial.ttf" ),
  267. ( "LiberationSans-Bold.ttf", "arialbd.ttf" ),
  268. ( "LiberationSerif-Regular.ttf", "times.ttf" ),
  269. ( "LiberationMono-Regular.ttf", "cour.ttf" ),
  270. ]
  271. windowsfonts = prefix_path + "/drive_c/windows/Fonts"
  272. makedirs(windowsfonts)
  273. for p in fontsmap:
  274. lname = os.path.join(windowsfonts, p[1])
  275. fname = os.path.join(fontsdir, p[0])
  276. if os.path.lexists(lname):
  277. if os.path.islink(lname):
  278. os.remove(lname)
  279. os.symlink(fname, lname)
  280. else:
  281. os.symlink(fname, lname)
  282. prefix_lock = FileLock(os.environ["STEAM_COMPAT_DATA_PATH"] + "/pfx.lock", timeout=-1)
  283. with prefix_lock:
  284. version_file = os.environ["STEAM_COMPAT_DATA_PATH"] + "/version"
  285. if os.path.exists(version_file):
  286. with open(version_file, "r") as f:
  287. upgrade_pfx(f.readline().strip())
  288. else:
  289. upgrade_pfx(None)
  290. if not os.path.exists(prefix + "/user.reg"):
  291. #copy default prefix into place
  292. with open(os.environ["STEAM_COMPAT_DATA_PATH"] + "/tracked_files", "w") as tfiles:
  293. mergedirs(default_prefixdir, prefix, tfiles)
  294. with open(version_file, "w") as f:
  295. f.write(CURRENT_PREFIX_VERSION + "\n")
  296. #create font files symlinks
  297. create_fonts_symlinks(prefix)
  298. #copy steam files into place
  299. if "STEAM_COMPAT_CLIENT_INSTALL_PATH" in os.environ:
  300. #modern steam client sets this
  301. steamdir = os.environ["STEAM_COMPAT_CLIENT_INSTALL_PATH"]
  302. else:
  303. #linux-only fallback, really shouldn't get here
  304. steamdir = os.environ["HOME"] + ".steam/root/"
  305. dst = prefix + "/drive_c/Program Files (x86)/"
  306. makedirs(dst + "Steam")
  307. filestocopy = [
  308. "steamclient.dll",
  309. "steamclient64.dll",
  310. "Steam.dll"
  311. ]
  312. for f in filestocopy:
  313. if os.path.isfile(steamdir + "/legacycompat/" + f):
  314. dstfile = dst + "Steam/" + f
  315. if os.path.isfile(dstfile):
  316. os.remove(dstfile)
  317. try_copy(steamdir + "/legacycompat/" + f, dstfile)
  318. #copy openvr files into place
  319. dst = prefix + "/drive_c/vrclient/bin/"
  320. makedirs(dst)
  321. try_copy(libdir + "/wine/fakedlls/vrclient.dll", dst)
  322. try_copy(lib64dir + "/wine/fakedlls/vrclient_x64.dll", dst)
  323. try_copy(dxvkdir + "/openvr_api_dxvk.dll", prefix + "/drive_c/windows/syswow64/")
  324. try_copy(dxvk64dir + "/openvr_api_dxvk.dll", prefix + "/drive_c/windows/system32/")
  325. #parse linux openvr config and present it in win32 format to the app.
  326. #logic from openvr's CVRPathRegistry_Public::GetPaths
  327. #check environment for overrides
  328. vr_runtime = None
  329. if "VR_OVERRIDE" in env:
  330. vr_runtime = env["VR_OVERRIDE"]
  331. env.pop("VR_OVERRIDE")
  332. vr_config = None
  333. if "VR_CONFIG_PATH" in env:
  334. vr_config = env["VR_CONFIG_PATH"]
  335. env.pop("VR_CONFIG_PATH")
  336. vr_log = None
  337. if "VR_LOG_PATH" in env:
  338. vr_log = env["VR_LOG_PATH"]
  339. env.pop("VR_LOG_PATH")
  340. #load from json if needed
  341. if vr_runtime is None or \
  342. vr_config is None or \
  343. vr_log is None:
  344. try:
  345. path = os.environ.get("XDG_CONFIG_HOME", os.environ["HOME"] + "/.config")
  346. path = path + "/openvr/openvrpaths.vrpath"
  347. with open(path, "r") as jfile:
  348. j = json.load(jfile)
  349. if vr_runtime is None:
  350. vr_runtime = j["runtime"][0]
  351. if vr_config is None:
  352. vr_config = j["config"][0]
  353. if vr_log is None:
  354. vr_log = j["log"][0]
  355. except (TypeError, ValueError, OSError):
  356. log("Missing or invalid openvrpaths.vrpath file! " + str(sys.exc_info()[1]))
  357. makedirs(prefix + "/drive_c/users/" + os.environ["USER"] + "/Local Settings/Application Data/openvr")
  358. #remove existing file
  359. vrpaths_name = prefix + "/drive_c/users/" + os.environ["USER"] + "/Local Settings/Application Data/openvr/openvrpaths.vrpath"
  360. if os.path.exists(vrpaths_name):
  361. os.remove(vrpaths_name)
  362. #dump new file
  363. if not vr_runtime is None:
  364. try:
  365. env["PROTON_VR_RUNTIME"] = vr_runtime
  366. j = { "runtime": [ "C:\\vrclient\\", "C:\\vrclient" ] }
  367. if not vr_config is None:
  368. win_vr_config = subprocess.check_output([wine_path, "winepath", "-w", vr_config], env=env, stderr=lfile).decode("utf-8")
  369. j["config"] = [ win_vr_config.strip() ]
  370. if not vr_log is None:
  371. win_vr_log = subprocess.check_output([wine_path, "winepath", "-w", vr_log], env=env, stderr=lfile).decode("utf-8")
  372. j["log"] = [ win_vr_log.strip() ]
  373. j["version"] = 1
  374. j["jsonid"] = "vrpathreg"
  375. with open(vrpaths_name, "w") as vfile:
  376. json.dump(j, vfile, indent=2)
  377. except (ValueError, OSError):
  378. log("Unable to write VR config! " + str(sys.exc_info()[1]))
  379. dxvkfiles = ("d3d11", "d3d10", "d3d10core", "d3d10_1", "dxgi", "d3d9")
  380. def make_dxvk_links(dll_dir, link_dir):
  381. for f in dxvkfiles:
  382. dst = link_dir + "/" + f + ".dll"
  383. src = dll_dir + "/" + f + ".dll"
  384. if os.path.lexists(dst):
  385. os.remove(dst)
  386. os.symlink(src, dst)
  387. if "wined3d" in config_opts:
  388. #use gl-based wined3d for d3d11, d3d10 and d3d9
  389. make_dxvk_links(lib64dir + "/wine/fakedlls/",
  390. prefix + "drive_c/windows/system32")
  391. make_dxvk_links(libdir + "/wine/fakedlls/",
  392. prefix + "drive_c/windows/syswow64")
  393. else:
  394. #use vulkan-based dxvk for d3d11, d3d10 and d3d9
  395. make_dxvk_links(dxvk64dir,
  396. prefix + "drive_c/windows/system32")
  397. make_dxvk_links(dxvkdir,
  398. prefix + "drive_c/windows/syswow64")
  399. for f in dxvkfiles:
  400. dlloverrides[f] = "n"
  401. if "nod3d11" in config_opts:
  402. dlloverrides["d3d11"] = ""
  403. if "dxgi" in dlloverrides:
  404. del dlloverrides["dxgi"]
  405. if "nod3d10" in config_opts:
  406. dlloverrides["d3d10_1"] = ""
  407. dlloverrides["d3d10"] = ""
  408. dlloverrides["dxgi"] = ""
  409. if "nod3d9" in config_opts:
  410. dlloverrides["d3d9"] = ""
  411. s = ""
  412. for dll in dlloverrides:
  413. setting = dlloverrides[dll]
  414. if len(s) > 0:
  415. s = s + ";" + dll + "=" + setting
  416. else:
  417. s = dll + "=" + setting
  418. if "WINEDLLOVERRIDES" in os.environ:
  419. env["WINEDLLOVERRIDES"] = os.environ["WINEDLLOVERRIDES"] + ";" + s
  420. else:
  421. env["WINEDLLOVERRIDES"] = s
  422. def dump_dbg_env(f):
  423. f.write("PATH=\"" + env["PATH"] + "\" \\\n")
  424. f.write("\tTERM=\"xterm\" \\\n") #XXX
  425. f.write("\tWINEDEBUG=\"-all\" \\\n")
  426. f.write("\tWINEDLLPATH=\"" + env["WINEDLLPATH"] + "\" \\\n")
  427. f.write("\t" + ld_path_var + "=\"" + env[ld_path_var] + "\" \\\n")
  428. f.write("\tWINEPREFIX=\"" + env["WINEPREFIX"] + "\" \\\n")
  429. if "WINEESYNC" in env:
  430. f.write("\tWINEESYNC=\"" + env["WINEESYNC"] + "\" \\\n")
  431. if "SteamGameId" in env:
  432. f.write("\tSteamGameId=\"" + env["SteamGameId"] + "\" \\\n")
  433. if "SteamAppId" in env:
  434. f.write("\tSteamAppId=\"" + env["SteamAppId"] + "\" \\\n")
  435. if "PROTON_VR_RUNTIME" in env:
  436. f.write("\tPROTON_VR_RUNTIME=\"" + env["PROTON_VR_RUNTIME"] + "\" \\\n")
  437. if "WINEDLLOVERRIDES" in env:
  438. f.write("\tWINEDLLOVERRIDES=\"" + env["WINEDLLOVERRIDES"] + "\" \\\n")
  439. if "STEAM_COMPAT_CLIENT_INSTALL_PATH" in env:
  440. f.write("\tSTEAM_COMPAT_CLIENT_INSTALL_PATH=\"" + env["STEAM_COMPAT_CLIENT_INSTALL_PATH"] + "\" \\\n")
  441. if "WINE_LARGE_ADDRESS_AWARE" in env:
  442. f.write("\tWINE_LARGE_ADDRESS_AWARE=\"" + env["WINE_LARGE_ADDRESS_AWARE"] + "\" \\\n")
  443. def dump_dbg_scripts():
  444. exe_name = os.path.basename(sys.argv[2])
  445. tmpdir = env.get("PROTON_DEBUG_DIR", "/tmp") + "/proton_" + os.environ["USER"] + "/"
  446. makedirs(tmpdir)
  447. with open(tmpdir + "winedbg", "w") as f:
  448. f.write("#!/bin/bash\n")
  449. f.write("#Run winedbg with args\n\n")
  450. f.write("cd \"" + os.getcwd() + "\"\n")
  451. dump_dbg_env(f)
  452. f.write("\t\"" + wine_path + "\" winedbg \"$@\"\n")
  453. os.chmod(tmpdir + "winedbg", 0o755)
  454. with open(tmpdir + "winedbg_run", "w") as f:
  455. f.write("#!/bin/bash\n")
  456. f.write("#Run winedbg and prepare to run game or given program\n\n")
  457. f.write("cd \"" + os.getcwd() + "\"\n")
  458. f.write("DEF_CMD=(")
  459. first = True
  460. for arg in sys.argv[2:]:
  461. if first:
  462. f.write("\"" + arg + "\"")
  463. first = False
  464. else:
  465. f.write(" \"" + arg + "\"")
  466. f.write(")\n")
  467. dump_dbg_env(f)
  468. f.write("\t\"" + wine_path + "\" winedbg \"${@:-${DEF_CMD[@]}}\"\n")
  469. os.chmod(tmpdir + "winedbg_run", 0o755)
  470. with open(tmpdir + "gdb_attach", "w") as f:
  471. f.write("#!/bin/bash\n")
  472. f.write("#Run winedbg in gdb mode and auto-attach to already-running program\n\n")
  473. f.write("cd \"" + os.getcwd() + "\"\n")
  474. f.write("EXE_NAME=${1:-\"" + exe_name + "\"}\n")
  475. f.write("WPID_HEX=$(\"" + tmpdir + "winedbg\" --command 'info process' | grep -i \"$EXE_NAME\" | cut -f2 -d' ' | tr -d '0')\n")
  476. f.write("if [ -z \"$WPID_HEX\" ]; then \n")
  477. f.write(" echo \"Program does not appear to be running: \\\"$EXE_NAME\\\"\"\n")
  478. f.write(" exit 1\n")
  479. f.write("fi\n")
  480. f.write("WPID_DEC=$(printf %d 0x$WPID_HEX)\n")
  481. dump_dbg_env(f)
  482. f.write("\t\"" + wine_path + "\" winedbg --gdb $WPID_DEC\n")
  483. os.chmod(tmpdir + "gdb_attach", 0o755)
  484. with open(tmpdir + "gdb_run", "w") as f:
  485. f.write("#!/bin/bash\n")
  486. f.write("#Run winedbg in gdb mode and prepare to run game or given program\n\n")
  487. f.write("cd \"" + os.getcwd() + "\"\n")
  488. f.write("DEF_CMD=(")
  489. first = True
  490. for arg in sys.argv[2:]:
  491. if first:
  492. f.write("\"" + arg + "\"")
  493. first = False
  494. else:
  495. f.write(" \"" + arg + "\"")
  496. f.write(")\n")
  497. dump_dbg_env(f)
  498. f.write("\t\"" + wine_path + "\" winedbg --gdb \"${@:-${DEF_CMD[@]}}\"\n")
  499. os.chmod(tmpdir + "gdb_run", 0o755)
  500. with open(tmpdir + "run", "w") as f:
  501. f.write("#!/bin/bash\n")
  502. f.write("#Run game or given command in environment\n\n")
  503. f.write("cd \"" + os.getcwd() + "\"\n")
  504. f.write("DEF_CMD=(")
  505. first = True
  506. for arg in sys.argv[2:]:
  507. if first:
  508. f.write("\"" + arg + "\"")
  509. first = False
  510. else:
  511. f.write(" \"" + arg + "\"")
  512. f.write(")\n")
  513. dump_dbg_env(f)
  514. f.write("\t\"" + wine_path + "\" steam.exe \"${@:-${DEF_CMD[@]}}\"\n")
  515. os.chmod(tmpdir + "run", 0o755)
  516. def run():
  517. if "PROTON_DUMP_DEBUG_COMMANDS" in env and nonzero(env["PROTON_DUMP_DEBUG_COMMANDS"]):
  518. try:
  519. dump_dbg_scripts()
  520. except OSError:
  521. log("Unable to write debug scripts! " + str(sys.exc_info()[1]))
  522. run_wine([wine_path, "steam"] + sys.argv[2:])
  523. if sys.version_info[0] == 2:
  524. binary_stdout = sys.stdout
  525. elif sys.version_info[0] == 3:
  526. binary_stdout = sys.stdout.buffer
  527. else:
  528. raise Exception("Unsupported python version")
  529. #determine mode
  530. if sys.argv[1] == "run":
  531. #start target app
  532. run()
  533. elif sys.argv[1] == "waitforexitandrun":
  534. #wait for wineserver to shut down
  535. run_wine([bindir + "/wineserver", "-w"])
  536. #then run
  537. run()
  538. elif sys.argv[1] == "getcompatpath":
  539. #linux -> windows path
  540. path = subprocess.check_output([wine_path, "winepath", "-w", sys.argv[2]], env=env, stderr=lfile)
  541. binary_stdout.write(path)
  542. elif sys.argv[1] == "getnativepath":
  543. #windows -> linux path
  544. path = subprocess.check_output([wine_path, "winepath", sys.argv[2]], env=env, stderr=lfile)
  545. binary_stdout.write(path)
  546. else:
  547. log("Need a verb.")
  548. sys.exit(1)
  549. sys.exit(0)
  550. # vim: set syntax=python: