Skip to content

Commit b98bd95

Browse files
author
Mindless2831
committed
Add Discord SDK crash fallback
1 parent a89227d commit b98bd95

7 files changed

Lines changed: 185 additions & 15 deletions

File tree

Nitrox.Launcher/Models/Extensions/KeyValueStoreExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,7 @@ public static class KeyValueStoreExtensions
1616
public void SetPreferEmbedded(bool value) => self.SetValue("PreferEmbedded", value);
1717
public bool GetUseBigPictureMode(bool defaultValue = false) => self.GetValue("UseBigPictureMode", defaultValue);
1818
public void SetBigPictureMode(bool value) => self.SetValue("UseBigPictureMode", value);
19+
public bool GetIsDiscordIntegrationEnabled(bool defaultValue = true) => self.GetValue("IsDiscordIntegrationEnabled", defaultValue);
20+
public void SetIsDiscordIntegrationEnabled(bool value) => self.SetValue("IsDiscordIntegrationEnabled", value);
1921
}
2022
}

Nitrox.Launcher/ViewModels/LaunchGameViewModel.cs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.ComponentModel;
33
using System.Diagnostics;
44
using System.IO;
5+
using System.Linq;
56
using System.Runtime.InteropServices;
67
using System.Threading;
78
using 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)

Nitrox.Launcher/ViewModels/OptionsViewModel.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.IO;
33
using System.Threading;
44
using System.Threading.Tasks;
@@ -54,11 +54,14 @@ internal partial class OptionsViewModel(IKeyValueStore keyValueStore, StorageSer
5454
public partial bool AllowMultipleGameInstances { get; set; }
5555

5656
[ObservableProperty]
57-
public partial bool UseBigPictureMode { get; set; }
57+
public partial bool UseBigPictureMode { get; set; }
58+
59+
[ObservableProperty]
60+
public partial bool DiscordIntegrationEnabled { get; set; }
5861

5962
[ObservableProperty]
6063
public partial bool IsInReleaseMode { get; set; }
61-
64+
6265
private static string DefaultLaunchArg => "-vrmode none";
6366
private bool isResettingArgs;
6467

@@ -73,6 +76,7 @@ internal override async Task ViewContentLoadAsync(CancellationToken cancellation
7376
LightModeEnabled = keyValueStore.GetIsLightModeEnabled();
7477
AllowMultipleGameInstances = keyValueStore.GetIsMultipleGameInstancesAllowed();
7578
UseBigPictureMode = keyValueStore.GetUseBigPictureMode();
79+
DiscordIntegrationEnabled = keyValueStore.GetIsDiscordIntegrationEnabled();
7680
IsInReleaseMode = NitroxEnvironment.IsReleaseMode;
7781
await Task.Run(() => SetTargetedSubnauticaPath(SelectedGame.PathToGame), cancellationToken).ContinueWithHandleError(ex => LauncherNotifier.Error(ex.Message));
7882
}
@@ -196,4 +200,9 @@ partial void OnUseBigPictureModeChanged(bool value)
196200
}
197201
keyValueStore.SetBigPictureMode(value);
198202
}
203+
204+
partial void OnDiscordIntegrationEnabledChanged(bool value)
205+
{
206+
keyValueStore.SetIsDiscordIntegrationEnabled(value);
207+
}
199208
}

Nitrox.Launcher/Views/OptionsView.axaml

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -184,14 +184,22 @@
184184
</WrapPanel.Styles>
185185

186186
<Border>
187-
<StackPanel>
188-
<TextBlock Text="Enable light mode" />
189-
<CheckBox Classes="switch"
190-
IsChecked="{Binding LightModeEnabled}"
191-
ToolTip.Tip="Enable this option to use the Nitrox launcher in light mode" />
192-
</StackPanel>
193-
</Border>
194-
<Border IsVisible="{Binding SelectedGame.Platform, Converter={converters:EqualityConverter}, ConverterParameter={x:Static models:Platform.STEAM}}">
187+
<StackPanel>
188+
<TextBlock Text="Enable light mode" />
189+
<CheckBox Classes="switch"
190+
IsChecked="{Binding LightModeEnabled}"
191+
ToolTip.Tip="Enable this option to use the Nitrox launcher in light mode" />
192+
</StackPanel>
193+
</Border>
194+
<Border>
195+
<StackPanel>
196+
<TextBlock Text="Enable Discord integration" />
197+
<CheckBox Classes="switch"
198+
IsChecked="{Binding DiscordIntegrationEnabled}"
199+
ToolTip.Tip="Enable Discord Rich Presence and Discord join integration. Disable this if Discord overlay or recording causes Subnautica to crash." />
200+
</StackPanel>
201+
</Border>
202+
<Border IsVisible="{Binding SelectedGame.Platform, Converter={converters:EqualityConverter}, ConverterParameter={x:Static models:Platform.STEAM}}">
195203
<StackPanel>
196204
<TextBlock Text="Allow multiple game instances" />
197205
<CheckBox Classes="switch"

