Skip to content

Commit

Permalink
Add support for forwarding commands into the container environment
Browse files Browse the repository at this point in the history
Recent versions of the Steam Runtime include an IPC server/client pair
which can be used to run commands inside the container environment
(or any other special execution environment), analogous to sshd/ssh or
flatpak-portal/flatpak-spawn. The server runs inside the Steam Runtime
container and accepts commands over D-Bus; the client runs on the host
system, asks the server to run a command, and forwards its stdin, stdout
and stderr back to the host.

https://gitlab.steamos.cloud/steamrt/steamlinuxruntime/-/merge_requests/72
adds support for injecting commands into the SteamLinuxRuntime_soldier
compatibility tool (and any later version, such as sniper). However,
Steam compatibility tools are stackable: in particular, Proton runs in a
soldier container (or presumably sniper in future). If we are debugging
a Proton game, then ideally we will want to inject commands into Proton's
execution environment rather than soldier's, so that they run with the
correct environment variables etc. to communicate with a running Proton
session. In particular, it's important that the `WINEPREFIX` is correct.

The intended "API" for this is that either the Steam client or a Steam
user can set `$STEAM_COMPAT_LAUNCHER_SERVICE` to the Steam app-ID of
the compatibility tool into which we want to inject commands, for
example STEAM_COMPAT_LAUNCHER_SERVICE=1391110 to provide this debug
interface for the soldier container, or
STEAM_COMPAT_LAUNCHER_SERVICE=1887720 to provide this debug interface
for Proton 7.0. For developer convenience, I've also made
STEAM_COMPAT_LAUNCHER_SERVICE=container-runtime and
STEAM_COMPAT_LAUNCHER_SERVICE=proton do the obvious thing.

Wire this up to the Proton launch script, so that it can be used to run
commands inside the Proton environment, with all of Proton's special
environment variables set.

Signed-off-by: Simon McVittie <smcv@collabora.com>
  • Loading branch information
smcv committed Jun 8, 2022
1 parent 0178b2b commit 0f13df9
Showing 1 changed file with 70 additions and 9 deletions.
79 changes: 70 additions & 9 deletions proton
Expand Up @@ -28,10 +28,18 @@ from ctypes import c_void_p
from filelock import FileLock
from random import randrange

try:
# available since 3.3
from shlex import quote as shell_quote
except ImportError:
# exists in older versions, undocumented
from pipes import quote as shell_quote

#To enable debug logging, copy "user_settings.sample.py" to "user_settings.py"
#and edit it if needed.

CURRENT_PREFIX_VERSION="7.0-100"
PROTON_APP_ID="1887720"

PFX="Proton: "
ld_path_var = "LD_LIBRARY_PATH"
Expand Down Expand Up @@ -1233,10 +1241,19 @@ class Session:
s = dll + "=" + setting
append_to_env_str(self.env, "WINEDLLOVERRIDES", s, ";")

def dump_dbg_env(self, f):
f.write("PATH=\"" + self.env["PATH"] + "\" \\\n")
def dump_dbg_env(self, f, *, using_launcher_service=False):
if not using_launcher_service:
f.write("PATH=\"" + self.env["PATH"] + "\" \\\n")

f.write("\tTERM=\"xterm\" \\\n") #XXX
f.write("\tWINEDEBUG=\"-all\" \\\n")

if using_launcher_service:
# All remaining variables are from self.env, and children
# of steam-runtime-launcher-service are going to inherit those
# anyway
return

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")
Expand Down Expand Up @@ -1267,16 +1284,40 @@ class Session:
if "MEDIACONV_VIDEO_TRANSCODED_FILE" in self.env:
f.write("\tMEDIACONV_VIDEO_TRANSCODED_FILE=\"" + self.env["MEDIACONV_VIDEO_TRANSCODED_FILE"] + "\" \\\n")

def dump_dbg_scripts(self):
def get_steam_runtime_launch_client_command(self, launcher_address):
launch_client = os.path.expanduser("~/.steam/root/ubuntu12_32/steam-runtime/amd64/bin/steam-runtime-launch-client")

# This relies on an implementation detail of
# SteamLinuxRuntime_soldier, but is necessary until the scout
# runtime shipped with the GA Steam client has been updated to
# include a copy of s-r-launch-client.
if "PRESSURE_VESSEL_RUNTIME_BASE" in self.env and not os.path.exists(launch_client):
launch_client = self.env["PRESSURE_VESSEL_RUNTIME_BASE"] + "/pressure-vessel/bin/steam-runtime-launch-client"

