15
15
from .steamcmd import update_game
16
16
from .truckersmp import update_mod
17
17
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 ,
20
20
setup_wine_discord_ipc_bridge , wait_for_steam ,
21
21
)
22
22
from .variables import AppId , Args , Dir , File , URL
@@ -66,6 +66,7 @@ def get_version_string():
66
66
67
67
def main ():
68
68
"""truckersmp-cli main function."""
69
+ # pylint: disable=too-many-branches,too-many-statements
69
70
signal .signal (signal .SIGINT , signal .SIG_DFL )
70
71
71
72
# load Proton AppID info from "proton.json":
@@ -78,6 +79,16 @@ def main():
78
79
except (OSError , ValueError ) as ex :
79
80
sys .exit ("Failed to load proton.json: {}" .format (ex ))
80
81
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
+
81
92
# parse options
82
93
arg_parser = create_arg_parser ()[0 ]
83
94
arg_parser .parse_args (namespace = Args )
@@ -138,11 +149,26 @@ def main():
138
149
139
150
# start truckersmp with proton or wine
140
151
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 {}
145
156
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 ))
146
172
147
173
if not check_libsdl2 ():
148
174
sys .exit ("SDL2 was not found on your system." )
@@ -186,21 +212,50 @@ def start_with_proton():
186
212
187
213
prefix = os .path .join (Args .prefixdir , "pfx" )
188
214
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
+
190
244
env = os .environ .copy ()
191
245
env ["STEAM_COMPAT_DATA_PATH" ] = Args .prefixdir
192
246
env ["STEAM_COMPAT_CLIENT_INSTALL_PATH" ] = steamdir
193
247
194
248
# Proton's "dist" directory tree is missing until first run
195
249
# 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 )
198
253
# native d3dcompiler_47 is removed when the prefix is downgraded
199
254
# make sure the prefix is already upgraded/downgraded
200
255
or Args .activate_native_d3dcompiler_47 ):
201
256
try :
202
257
subproc .check_output (
203
- argv + ["wineboot" , ], env = env , stderr = subproc .STDOUT )
258
+ proton_args + ["wineboot" , ], env = env , stderr = subproc .STDOUT )
204
259
except OSError as ex :
205
260
sys .exit ("Failed to run wineboot: {}" .format (ex ))
206
261
except subproc .CalledProcessError as ex :
@@ -224,28 +279,18 @@ def start_with_proton():
224
279
else :
225
280
env ["LD_PRELOAD" ] = overlayrenderer
226
281
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
-
237
282
# check whether singleplayer or multiplayer
238
283
if Args .singleplayer :
239
284
exename = "eurotrucks2.exe" if Args .ets2 else "amtrucks.exe"
240
285
gamepath = os .path .join (Args .gamedir , "bin/win_x64" , exename )
241
- argv .append (gamepath )
286
+ proton_args .append (gamepath )
242
287
else :
243
- argv += File .inject_exe , Args .gamedir , Args .moddir
288
+ proton_args += File .inject_exe , Args .gamedir , Args .moddir
244
289
245
290
# game options
246
291
for opt in Args .game_options .split (" " ):
247
292
if opt != "" :
248
- argv .append (opt )
293
+ proton_args .append (opt )
249
294
250
295
env ["SteamGameId" ] = Args .steamid
251
296
env ["SteamAppId" ] = Args .steamid
@@ -259,25 +304,35 @@ def start_with_proton():
259
304
"STEAM_COMPAT_DATA_PATH" ,
260
305
]
261
306
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
+
262
318
env_str = ""
263
319
cmd_str = ""
264
320
name_value_pairs = []
265
321
for name in env_print :
266
322
name_value_pairs .append ("{}={}" .format (name , env [name ]))
267
323
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 )
270
326
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 = "" )
273
333
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" ))
281
336
282
337
# disable Wine desktop if enabled
283
338
if Args .wine_desktop :
@@ -288,8 +343,9 @@ def start_with_wine():
288
343
"""Start game with Wine."""
289
344
# pylint: disable=consider-using-with,too-many-branches
290
345
wine = os .environ ["WINE" ] if "WINE" in os .environ else "wine"
346
+ argv = [wine , ]
291
347
if Args .activate_native_d3dcompiler_47 :
292
- activate_native_d3dcompiler_47 (Args .prefixdir , wine )
348
+ activate_native_d3dcompiler_47 (Args .prefixdir , argv )
293
349
294
350
env = os .environ .copy ()
295
351
env ["WINEDEBUG" ] = "-all"
@@ -303,8 +359,6 @@ def start_with_wine():
303
359
env = env ,
304
360
)
305
361
306
- argv = [wine , ]
307
-
308
362
ipcbr_proc = None
309
363
if not Args .singleplayer and not Args .without_wine_discord_ipc_bridge :
310
364
ipcbr_path = setup_wine_discord_ipc_bridge ()
0 commit comments