NitroxClient/MonoBehaviours/Gui/MainMenu/NitroxMainMenuModifications.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using NitroxClient.MonoBehaviours.Discord;
1+
using NitroxClient.MonoBehaviours.Discord;
22
using NitroxClient.MonoBehaviours.Gui.MainMenu.ServerJoin;
33
using NitroxClient.MonoBehaviours.Gui.MainMenu.ServersList;
44
using TMPro;
@@ -22,7 +22,11 @@ private void SceneManager_sceneLoaded(Scene scene, LoadSceneMode loadMode)
2222
{
2323
rightSide = MainMenuRightSide.main;
2424
MultiplayerMenuMods();
25-
DiscordClient.InitializeRPMenu();
25+
26+
if (!NitroxBootstrapper.IsDiscordIntegrationDisabled)
27+
{
28+
DiscordClient.InitializeRPMenu();
29+
}
2630
}
2731
}
2832

NitroxClient/MonoBehaviours/Multiplayer.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,10 @@ private static void SetLoadingComplete()
193193
PlayerManager remotePlayerManager = NitroxServiceLocator.LocateService<PlayerManager>();
194194

195195
TopRightWatermarkText.ApplyChangesForInGame();
196-
DiscordClient.InitializeRPInGame(Main.multiplayerSession.AuthenticationContext.Username, remotePlayerManager.GetTotalPlayerCount(), Main.multiplayerSession.SessionPolicy.MaxConnections);
196+
if (!NitroxBootstrapper.IsDiscordIntegrationDisabled)
197+
{
198+
DiscordClient.InitializeRPInGame(Main.multiplayerSession.AuthenticationContext.Username, remotePlayerManager.GetTotalPlayerCount(), Main.multiplayerSession.SessionPolicy.MaxConnections);
199+
}
197200
CoroutineHost.StartCoroutine(PlayerChatManager.Instance.LoadChatKeyHint());
198201
}
199202

NitroxClient/MonoBehaviours/NitroxBootstrapper.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using NitroxClient.MonoBehaviours.Discord;
23
using NitroxClient.MonoBehaviours.Gui.MainMenu;
34
using UnityEngine;
@@ -6,8 +7,13 @@ namespace NitroxClient.MonoBehaviours;
67

78
public class NitroxBootstrapper : MonoBehaviour
89
{
10+
private const string DISABLE_DISCORD_INTEGRATION_ARG = "--disable-discord-integration";
11+
912
internal static NitroxBootstrapper Instance;
1013

14+
public static bool IsDiscordIntegrationDisabled =>
15+
Array.Exists(Environment.GetCommandLineArgs(), arg => arg.Equals(DISABLE_DISCORD_INTEGRATION_ARG, StringComparison.OrdinalIgnoreCase));
16+
1117
// Awake is too early in Subnautica's lifecycle to access PlatformUtils
1218
// so we pick Start which will always happen after it's initialized
1319
private void Start()
@@ -16,7 +22,15 @@ private void Start()
1622
Instance = this;
1723
gameObject.AddComponent<SceneCleanerPreserve>();
1824
gameObject.AddComponent<NitroxMainMenuModifications>();
19-
gameObject.AddComponent<DiscordClient>();
25+
26+
if (IsDiscordIntegrationDisabled)
27+
{
28+
Log.Info("[Discord] Discord integration disabled by launch argument");
29+
}
30+
else
31+
{
32+
gameObject.AddComponent<DiscordClient>();
33+
}
2034

2135
#if DEBUG
2236
EnableDeveloperFeatures();

0 commit comments

Comments
 (0)