return "".join([
launch_client, " \\\n",
"\t", shell_quote(launcher_address), " \\\n",
# --directory='' means keep using the game's working directory
"\t--directory='' \\\n",
"\t-- \\\n",
])

def dump_dbg_scripts(self, *, launcher_address=""):
exe_name = os.path.basename(sys.argv[2])

tmpdir = self.env.get("PROTON_DEBUG_DIR", "/tmp") + "/proton_" + os.environ["USER"] + "/"
makedirs(tmpdir)

if launcher_address:
launch_client = self.get_steam_runtime_launch_client_command(launcher_address) + "\tenv \\\n"
else:
launch_client = ""

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")
# TODO: Should this run via launch_client?
self.dump_dbg_env(f)
f.write("\t\"" + g_proton.wine_bin + "\" winedbg \"$@\"\n")
os.chmod(tmpdir + "winedbg", 0o755)
Expand All @@ -1294,6 +1335,7 @@ class Session:
else:
f.write(" \"" + arg + "\"")
f.write(")\n")
# TODO: Should this run via launch_client?
self.dump_dbg_env(f)
f.write("\t\"" + g_proton.wine_bin + "\" winedbg \"${@:-${DEF_CMD[@]}}\"\n")
os.chmod(tmpdir + "winedbg_run", 0o755)
Expand Down Expand Up @@ -1343,7 +1385,8 @@ class Session:
else:
f.write(" \"" + arg + "\"")
f.write(")\n")
self.dump_dbg_env(f)
f.write(launch_client)
self.dump_dbg_env(f, using_launcher_service=bool(launcher_address))
f.write("\t\"" + g_proton.wine64_bin + "\" c:\\\\windows\\\\system32\\\\steam.exe \"${@:-${DEF_CMD[@]}}\"\n")
os.chmod(tmpdir + "run", 0o755)

Expand All @@ -1352,10 +1395,24 @@ class Session:
local_env = self.env
return subprocess.call(args, env=local_env, stderr=self.log_file, stdout=self.log_file)

def run(self):
def run(self, *, using_launcher_service=False):
adverb = []

if using_launcher_service:
appid = os.environ.get("STEAM_COMPAT_APP_ID", os.environ.get("SteamAppId", "0"))
launcher_address = "--bus-name=com.steampowered.App" + appid
adverb = ['steam-runtime-launcher-service', '--replace', launcher_address, '--']

sys.stderr.write("Starting program with command-launcher service.\n")
sys.stderr.write("\n")
sys.stderr.write("To inject commands into the container, use a command like:\n")
sys.stderr.write("\n")
sys.stderr.write(self.get_steam_runtime_launch_client_command(launcher_address))
sys.stderr.write("\txterm\n")

if "PROTON_DUMP_DEBUG_COMMANDS" in self.env and nonzero(self.env["PROTON_DUMP_DEBUG_COMMANDS"]):
try:
self.dump_dbg_scripts()
self.dump_dbg_scripts(launcher_address=launcher_address)
except OSError:
log("Unable to write debug scripts! " + str(sys.exc_info()[1]))

Expand All @@ -1370,9 +1427,11 @@ class Session:

# CoD: Black Ops 3 workaround
if os.environ.get("SteamGameId", 0) == "311210":
rc = self.run_proc([g_proton.wine_bin, "c:\\Program Files (x86)\\Steam\\steam.exe"] + sys.argv[2:] + self.cmdlineappend)
argv = [g_proton.wine_bin, "c:\\Program Files (x86)\\Steam\\steam.exe"]
else:
rc = self.run_proc([g_proton.wine64_bin, "c:\\windows\\system32\\steam.exe"] + sys.argv[2:] + self.cmdlineappend)
argv = [g_proton.wine64_bin, "c:\\windows\\system32\\steam.exe"]

rc = self.run_proc(adverb + argv + sys.argv[2:] + self.cmdlineappend)

if remote_debug_proc:
remote_debug_proc.kill()
Expand Down Expand Up @@ -1417,7 +1476,9 @@ if __name__ == "__main__":
#wait for wineserver to shut down
g_session.run_proc([g_proton.wineserver_bin, "-w"])
#then run
rc = g_session.run()
launcher_service = os.environ.get("STEAM_COMPAT_LAUNCHER_SERVICE", "")
using_launcher_service = launcher_service and launcher_service in ("proton", PROTON_APP_ID)
rc = g_session.run(using_launcher_service=bool(launcher_service))
elif sys.argv[1] == "runinprefix":
rc = g_session.run_proc([g_proton.wine_bin] + sys.argv[2:])
elif sys.argv[1] == "destroyprefix":
Expand Down

0 comments on commit 0f13df9

Please sign in to comment.