Skip to content

Commit d90b5ba

Browse files
kakurasanLucki
andauthored
Allow to use Steam Linux Runtime (Soldier) container when using Proton 5.13+ (truckersmp-cli#159)
Co-authored-by: Lucki <[email protected]>
1 parent e87645b commit d90b5ba

File tree

8 files changed

+343
-124
lines changed

8 files changed

+343
-124
lines changed

Diff for: README.md

+2
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,11 @@ Short option|Long option|Description
109109
(Not available)|`--self-update`|Update files to the latest release and quit
110110
(Not available)|`--singleplayer`|**DEPRECATED** Start singleplayer game, useful for save editing, using/testing DXVK in singleplayer, etc.
111111
(Not available)|`--skip-update-proton`|Skip updating already-installed Proton when updating game with Proton enabled
112+
(Not available)|`--steamruntimedir`|Choose a different Steam Runtime directory for Proton 5.13 or newer
112113
(Not available)|`--use-wined3d`|Use OpenGL-based D3D11 instead of DXVK when using Proton
113114
(Not available)|`--wine-desktop SIZE`|Use Wine desktop, work around missing TruckerMP overlay after tabbing out using DXVK, mouse clicking won't work in other GUI apps while the game is running, SIZE must be 'WIDTHxHEIGHT' format (e.g. 1920x1080)
114115
(Not available)|`--wine-steam-dir`|Choose a directory for Windows version of Steam [Default: `C:\Program Files (x86)\Steam` in the prefix]
116+
(Not available)|`--without-steam-runtime`|Don't use Steam Runtime even when using Proton 5.13 or newer
115117
(Not available)|`--without-wine-discord-ipc-bridge`|Don't use wine-discord-ipc-bridge for Discord Rich Presence
116118
(Not available)|`--version`|Print version information and quit
117119

Diff for: truckersmp_cli/args.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ def check_args_errors():
167167
logging.info("Prefix: %s", Args.prefixdir)
168168
if Args.proton:
169169
logging.info("Proton directory: %s", Args.protondir)
170+
logging.info("Steam Runtime directory: %s", Args.steamruntimedir)
170171

171172

172173
def create_arg_parser():
@@ -269,6 +270,11 @@ def create_arg_parser():
269270
help="""**DEPRECATED** start the game
270271
[Default if neither start or update are specified]""",
271272
action="store_true"))
273+
store_actions.append(parser.add_argument(
274+
"--steamruntimedir", metavar="DIR",
275+
default=Dir.default_steamruntimedir,
276+
help="""choose a different Steam Runtime directory for Proton 5.13 or newer
277+
[Default: $XDG_DATA_HOME/truckersmp-cli/SteamRuntime]"""))
272278
store_actions.append(parser.add_argument(
273279
"-u", "--update",
274280
help="""**DEPRECATED** update the game
@@ -330,7 +336,7 @@ def create_arg_parser():
330336
action="store_true"))
331337
store_actions.append(parser.add_argument(
332338
"--skip-update-proton",
333-
help="""skip updating already-installed Proton
339+
help="""skip updating already-installed Proton and Steam Runtime
334340
when updating game with Proton enabled""",
335341
action="store_true"))
336342
store_actions.append(parser.add_argument(
@@ -347,6 +353,10 @@ def create_arg_parser():
347353
"--wine-steam-dir", metavar="DIR", type=str,
348354
help="""choose a directory for Windows version of Steam
349355
[Default: "C:\\Program Files (x86)\\Steam" in the prefix]"""))
356+
store_actions.append(parser.add_argument(
357+
"--without-steam-runtime",
358+
help="don't use Steam Runtime even when using Proton 5.13 or newer",
359+
action="store_true"))
350360
store_actions.append(parser.add_argument(
351361
"--without-wine-discord-ipc-bridge",
352362
help="don't use wine-discord-ipc-bridge for Discord Rich Presence",

Diff for: truckersmp_cli/main.py

+91-37
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
from .steamcmd import update_game
1616
from .truckersmp import update_mod
1717
from .utils import (
18-
activate_native_d3dcompiler_47, check_libsdl2,
19-
perform_self_update, set_wine_desktop_registry,
18+
activate_native_d3dcompiler_47, check_libsdl2, find_discord_ipc_sockets,
19+
get_proton_version, perform_self_update, set_wine_desktop_registry,
2020
setup_wine_discord_ipc_bridge, wait_for_steam,
2121
)
2222
from .variables import AppId, Args, Dir, File, URL
@@ -66,6 +66,7 @@ def get_version_string():
6666

6767
def main():
6868
"""truckersmp-cli main function."""
69+
# pylint: disable=too-many-branches,too-many-statements
6970
signal.signal(signal.SIGINT, signal.SIG_DFL)
7071

7172
# load Proton AppID info from "proton.json":
@@ -78,6 +79,16 @@ def main():
7879
except (OSError, ValueError) as ex:
7980
sys.exit("Failed to load proton.json: {}".format(ex))
8081

82+
# load Steam Runtime AppID info from "steamruntime.json":
83+
# {"platform": AppID, ... }
84+
# example:
85+
# {"Linux": 1391110}
86+
try:
87+
with open(File.steamruntime_json) as f_in:
88+
AppId.steamruntime = json.load(f_in)
89+
except (OSError, ValueError) as ex:
90+
sys.exit("Failed to load steamruntime.json: {}".format(ex))
91+
8192
# parse options
8293
arg_parser = create_arg_parser()[0]
8394
arg_parser.parse_args(namespace=Args)
@@ -138,11 +149,26 @@ def main():
138149

139150
# start truckersmp with proton or wine
140151
if Args.start:
141-
# check for Proton availability when starting with Proton
142-
if (Args.proton
143-
and not os.access(os.path.join(Args.protondir, "proton"), os.R_OK)):
144-
sys.exit("""Proton is not found in {}
152+
if Args.proton:
153+
# check for Proton availability when starting with Proton
154+
if not os.access(os.path.join(Args.protondir, "proton"), os.R_OK):
155+
sys.exit("""Proton is not found in {}
145156
Run with '--update' option to install Proton""".format(Args.protondir))
157+
# check for Steam Runtime availability and the permission of "var"
158+
# when starting with Proton + Steam Runtime
159+
run = os.path.join(Args.steamruntimedir, "run")
160+
var = os.path.join(Args.steamruntimedir, "var")
161+
if not Args.without_steam_runtime:
162+
if not os.access(run, os.R_OK | os.X_OK):
163+
sys.exit("""Steam Runtime is not found in {}
164+
Update the game or start with "--without-steam-runtime" option
165+
to disable the Steam Runtime""".format(
166+
Args.steamruntimedir))
167+
if (not os.access(Args.steamruntimedir, os.R_OK | os.W_OK | os.X_OK)
168+
or (os.path.isdir(var)
169+
and not os.access(var, os.R_OK | os.W_OK | os.X_OK))):
170+
sys.exit("""The Steam Runtime directory ({}) and
171+
the "var" subdirectory must be writable""".format(Args.steamruntimedir))
146172

147173
if not check_libsdl2():
148174
sys.exit("SDL2 was not found on your system.")
@@ -186,21 +212,50 @@ def start_with_proton():
186212

187213
prefix = os.path.join(Args.prefixdir, "pfx")
188214
proton = os.path.join(Args.protondir, "proton")
189-
argv = [sys.executable, proton, "run"]
215+
major, minor = get_proton_version(Args.protondir)
216+
logging.info("Proton version is (major=%d, minor=%d)", major, minor)
217+
proton_args = []
218+
run_in_steamrt = []
219+
discord_sockets = []
220+
if not Args.without_steam_runtime and (major >= 6 or (major == 5 and minor >= 13)):
221+
# use Steam Runtime container for Proton 5.13+
222+
logging.info("Using Steam Runtime container")
223+
# share directories with Steam Runtime container
224+
shared_paths = [Args.gamedir, Args.protondir, Args.prefixdir]
225+
if not Args.singleplayer:
226+
shared_paths += [Args.moddir, Dir.truckersmp_cli_data, Dir.scriptdir]
227+
discord_sockets = find_discord_ipc_sockets()
228+
if len(discord_sockets) > 0:
229+
shared_paths += discord_sockets
230+
logging.debug("Shared paths: %s", shared_paths)
231+
run_in_steamrt.append(os.path.join(Args.steamruntimedir, "run"))
232+
for shared_path in shared_paths:
233+
run_in_steamrt += ["--filesystem", shared_path]
234+
run_in_steamrt += ["--", "python3"] # helper script
235+
proton_args.append("python3") # Proton script
236+
else:
237+
# don't use Steam Runtime container for older Proton
238+
logging.info("Not using Steam Runtime container")
239+
run_in_steamrt.append(sys.executable) # helper
240+
proton_args.append(sys.executable) # Proton
241+
wine = run_in_steamrt.copy()
242+
proton_args += [proton, "run"]
243+
190244
env = os.environ.copy()
191245
env["STEAM_COMPAT_DATA_PATH"] = Args.prefixdir
192246
env["STEAM_COMPAT_CLIENT_INSTALL_PATH"] = steamdir
193247

194248
# Proton's "dist" directory tree is missing until first run
195249
# make sure it's present for using "dist/bin/wine" directly
196-
wine = os.path.join(Args.protondir, "dist/bin/wine")
197-
if (not os.access(wine, os.R_OK)
250+
wine_command = os.path.join(Args.protondir, "dist/bin/wine")
251+
wine.append(wine_command)
252+
if (not os.access(wine_command, os.R_OK)
198253
# native d3dcompiler_47 is removed when the prefix is downgraded
199254
# make sure the prefix is already upgraded/downgraded
200255
or Args.activate_native_d3dcompiler_47):
201256
try:
202257
subproc.check_output(
203-
argv + ["wineboot", ], env=env, stderr=subproc.STDOUT)
258+
proton_args + ["wineboot", ], env=env, stderr=subproc.STDOUT)
204259
except OSError as ex:
205260
sys.exit("Failed to run wineboot: {}".format(ex))
206261
except subproc.CalledProcessError as ex:
@@ -224,28 +279,18 @@ def start_with_proton():
224279
else:
225280
env["LD_PRELOAD"] = overlayrenderer
226281

227-
# start wine-discord-ipc-bridge for multiplayer
228-
# unless "--without-wine-discord-ipc-bridge" is specified
229-
ipcbr_proc = None
230-
if not Args.singleplayer and not Args.without_wine_discord_ipc_bridge:
231-
ipcbr_path = setup_wine_discord_ipc_bridge()
232-
logging.info("Starting wine-discord-ipc-bridge")
233-
ipcbr_proc = subproc.Popen(
234-
argv + [ipcbr_path, ],
235-
env=env, stdout=subproc.DEVNULL, stderr=subproc.DEVNULL)
236-
237282
# check whether singleplayer or multiplayer
238283
if Args.singleplayer:
239284
exename = "eurotrucks2.exe" if Args.ets2 else "amtrucks.exe"
240285
gamepath = os.path.join(Args.gamedir, "bin/win_x64", exename)
241-
argv.append(gamepath)
286+
proton_args.append(gamepath)
242287
else:
243-
argv += File.inject_exe, Args.gamedir, Args.moddir
288+
proton_args += File.inject_exe, Args.gamedir, Args.moddir
244289

245290
# game options
246291
for opt in Args.game_options.split(" "):
247292
if opt != "":
248-
argv.append(opt)
293+
proton_args.append(opt)
249294

250295
env["SteamGameId"] = Args.steamid
251296
env["SteamAppId"] = Args.steamid
@@ -259,25 +304,35 @@ def start_with_proton():
259304
"STEAM_COMPAT_DATA_PATH",
260305
]
261306

307+
argv_helper = run_in_steamrt
308+
argv_helper.append(File.steamruntime_helper)
309+
if (not Args.singleplayer
310+
and not Args.without_wine_discord_ipc_bridge
311+
# don't start wine-discord-ipc-bridge when no Discord sockets found
312+
and len(discord_sockets) > 0):
313+
argv_helper += ["--executable", File.ipcbridge]
314+
if Args.verbose:
315+
argv_helper.append("-v" if Args.verbose == 1 else "-vv")
316+
argv_helper += ["--", ] + proton_args
317+
262318
env_str = ""
263319
cmd_str = ""
264320
name_value_pairs = []
265321
for name in env_print:
266322
name_value_pairs.append("{}={}".format(name, env[name]))
267323
env_str += "\n ".join(name_value_pairs) + "\n "
268-
cmd_str += "\n ".join(argv)
269-
logging.info("Running Proton:\n %s%s", env_str, cmd_str)
324+
cmd_str += "\n ".join(proton_args)
325+
logging.info("Running Steam Runtime helper:\n %s%s", env_str, cmd_str)
270326
try:
271-
output = subproc.check_output(argv, env=env, stderr=subproc.STDOUT)
272-
logging.info("Proton output:\n%s", output.decode("utf-8"))
327+
with subproc.Popen(
328+
argv_helper,
329+
env=env, stdout=subproc.PIPE, stderr=subproc.STDOUT) as proc:
330+
if Args.verbose and Args.verbose >= 1:
331+
for line in proc.stdout:
332+
print(line.decode("utf-8"), end="")
273333
except subproc.CalledProcessError as ex:
274-
logging.error("Proton output:\n%s", ex.output.decode("utf-8"))
275-
276-
if ipcbr_proc:
277-
# make sure wine-discord-ipc-bridge is exited
278-
if ipcbr_proc.poll() is None:
279-
ipcbr_proc.kill()
280-
ipcbr_proc.wait()
334+
logging.error(
335+
"Steam Runtime helper exited abnormally:\n%s", ex.output.decode("utf-8"))
281336

282337
# disable Wine desktop if enabled
283338
if Args.wine_desktop:
@@ -288,8 +343,9 @@ def start_with_wine():
288343
"""Start game with Wine."""
289344
# pylint: disable=consider-using-with,too-many-branches
290345
wine = os.environ["WINE"] if "WINE" in os.environ else "wine"
346+
argv = [wine, ]
291347
if Args.activate_native_d3dcompiler_47:
292-
activate_native_d3dcompiler_47(Args.prefixdir, wine)
348+
activate_native_d3dcompiler_47(Args.prefixdir, argv)
293349

294350
env = os.environ.copy()
295351
env["WINEDEBUG"] = "-all"
@@ -303,8 +359,6 @@ def start_with_wine():
303359
env=env,
304360
)
305361

306-
argv = [wine, ]
307-
308362
ipcbr_proc = None
309363
if not Args.singleplayer and not Args.without_wine_discord_ipc_bridge:
310364
ipcbr_path = setup_wine_discord_ipc_bridge()

0 commit comments

Comments
 (0)