#region Using Statements using Barotrauma.Steam; using System; using System.Diagnostics; using Barotrauma.IO; using System.Linq; using System.Text; using System.Threading; using Barotrauma.Debugging; using Barotrauma.Networking; #if LINUX using System.Runtime.InteropServices; #endif #endregion namespace Barotrauma { /// /// The main class. /// public static class Program { #if LINUX /// /// Sets the required environment variables for the game to initialize Steamworks correctly. /// [DllImport("linux_steam_env", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private static extern void setLinuxEnv(); #endif public static bool TryStartChildServerRelay(string[] commandLineArgs) { for (int i = 0; i < commandLineArgs.Length; i++) { switch (commandLineArgs[i].Trim()) { case "-pipes": ChildServerRelay.Start(commandLineArgs[i + 2], commandLineArgs[i + 1]); return true; } } return false; } /// /// The main entry point for the application. /// [STAThread] static void Main(string[] args) { AppDomain currentDomain = AppDomain.CurrentDomain; currentDomain.ProcessExit += OnProcessExit; #if !DEBUG currentDomain.UnhandledException += new UnhandledExceptionEventHandler(CrashHandler); #endif TryStartChildServerRelay(args); #if LINUX setLinuxEnv(); AppDomain.CurrentDomain.ProcessExit += (s, e) => { GameMain.ShouldRun = false; }; #endif Console.WriteLine("Barotrauma Dedicated Server(EP) " + GameMain.Version + " (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")"); if (Console.IsOutputRedirected) { Console.WriteLine("Output redirection detected; colored text and command input will be disabled."); } if (Console.IsInputRedirected) { Console.WriteLine("Redirected input is detected but is not supported by this application. Input will be ignored."); } string executableDir = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location); if (!File.Exists(Path.Combine(executableDir, "workshop.txt"))) { Directory.SetCurrentDirectory(executableDir); } DebugConsoleCore.Init( newMessage: (s, c) => DebugConsole.NewMessage(s, c), log: DebugConsole.Log); Game = new GameMain(args); Game.Run(); ShutDown(); } private static bool hasShutDown = false; private static void ShutDown() { SingleThreadWorker.Instance.Dispose(); if (hasShutDown) { return; } hasShutDown = true; if (GameAnalyticsManager.SendUserStatistics) { GameAnalyticsManager.ShutDown(); } SteamManager.ShutDown(); // Gracefully exit EOS by ticking until the session is closed EosInterface.Core.CleanupAndQuit(); while (EosInterface.Core.IsInitialized) { EosInterface.Core.Update(); TaskPool.Update(); Thread.Sleep(16); } } private static void OnProcessExit(object sender, EventArgs e) { ShutDown(); } static GameMain Game; private static void NotifyCrash(string reportFilePath, Exception e) { string errorMsg = $"{reportFilePath}||\n{e.Message} ({e.GetType().Name}) {e.StackTrace}"; if (e.InnerException != null) { var innerMost = e.GetInnermost(); errorMsg += $"\nInner exception: {innerMost.Message} ({innerMost.GetType().Name}) {e.StackTrace}"; } if (errorMsg.Length > ushort.MaxValue) { errorMsg = errorMsg[..ushort.MaxValue]; } ChildServerRelay.NotifyCrash(errorMsg); GameMain.Server?.NotifyCrash(); } private static void CrashHandler(object sender, UnhandledExceptionEventArgs args) { static void swallowExceptions(Action action) { try { action(); } catch { //discard exceptions and keep going } } Exception unhandledException = args.ExceptionObject as Exception; string reportFilePath = ""; try { reportFilePath = "servercrashreport.log"; CrashDump(ref reportFilePath, unhandledException); } catch (Exception exceptionHandlerError) { Debug.WriteLine(exceptionHandlerError.Message); string slimCrashReport = "Exception handler failed: " + exceptionHandlerError.Message + "\n" + exceptionHandlerError.StackTrace; if (unhandledException != null) { slimCrashReport += "\n\nInitial exception: " + unhandledException.Message + "\n" + unhandledException.StackTrace; } File.WriteAllText("servercrashreportslim.log", slimCrashReport); reportFilePath = ""; } swallowExceptions(() => NotifyCrash(reportFilePath, unhandledException)); swallowExceptions(() => Game?.Exit()); } static void CrashDump(ref string filePath, Exception exception) { try { GameMain.Server?.ServerSettings?.SaveSettings(); GameMain.Server?.ServerSettings?.BanList.Save(); if (GameMain.Server?.ServerSettings?.KarmaPreset == "custom") { GameMain.Server?.KarmaManager?.SaveCustomPreset(); GameMain.Server?.KarmaManager?.Save(); } } catch (Exception e) { //couldn't save, whatever } int existingFiles = 0; string originalFilePath = filePath; while (File.Exists(filePath)) { existingFiles++; filePath = Path.GetFileNameWithoutExtension(originalFilePath) + " (" + (existingFiles + 1) + ")" + Path.GetExtension(originalFilePath); } StringBuilder sb = new StringBuilder(); sb.AppendLine("Barotrauma Dedicated Server crash report (generated on " + DateTime.Now + ")"); sb.AppendLine("\n"); sb.AppendLine("Barotrauma seems to have crashed. Sorry for the inconvenience! "); sb.AppendLine("\n"); sb.AppendLine("Game version " + GameMain.Version + " (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")"); sb.AppendLine("Language: " + GameSettings.CurrentConfig.Language); if (ContentPackageManager.EnabledPackages.All != null) { sb.AppendLine("Selected content packages: " + (!ContentPackageManager.EnabledPackages.All.Any() ? "None" : string.Join(", ", ContentPackageManager.EnabledPackages.All.Select(c => $"{c.Name} ({c.Hash?.ShortRepresentation ?? "unknown"})")))); } sb.AppendLine("Level seed: " + ((Level.Loaded == null) ? "no level loaded" : Level.Loaded.Seed)); sb.AppendLine("Loaded submarine: " + ((Submarine.MainSub == null) ? "None" : Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash + ")")); sb.AppendLine("Selected screen: " + (Screen.Selected == null ? "None" : Screen.Selected.ToString())); if (GameMain.Server != null) { sb.AppendLine("Server (" + (GameMain.Server.GameStarted ? "Round had started)" : "Round hadn't been started)")); } sb.AppendLine("\n"); sb.AppendLine("System info:"); sb.AppendLine(" Operating system: " + System.Environment.OSVersion + (System.Environment.Is64BitOperatingSystem ? " 64 bit" : " x86")); sb.AppendLine("\n"); sb.AppendLine("Exception: " + exception.Message + " (" + exception.GetType().ToString() + ")"); sb.AppendLine("Target site: " +exception.TargetSite.ToString()); if (exception.StackTrace != null) { sb.AppendLine("Stack trace: "); sb.AppendLine(exception.StackTrace.CleanupStackTrace()); sb.AppendLine("\n"); } if (exception.InnerException != null) { sb.AppendLine("InnerException: " + exception.InnerException.Message); if (exception.InnerException.TargetSite != null) { sb.AppendLine("Target site: " + exception.InnerException.TargetSite.ToString()); } if (exception.InnerException.StackTrace != null) { sb.AppendLine("Stack trace: "); sb.AppendLine(exception.InnerException.StackTrace.CleanupStackTrace()); } } if (GameAnalyticsManager.SendUserStatistics) { //send crash report before appending debug console messages (which may contain non-anonymous information) GameAnalyticsManager.AddErrorEvent(GameAnalyticsManager.ErrorSeverity.Critical, sb.ToString()); GameAnalyticsManager.ShutDown(); } sb.AppendLine("Last debug messages:"); DebugConsole.Clear(); for (int i = DebugConsole.Messages.Count - 1; i > 0 && i > DebugConsole.Messages.Count - 15; i--) { sb.AppendLine(" " + DebugConsole.Messages[i].Time + " - " + DebugConsole.Messages[i].Text); } string crashReport = sb.ToString(); if (!Console.IsOutputRedirected) { Console.ForegroundColor = ConsoleColor.Red; } Console.Write(crashReport); File.WriteAllText(filePath, sb.ToString()); if (GameSettings.CurrentConfig.SaveDebugConsoleLogs || GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.SaveLogs(); } if (GameAnalyticsManager.SendUserStatistics) { Console.Write("A crash report (\"servercrashreport.log\") was saved in the root folder of the game and sent to the developers."); } else { Console.Write("A crash report(\"servercrashreport.log\") was saved in the root folder of the game. The error was not sent to the developers because user statistics have been disabled, but" + " if you'd like to help fix this bug, you may post it on Barotrauma's GitHub issue tracker: https://github.com/Regalis11/Barotrauma/issues/"); } SteamManager.ShutDown(); } } }