Client LuaCsForBarotrauma
Program.cs
1 #region Using Statements
2 
3 using System;
4 using Barotrauma.IO;
5 using System.Linq;
6 using System.Text;
7 using Barotrauma.Steam;
8 using System.Diagnostics;
9 using System.Runtime.InteropServices;
10 using System.Xml.Linq;
11 using Barotrauma.Debugging;
12 
13 #if WINDOWS
14 using SharpDX;
15 #endif
16 
17 #endregion
18 
19 namespace Barotrauma
20 {
24  public static class Program
25  {
26 
27 #if LINUX
31  [DllImport("linux_steam_env", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
32  private static extern void setLinuxEnv();
33 #endif
34 
38  [STAThread]
39  static void Main(string[] args)
40  {
41  string executableDir = "";
42 
43 #if !DEBUG
44  AppDomain currentDomain = AppDomain.CurrentDomain;
45  currentDomain.UnhandledException += new UnhandledExceptionEventHandler(CrashHandler);
46 #endif
47 
48 #if LINUX
49  setLinuxEnv();
50 #endif
51 
52  Game = null;
53  executableDir = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
54  Directory.SetCurrentDirectory(executableDir);
55  DebugConsoleCore.Init(
56  newMessage: (s, c) => DebugConsole.NewMessage(s, c),
57  log: DebugConsole.Log);
58  StoreIntegration.Init(ref args);
59  EnableNvOptimus();
60  Game = new GameMain(args);
61  Game.Run();
62  Game.Dispose();
63  FreeNvOptimus();
64 
65  CrossThread.ProcessTasks();
66  }
67 
68  private static GameMain Game;
69 
70  private static void CrashHandler(object sender, UnhandledExceptionEventArgs args)
71  {
72  Exception unhandledException = args.ExceptionObject as Exception;
73  try
74  {
75  Game?.Exit();
76  CrashDump(Game, "crashreport.log", unhandledException);
77  Game?.Dispose();
78  }
79  catch (Exception exceptionHandlerError)
80  {
81  Debug.WriteLine(exceptionHandlerError.Message);
82  string slimCrashReport = "Exception handler failed: " + exceptionHandlerError.Message + "\n" + exceptionHandlerError.StackTrace;
83  if (unhandledException != null)
84  {
85  slimCrashReport += "\n\nInitial exception: " + unhandledException.Message + "\n" + unhandledException.StackTrace;
86  }
87  File.WriteAllText("crashreportslim.log", slimCrashReport);
88  //exception handler is broken, we have a serious problem here!!
89  return;
90  }
91  }
92 
93  public static void CrashMessageBox(string message, string filePath)
94  {
95  Microsoft.Xna.Framework.MessageBox.ShowWrapped(Microsoft.Xna.Framework.MessageBox.Flags.Error, "Oops! Barotrauma just crashed.", message);
96 
97  // Open the crash log.
98  if (!string.IsNullOrWhiteSpace(filePath)) { ToolBox.OpenFileWithShell(filePath); }
99  }
100 
101  static void CrashDump(GameMain game, string filePath, Exception exception)
102  {
103  int existingFiles = 0;
104  string originalFilePath = filePath;
105  while (File.Exists(filePath))
106  {
107  existingFiles++;
108  filePath = Path.GetFileNameWithoutExtension(originalFilePath) + " (" + (existingFiles + 1) + ")" + Path.GetExtension(originalFilePath);
109  }
110 
111  DebugConsole.DequeueMessages();
112 
113  Md5Hash exeHash = null;
114  try
115  {
116  string exePath = System.Reflection.Assembly.GetEntryAssembly().Location;
117  exeHash = Md5Hash.CalculateForFile(exePath, Md5Hash.StringHashOptions.BytePerfect);
118  }
119  catch
120  {
121  //do nothing, generate the rest of the crash report
122  }
123 
124  StringBuilder sb = new StringBuilder();
125 
126  sb.AppendLine("Barotrauma Client crash report (generated on " + DateTime.Now + ")");
127  sb.AppendLine();
128  sb.AppendLine("Barotrauma seems to have crashed. Sorry for the inconvenience! ");
129  sb.AppendLine();
130 
131  string dxgiErrorHelpText =
132 #if WINDOWS
133  GetDXGIErrorHelpText(game, exception);
134 #else
135  string.Empty;
136 #endif
137  if (!string.IsNullOrEmpty(dxgiErrorHelpText))
138  {
139  sb.AppendLine(dxgiErrorHelpText);
140  sb.AppendLine();
141  }
142 
143  try
144  {
145  if (exception.StackTrace.Contains("Barotrauma.GameMain.Load"))
146  {
147  //exception occurred in loading screen:
148  //assume content packages are the culprit and reset them
149  XDocument doc = XMLExtensions.TryLoadXml(GameSettings.PlayerConfigPath);
150  if (doc?.Root != null)
151  {
152  XElement newElement = new XElement(doc.Root.Name);
153  newElement.Add(doc.Root.Attributes());
154  Identifier[] contentPackageTags = { "contentpackage".ToIdentifier(), "contentpackages".ToIdentifier() };
155  newElement.Add(doc.Root.Elements().Where(e => !contentPackageTags.Contains(e.NameAsIdentifier())));
156  newElement.Add(new XElement("core",
157  new XAttribute("path", ContentPackageManager.VanillaFileList)));
158  XDocument newDoc = new XDocument(newElement);
159  newDoc.Save(GameSettings.PlayerConfigPath);
160  sb.AppendLine("To prevent further startup errors, installed mods will be disabled the next time you launch the game.");
161  sb.AppendLine();
162  }
163  }
164  }
165  catch
166  {
167  //welp i guess we couldn't reset the config!
168  }
169 
170  if (exeHash?.StringRepresentation != null)
171  {
172  sb.AppendLine(exeHash.StringRepresentation);
173  }
174  sb.AppendLine();
175  sb.AppendLine("Game version " + GameMain.Version +
176  " (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")");
177  sb.AppendLine($"Graphics mode: {GameSettings.CurrentConfig.Graphics.Width}x{GameSettings.CurrentConfig.Graphics.Height} ({GameSettings.CurrentConfig.Graphics.DisplayMode})");
178  sb.AppendLine("VSync " + (GameSettings.CurrentConfig.Graphics.VSync ? "ON" : "OFF"));
179  sb.AppendLine("Language: " + GameSettings.CurrentConfig.Language);
180  if (ContentPackageManager.EnabledPackages.All != null)
181  {
182  sb.AppendLine("Selected content packages: " +
183  (!ContentPackageManager.EnabledPackages.All.Any() ?
184  "None" :
185  string.Join(", ", ContentPackageManager.EnabledPackages.All.Select(c => $"{c.Name} ({c.Hash?.ShortRepresentation ?? "unknown"})"))));
186  }
187  sb.AppendLine("Level seed: " + ((Level.Loaded == null) ? "no level loaded" : Level.Loaded.Seed));
188  sb.AppendLine("Loaded submarine: " + ((Submarine.MainSub == null) ? "None" : Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash + ")"));
189  sb.AppendLine("Selected screen: " + (Screen.Selected == null ? "None" : Screen.Selected.ToString()));
190  if (SteamManager.IsInitialized)
191  {
192  sb.AppendLine("SteamManager initialized");
193  }
194  else if (EosInterface.IdQueries.IsLoggedIntoEosConnect)
195  {
196  sb.AppendLine("Logged in to EOS connect");
197  }
198 
199  if (GameMain.Client != null)
200  {
201  sb.AppendLine("Client (" + (GameMain.Client.GameStarted ? "Round had started)" : "Round hadn't been started)"));
202  }
203 
204  sb.AppendLine();
205  sb.AppendLine("System info:");
206  sb.AppendLine(" Operating system: " + System.Environment.OSVersion + (System.Environment.Is64BitOperatingSystem ? " 64 bit" : " x86"));
207 
208  if (game == null)
209  {
210  sb.AppendLine(" Game not initialized");
211  }
212  else
213  {
214  if (game.GraphicsDevice == null)
215  {
216  sb.AppendLine(" Graphics device not set");
217  }
218  else
219  {
220  if (game.GraphicsDevice.Adapter == null)
221  {
222  sb.AppendLine(" Graphics adapter not set");
223  }
224  else
225  {
226  sb.AppendLine(" GPU name: " + game.GraphicsDevice.Adapter.Description);
227  sb.AppendLine(" Display mode: " + game.GraphicsDevice.Adapter.CurrentDisplayMode);
228  }
229 
230  sb.AppendLine(" GPU status: " + game.GraphicsDevice.GraphicsDeviceStatus);
231  }
232  }
233 
234  sb.AppendLine();
235  sb.AppendLine($"Exception: {exception.Message} ({exception.GetType()})");
236 #if WINDOWS
237  if (exception is SharpDXException sharpDxException && ((uint)sharpDxException.HResult) == 0x887A0005)
238  {
239  var dxDevice = (SharpDX.Direct3D11.Device)game.GraphicsDevice.Handle;
240  var descriptor = ResultDescriptor.Find(dxDevice.DeviceRemovedReason)?.ApiCode ?? "UNKNOWN";
241  sb.AppendLine($"Device removed reason: {descriptor} ({dxDevice.DeviceRemovedReason})");
242  }
243 #endif
244  if (exception.TargetSite != null)
245  {
246  sb.AppendLine("Target site: " + exception.TargetSite.ToString());
247  }
248 
249  if (exception.StackTrace != null)
250  {
251  sb.AppendLine("Stack trace: ");
252  sb.AppendLine(exception.StackTrace.CleanupStackTrace());
253  sb.AppendLine();
254  }
255 
256  if (exception.InnerException != null)
257  {
258  sb.AppendLine("InnerException: " + exception.InnerException.Message);
259  if (exception.InnerException.TargetSite != null)
260  {
261  sb.AppendLine("Target site: " + exception.InnerException.TargetSite.ToString());
262  }
263  if (exception.InnerException.StackTrace != null)
264  {
265  sb.AppendLine("Stack trace: ");
266  sb.AppendLine(exception.InnerException.StackTrace.CleanupStackTrace());
267  }
268  }
269 
270  if (GameAnalyticsManager.SendUserStatistics)
271  {
272  //send crash report before appending debug console messages (which may contain non-anonymous information)
273  string crashHeader = exception.Message;
274  if (exception.TargetSite != null)
275  {
276  crashHeader += " " + exception.TargetSite.ToString();
277  }
278  //log the message separately, so the same error messages get grouped as the same error in GA
279  //(the full crash report tends to always have some differences between clients, so they get displayed separately)
280  GameAnalyticsManager.AddErrorEvent(GameAnalyticsManager.ErrorSeverity.Critical, crashHeader);
281  GameAnalyticsManager.AddErrorEvent(GameAnalyticsManager.ErrorSeverity.Critical, crashHeader + "\n\n" + sb.ToString());
282  GameAnalyticsManager.ShutDown();
283  }
284 
285  sb.AppendLine("Last debug messages:");
286  for (int i = DebugConsole.Messages.Count - 1; i >= 0; i--)
287  {
288  sb.AppendLine("[" + DebugConsole.Messages[i].Time + "] " + DebugConsole.Messages[i].Text);
289  }
290 
291  string crashReport = sb.ToString();
292 
293  File.WriteAllText(filePath, crashReport);
294 
295  if (GameSettings.CurrentConfig.SaveDebugConsoleLogs
296  || GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.SaveLogs(); }
297 
298  string msg = string.Empty;
299  if (GameAnalyticsManager.SendUserStatistics)
300  {
301  msg = "A crash report (\"" + filePath + "\") was saved in the root folder of the game and sent to the developers.";
302  }
303  else
304  {
305  msg = "A crash report (\"" + filePath + "\") was saved in the root folder of the game. The error was not sent to the developers because user statistics have been disabled, but" +
306  " 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/";
307  }
308  if (string.IsNullOrEmpty(dxgiErrorHelpText))
309  {
310  msg += "\n\n" + dxgiErrorHelpText;
311  }
312  CrashMessageBox(msg, filePath);
313  }
314 
315 #if WINDOWS
316  private static string GetDXGIErrorHelpText(GameMain game, Exception exception)
317  {
318  string text = string.Empty;
319  if (exception is SharpDXException sharpDxException && ((uint)sharpDxException.HResult) == 0x887A0005)
320  {
321  var dxDevice = (SharpDX.Direct3D11.Device)game.GraphicsDevice.Handle;
322  var descriptor = ResultDescriptor.Find(dxDevice.DeviceRemovedReason)?.ApiCode ?? "UNKNOWN";
323 
324  text +=
325  $"The crash was caused by the DirectX error {descriptor} ({dxDevice.DeviceRemovedReason}). " +
326  "This is a common DirectX error that can be related to various different issues, such as outdated drivers, RAM problems or an overclocked or otherwise overstressed GPU. " +
327  "There are several potential ways to fix the issue: ensuring your graphics drivers and DirectX installation are up-to-date, disabling overclocking and adjusting various GPU-specific settings. " +
328  $"You may also be able to find potential solutions to the problem by using the error code {descriptor} ({dxDevice.DeviceRemovedReason}) and your GPU manufacturer as search terms.";
329  }
330  return text;
331  }
332 #endif
333 
334  private static IntPtr nvApi64Dll = IntPtr.Zero;
335  private static void EnableNvOptimus()
336  {
337 #if WINDOWS && X64
338  // We force load nvapi64.dll so nvidia gives us the dedicated GPU on optimus laptops.
339  // This is not a method for getting optimus that is documented by nvidia, but it works, so...
340  if (NativeLibrary.TryLoad("nvapi64.dll", out nvApi64Dll))
341  {
342  DebugConsole.Log("Loaded nvapi64.dll successfully");
343  }
344 #endif
345  }
346 
347  private static void FreeNvOptimus()
348  {
349 #warning TODO: determine if we can do this safely
350  //NativeLibrary.Free(nvApi64Dll);
351  }
352 
353  }
354 }