diff --git a/DXMainClient/AssertFailedException.cs b/DXMainClient/AssertFailedException.cs new file mode 100644 index 000000000..5795e5e39 --- /dev/null +++ b/DXMainClient/AssertFailedException.cs @@ -0,0 +1,12 @@ +#nullable enable +using System; + +namespace DTAClient +{ + public class AssertFailedException : Exception + { + public AssertFailedException(string message) : base(message) + { + } + } +} diff --git a/DXMainClient/DXGUI/Multiplayer/CnCNet/CnCNetLobby.cs b/DXMainClient/DXGUI/Multiplayer/CnCNet/CnCNetLobby.cs index 586399f6f..df5dc19aa 100644 --- a/DXMainClient/DXGUI/Multiplayer/CnCNet/CnCNetLobby.cs +++ b/DXMainClient/DXGUI/Multiplayer/CnCNet/CnCNetLobby.cs @@ -1399,7 +1399,8 @@ private void RefreshPlayerList(object sender, EventArgs e) lbPlayerList.Clear(); // Note: IUserCollection.GetFirst() is not guaranteed to be implemented, unless it is a SortedUserCollection - Debug.Assert(currentChatChannel.Users is SortedUserCollection, "Channel 'users' is supposed to be a SortedUserCollection"); + Dev.Assert(currentChatChannel.Users is SortedUserCollection, "Channel 'users' is supposed to be a SortedUserCollection"); + var current = currentChatChannel.Users.GetFirst(); while (current != null) { diff --git a/DXMainClient/DXGUI/Multiplayer/GameInformationPanel.cs b/DXMainClient/DXGUI/Multiplayer/GameInformationPanel.cs index 6c0910c75..88116e372 100644 --- a/DXMainClient/DXGUI/Multiplayer/GameInformationPanel.cs +++ b/DXMainClient/DXGUI/Multiplayer/GameInformationPanel.cs @@ -197,7 +197,7 @@ public void SetInfo(GenericHostedGame game) mapTexture = mapLoader.GameModeMaps.Find(m => m.Map.UntranslatedName.Equals(game.Map, StringComparison.InvariantCultureIgnoreCase) && m.Map.IsPreviewTextureCached())?.Map?.LoadPreviewTexture(); if (mapTexture == null && noMapPreviewTexture != null) { - Debug.Assert(!noMapPreviewTexture.IsDisposed, "noMapPreviewTexture should not be disposed."); + Dev.Assert(!noMapPreviewTexture.IsDisposed, "noMapPreviewTexture should not be disposed."); mapTexture = noMapPreviewTexture; disposeTextures = false; } @@ -223,7 +223,7 @@ public void ClearInfo() if (mapTexture != null && disposeTextures) { - Debug.Assert(!mapTexture.IsDisposed, "mapTexture should not be disposed."); + Dev.Assert(!mapTexture.IsDisposed, "mapTexture should not be disposed."); mapTexture.Dispose(); mapTexture = null; } diff --git a/DXMainClient/Dev.cs b/DXMainClient/Dev.cs new file mode 100644 index 000000000..7e714dc5b --- /dev/null +++ b/DXMainClient/Dev.cs @@ -0,0 +1,43 @@ +#nullable enable +using System; +using System.Diagnostics; + +using ClientCore; + +namespace DTAClient +{ + public static class Dev + { + public static bool IsDev { get; private set; } = false; + + public static void Initialize() + { + IsDev = IsDev || ClientConfiguration.Instance.ModMode; + +#if DEVELOPMENT_BUILD + IsDev = IsDev || ClientConfiguration.Instance.ShowDevelopmentBuildWarnings; +#endif + } + + public static void Assert(bool condition, string message) + { + if (!IsDev) + { + Debug.Assert(condition, message); + return; + } + + if (!condition) + { + try + { + throw new AssertFailedException($"Assert failed. {message}"); + } + catch (Exception ex) + { + PreStartup.HandleException(null, ex, Environment.StackTrace); + } + } + } + } +} diff --git a/DXMainClient/Domain/Multiplayer/Map.cs b/DXMainClient/Domain/Multiplayer/Map.cs index 485fca83f..e961c6822 100644 --- a/DXMainClient/Domain/Multiplayer/Map.cs +++ b/DXMainClient/Domain/Multiplayer/Map.cs @@ -52,7 +52,7 @@ public Map(string baseFilePath) public Map(string baseFilePath, bool isCustomMap) { - Debug.Assert(!baseFilePath.EndsWith($".{ClientConfiguration.Instance.MapFileExtension}", StringComparison.InvariantCultureIgnoreCase), $"Unexpected map path {baseFilePath}. It should not end with the map extension."); + Dev.Assert(!baseFilePath.EndsWith($".{ClientConfiguration.Instance.MapFileExtension}", StringComparison.InvariantCultureIgnoreCase), $"Unexpected map path {baseFilePath}. It should not end with the map extension."); BaseFilePath = baseFilePath; customMapFilePath = isCustomMap @@ -400,7 +400,8 @@ public bool SetInfoFromMpMapsINI(IniFile iniFile) if (string.IsNullOrEmpty(waypoint)) break; - Debug.Assert(int.TryParse(waypoint.Split(',')[0], out _), $"waypoint should be a number, got {waypoint}"); + Dev.Assert(int.TryParse(waypoint.Split(',')[0], out _), $"waypoint should be a number, got {waypoint}"); + waypoints.Add(waypoint); } diff --git a/DXMainClient/Domain/Multiplayer/MapLoader.cs b/DXMainClient/Domain/Multiplayer/MapLoader.cs index 9c2679d8c..d20d52bec 100644 --- a/DXMainClient/Domain/Multiplayer/MapLoader.cs +++ b/DXMainClient/Domain/Multiplayer/MapLoader.cs @@ -255,7 +255,7 @@ private ConcurrentDictionary LoadCustomMapCache() /// The map if loading it was successful, otherwise false. public Map LoadCustomMap(string mapPath, out string resultMessage) { - Debug.Assert(!mapPath.EndsWith($".{ClientConfiguration.Instance.MapFileExtension}", StringComparison.InvariantCultureIgnoreCase), $"Unexpected map path {mapPath}. It should not end with the map extension."); + Dev.Assert(!mapPath.EndsWith($".{ClientConfiguration.Instance.MapFileExtension}", StringComparison.InvariantCultureIgnoreCase), $"Unexpected map path {mapPath}. It should not end with the map extension."); string customMapFilePath = SafePath.CombineFilePath(ProgramConstants.GamePath, FormattableString.Invariant($"{mapPath}.{ClientConfiguration.Instance.MapFileExtension}")); FileInfo customMapFile = SafePath.GetFile(customMapFilePath); diff --git a/DXMainClient/Online/Channel.cs b/DXMainClient/Online/Channel.cs index 9432341a6..3dfd053b1 100644 --- a/DXMainClient/Online/Channel.cs +++ b/DXMainClient/Online/Channel.cs @@ -148,7 +148,8 @@ public void OnUserListReceived(List userList) existingUser.IsFriend = user.IsFriend; // Note: IUserCollection.Reinsert() is not guaranteed to be implemented, unless it is a SortedUserCollection - Debug.Assert(users is SortedUserCollection, "Channel 'users' is supposed to be a SortedUserCollection"); + Dev.Assert(users is SortedUserCollection, "Channel 'users' is supposed to be a SortedUserCollection"); + users.Reinsert(user.IRCUser.Name); } } diff --git a/DXMainClient/Online/FileHashCalculator.cs b/DXMainClient/Online/FileHashCalculator.cs index 56946a5f0..ce619aad4 100644 --- a/DXMainClient/Online/FileHashCalculator.cs +++ b/DXMainClient/Online/FileHashCalculator.cs @@ -145,7 +145,7 @@ public void CalculateHashes() { string fileRelativePath = SafePath.CombineFilePath(path.Name, filename); string fileFullPath = SafePath.CombineFilePath(path.FullName, filename); - Debug.Assert(File.Exists(fileFullPath), $"File {fileFullPath} is supposed to but does not exist."); + Dev.Assert(File.Exists(fileFullPath), $"File {fileFullPath} is supposed to but does not exist."); string hash = fh.AddHashForFileIfExists(fileRelativePath, fileFullPath); if (!string.IsNullOrEmpty(hash)) @@ -262,7 +262,7 @@ public string AddHashForFileIfExists(string relativePath) => public string AddHashForFileIfExists(string relativePath, string filePath) { - Debug.Assert(!relativePath.StartsWith(ProgramConstants.GamePath), $"File path {relativePath} should be a relative path."); + Dev.Assert(!relativePath.StartsWith(ProgramConstants.GamePath), $"File path {relativePath} should be a relative path."); string hash = CalculateSHA1ForFile(filePath); if (!string.IsNullOrEmpty(hash)) diff --git a/DXMainClient/PreStartup.cs b/DXMainClient/PreStartup.cs index 0d73927a1..4906bd1ae 100644 --- a/DXMainClient/PreStartup.cs +++ b/DXMainClient/PreStartup.cs @@ -205,6 +205,8 @@ public static void Initialize(StartupParams parameters) MainClientConstants.DisplayErrorAction(null, error, true); } + Dev.Initialize(); + Startup startup = new(); #if DEBUG startup.Execute(); @@ -224,7 +226,7 @@ public static void Initialize(StartupParams parameters) } - public static void LogException(Exception ex, bool innerException = false) + public static void LogException(Exception ex, bool innerException = false, string stackTraceOverride = null) { if (!innerException) Logger.Log("KABOOOOOOM!!! Info:"); @@ -235,15 +237,15 @@ public static void LogException(Exception ex, bool innerException = false) Logger.Log("Message: " + ex.Message); Logger.Log("Source: " + ex.Source); Logger.Log("TargetSite.Name: " + ex.TargetSite?.Name); - Logger.Log("Stacktrace: " + ex.StackTrace); + Logger.Log("Stacktrace: " + (stackTraceOverride ?? ex.StackTrace)); if (ex.InnerException is not null) LogException(ex.InnerException, true); } - public static void HandleException(object sender, Exception ex) + public static void HandleException(object sender, Exception ex, string stackTraceOverride = null) { - LogException(ex, innerException: false); + LogException(ex, innerException: false, stackTraceOverride); string errorLogPath = SafePath.CombineFilePath(ProgramConstants.ClientUserFilesPath, "ClientCrashLogs", FormattableString.Invariant($"ClientCrashLog{DateTime.Now.ToString("_yyyy_MM_dd_HH_mm")}.txt")); bool crashLogCopied = false;