22using System . ComponentModel ;
33using System . Diagnostics ;
44using System . IO ;
5+ using System . Linq ;
56using System . Runtime . InteropServices ;
67using System . Threading ;
78using System . Threading . Tasks ;
@@ -27,6 +28,15 @@ internal partial class LaunchGameViewModel(DialogService dialogService, ServerSe
2728 : RoutableViewModelBase
2829{
2930 public static Task < string > ? LastFindSubnauticaTask ;
31+ private const string DISABLE_DISCORD_INTEGRATION_ARG = "--disable-discord-integration" ;
32+ private static readonly TimeSpan DiscordCrashObservationPeriod = TimeSpan . FromSeconds ( 45 ) ;
33+ private static readonly string [ ] DiscordCrashSignatures =
34+ [
35+ "discord_game_sdk" ,
36+ "DiscordClient:UpdateActivity" ,
37+ "DiscordClient:InitializeRPMenu" ,
38+ "DiscordGameSDKWrapper.ActivityManager:UpdateActivity"
39+ ] ;
3040 private static bool hasInstantLaunched ;
3141 private readonly DialogService dialogService = dialogService ;
3242 private readonly IKeyValueStore keyValueStore = keyValueStore ;
@@ -236,6 +246,12 @@ private async Task StartGameAsync(GameInfo gameInfo, string[]? args = null)
236246
237247 // Start game & gaming platform if needed.
238248 string launchArguments = $ "{ keyValueStore . GetLaunchArguments ( gameInfo ) } { string . Join ( " " , args ?? NitroxEnvironment . CommandLineArgs ) } ";
249+
250+ if ( ! keyValueStore . GetIsDiscordIntegrationEnabled ( ) && ! launchArguments . Contains ( DISABLE_DISCORD_INTEGRATION_ARG , StringComparison . OrdinalIgnoreCase ) )
251+ {
252+ launchArguments = $ "{ launchArguments } { DISABLE_DISCORD_INTEGRATION_ARG } ";
253+ }
254+
239255 ProcessEx game = NitroxUser . GamePlatform switch
240256 {
241257 Steam => await Steam . StartGameAsync ( gameExePath , launchArguments , gameInfo . SteamAppId , ShouldSkipSteam ( launchArguments ) , keyValueStore . GetUseBigPictureMode ( ) ) ,
@@ -250,6 +266,120 @@ private async Task StartGameAsync(GameInfo gameInfo, string[]? args = null)
250266 {
251267 throw new Exception ( $ "Game failed to start through { NitroxUser . GamePlatform ? . Name ?? "Standalone" } ") ;
252268 }
269+
270+ if ( ! launchArguments . Contains ( DISABLE_DISCORD_INTEGRATION_ARG , StringComparison . OrdinalIgnoreCase ) )
271+ {
272+ _ = MonitorDiscordCrashFallbackAsync ( args , game ) . ContinueWithHandleError ( ) ;
273+ }
274+ }
275+
276+ private async Task MonitorDiscordCrashFallbackAsync ( string [ ] ? args , ProcessEx game )
277+ {
278+ int processId = game . Id ;
279+ DateTime startedAt = DateTime . UtcNow ;
280+
281+ Log . Info ( $ "Monitoring Subnautica process #{ processId } for Discord SDK launch crash for { DiscordCrashObservationPeriod . TotalSeconds : 0} seconds") ;
282+
283+ try
284+ {
285+ while ( DateTime . UtcNow - startedAt < DiscordCrashObservationPeriod )
286+ {
287+ await Task . Delay ( 1000 ) ;
288+
289+ if ( ! game . IsRunning )
290+ {
291+ Log . Warn ( $ "Subnautica process #{ processId } exited during Discord crash observation window") ;
292+
293+ if ( PlayerLogContainsDiscordCrashSignature ( startedAt ) )
294+ {
295+ Log . Warn ( "Detected Discord SDK crash signature in Player.log. Relaunching with Discord integration disabled." ) ;
296+
297+ LauncherNotifier . Warning ( "Subnautica appeared to crash while initializing Discord integration. Nitrox is relaunching with Discord integration disabled." ) ;
298+
299+ string [ ] retryArgs = AppendDisableDiscordIntegrationArg ( args ) ;
300+ await StartSubnauticaAsync ( retryArgs ) ;
301+ }
302+
303+ return ;
304+ }
305+ }
306+ }
307+ catch ( Exception ex )
308+ {
309+ Log . Error ( ex , "Error while monitoring for Discord SDK crash fallback:" ) ;
310+ }
311+ finally
312+ {
313+ game . Dispose ( ) ;
314+ }
315+ }
316+
317+ private static string [ ] AppendDisableDiscordIntegrationArg ( string [ ] ? args )
318+ {
319+ if ( args ? . Any ( arg => arg . Equals ( DISABLE_DISCORD_INTEGRATION_ARG , StringComparison . OrdinalIgnoreCase ) ) == true )
320+ {
321+ return args ;
322+ }
323+
324+ return [ .. args ?? [ ] , DISABLE_DISCORD_INTEGRATION_ARG ] ;
325+ }
326+
327+ private static bool PlayerLogContainsDiscordCrashSignature ( DateTime launchMonitorStartedAt )
328+ {
329+ string playerLogPath = GetSubnauticaPlayerLogPath ( ) ;
330+
331+ if ( ! File . Exists ( playerLogPath ) )
332+ {
333+ Log . Warn ( $ "Unable to inspect Player.log for Discord crash signature because it does not exist: { playerLogPath } ") ;
334+ return false ;
335+ }
336+
337+ DateTime playerLogLastWriteTime = File . GetLastWriteTimeUtc ( playerLogPath ) ;
338+ if ( playerLogLastWriteTime < launchMonitorStartedAt . AddSeconds ( - 10 ) )
339+ {
340+ Log . Info ( $ "Ignoring Player.log for Discord crash detection because it was not updated during this launch attempt: { playerLogPath } ") ;
341+ return false ;
342+ }
343+
344+ string playerLog = File . ReadAllText ( playerLogPath ) ;
345+
346+ bool hasDiscordCrashSignature = DiscordCrashSignatures . Any ( signature => playerLog . Contains ( signature , StringComparison . OrdinalIgnoreCase ) ) ;
347+
348+ Log . Info ( $ "Discord crash signature detected in Player.log: { hasDiscordCrashSignature } ") ;
349+
350+ return hasDiscordCrashSignature ;
351+ }
352+
353+ private static string GetSubnauticaPlayerLogPath ( )
354+ {
355+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
356+ {
357+ return Path . Combine (
358+ Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) ,
359+ "Low" ,
360+ "Unknown Worlds" ,
361+ "Subnautica" ,
362+ "Player.log" ) ;
363+ }
364+
365+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) )
366+ {
367+ return Path . Combine (
368+ Environment . GetFolderPath ( Environment . SpecialFolder . Personal ) ,
369+ "Library" ,
370+ "Logs" ,
371+ "Unknown Worlds" ,
372+ "Subnautica" ,
373+ "Player.log" ) ;
374+ }
375+
376+ return Path . Combine (
377+ Environment . GetFolderPath ( Environment . SpecialFolder . UserProfile ) ,
378+ ".config" ,
379+ "unity3d" ,
380+ "Unknown Worlds" ,
381+ "Subnautica" ,
382+ "Player.log" ) ;
253383 }
254384
255385 private bool ShouldSkipSteam ( string args )
0 commit comments