Decompiled source of CrashWatcher v1.0.0

plugins/CatLib.CrashWatcher.exe

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Eventing.Reader;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Xml;
using CatLib.Diagnostics;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("CatLib contributors")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+4162013e7c932bfa1cf0340558d83186f1fd5817")]
[assembly: AssemblyProduct("CatLib")]
[assembly: AssemblyTitle("CatLib.CrashWatcher")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace CatLib.Diagnostics
{
	internal sealed class CrashSession
	{
		public const int CurrentFormat = 1;

		public const string FormatKey = "format";

		public const string CatLibKey = "catlib";

		public const string GameKey = "game";

		public const string GameBuildKey = "gameBuild";

		public const string StartedKey = "started";

		public const string ProcessKey = "pid";

		public const string LanguageKey = "language";

		public const string PlayerLogKey = "playerLog";

		public const string BepInExLogKey = "bepinexLog";

		public const string ReportsKey = "reports";

		public const string ModKey = "mod";

		public const string EventKey = "event";

		public const string CleanKey = "clean";

		public const string MainThreadKey = "mainThread";

		public const string ExceptionKey = "exception";

		public const string TextPrefix = "text.";

		public int Format { get; set; }

		public bool IsNewerFormat => Format > 1;

		public string WatcherVersion { get; set; } = string.Empty;

		public string CatLibVersion { get; set; } = string.Empty;

		public string GameVersion { get; set; } = string.Empty;

		public string GameBuild { get; set; } = string.Empty;

		public string Started { get; set; } = string.Empty;

		public int ProcessId { get; set; }

		public string Language { get; set; } = "en";

		public string PlayerLog { get; set; } = string.Empty;

		public string BepInExLog { get; set; } = string.Empty;

		public string ReportsDirectory { get; set; } = string.Empty;

		public List<string> Mods { get; } = new List<string>();

		public List<string> Events { get; } = new List<string>();

		public string CleanExit { get; set; }

		public int MainThreadId { get; set; }

		public List<string> ExceptionLines { get; } = new List<string>();

		public Dictionary<string, string> Texts { get; } = new Dictionary<string, string>(StringComparer.Ordinal);

		public CrashStrings Strings => new CrashStrings(Texts);

		public static string Line(string key, string value)
		{
			return key + "=" + Flatten(value);
		}

		public IEnumerable<string> HeaderLines()
		{
			yield return Line("format", Format.ToString(CultureInfo.InvariantCulture));
			yield return Line("catlib", CatLibVersion);
			yield return Line("game", GameVersion);
			yield return Line("started", Started);
			yield return Line("pid", ProcessId.ToString(CultureInfo.InvariantCulture));
			yield return Line("language", Language);
			yield return Line("playerLog", PlayerLog);
			yield return Line("bepinexLog", BepInExLog);
			yield return Line("reports", ReportsDirectory);
			if (MainThreadId != 0)
			{
				yield return Line("mainThread", MainThreadId.ToString(CultureInfo.InvariantCulture));
			}
			foreach (string mod in Mods)
			{
				yield return Line("mod", mod);
			}
		}

		public static CrashSession Parse(IEnumerable<string> lines)
		{
			CrashSession crashSession = new CrashSession();
			foreach (string line in lines)
			{
				int num = line?.IndexOf('=') ?? (-1);
				if (num <= 0)
				{
					continue;
				}
				string text = line.Substring(0, num);
				string text2 = line.Substring(num + 1);
				if (text.StartsWith("text.", StringComparison.Ordinal))
				{
					crashSession.Texts[text.Substring("text.".Length)] = text2;
					continue;
				}
				switch (text)
				{
				case "format":
				{
					crashSession.Format = (int.TryParse(text2, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result3) ? result3 : 0);
					break;
				}
				case "catlib":
					crashSession.CatLibVersion = text2;
					break;
				case "game":
					crashSession.GameVersion = text2;
					break;
				case "gameBuild":
					crashSession.GameBuild = text2;
					break;
				case "started":
					crashSession.Started = text2;
					break;
				case "pid":
				{
					crashSession.ProcessId = (int.TryParse(text2, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) ? result2 : 0);
					break;
				}
				case "language":
					crashSession.Language = text2;
					break;
				case "playerLog":
					crashSession.PlayerLog = text2;
					break;
				case "bepinexLog":
					crashSession.BepInExLog = text2;
					break;
				case "reports":
					crashSession.ReportsDirectory = text2;
					break;
				case "mod":
					crashSession.Mods.Add(text2);
					break;
				case "event":
					crashSession.Events.Add(text2);
					break;
				case "clean":
					crashSession.CleanExit = text2;
					break;
				case "exception":
					crashSession.ExceptionLines.Add(text2);
					break;
				case "mainThread":
				{
					crashSession.MainThreadId = (int.TryParse(text2, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) ? result : 0);
					break;
				}
				}
			}
			return crashSession;
		}

		private static string Flatten(string value)
		{
			return (value ?? string.Empty).Replace('\r', ' ').Replace('\n', ' ');
		}
	}
	internal sealed class CrashEventInfo
	{
		public string Module { get; set; } = string.Empty;

		public string Offset { get; set; } = string.Empty;

		public string ExceptionCode { get; set; } = string.Empty;

		public string ModulePath { get; set; } = string.Empty;

		public string RuntimeMessage { get; set; } = string.Empty;

		public int ThreadId { get; set; }

		public bool FromDebugger { get; set; }

		public bool Early { get; set; }

		public string Access { get; set; } = string.Empty;

		public int HangSeconds { get; set; }

		public bool Hung => HangSeconds > 0;

		public bool HasFault => Module.Length > 0;
	}
	internal sealed class CrashModule
	{
		public string Path { get; }

		public string Name => Path.Substring(Path.LastIndexOfAny(new char[2] { '\\', '/' }) + 1);

		public ulong Start { get; }

		public ulong Size { get; }

		public CrashModule(string path, ulong start, ulong size)
		{
			Path = path ?? string.Empty;
			Start = start;
			Size = size;
		}
	}
	internal static class CrashText
	{
		public const int LogTailLines = 40;

		public const int EventTailLines = 15;

		public const int DetailEventLines = 6;

		public const int HeadlineLength = 200;

		public const string ApplicationErrorProvider = "Application Error";

		public const string RuntimeProvider = ".NET Runtime";

		public const string ExecuteAccess = "execute";

		public static bool IsNormalExit(uint exitCode)
		{
			return exitCode == 0;
		}

		public static string AccessText(ulong kind, ulong address)
		{
			return kind switch
			{
				8uL => "execute", 
				1uL => "write", 
				0uL => "read", 
				_ => "access " + kind.ToString(CultureInfo.InvariantCulture), 
			} + " at " + Hex64(address);
		}

		public static string ExitKey(uint exitCode)
		{
			switch (exitCode)
			{
			case 1u:
				if (exitCode != 1)
				{
					break;
				}
				return "exit.closedOutside";
			case 0u:
				return "exit.normal";
			case 3221225477u:
				return "exit.accessViolation";
			case 3221226356u:
				return "exit.heapCorruption";
			case 3221226505u:
				return "exit.failFast";
			case 3221225725u:
				return "exit.stackOverflow";
			case 3221225501u:
				return "exit.illegalInstruction";
			case 3221225620u:
				return "exit.divideByZero";
			case 3221225786u:
				return "exit.consoleClosed";
			case 3762504530u:
				return "exit.dotnetException";
			case 2148734499u:
				return "exit.dotnetFailFast";
			}
			return "exit.unknown";
		}

		public static string DescribeExitCode(uint exitCode, CrashStrings strings)
		{
			return (strings ?? CrashStrings.EnglishOnly).Get(ExitKey(exitCode));
		}

		public static string Hex(uint value)
		{
			return "0x" + value.ToString("X8", CultureInfo.InvariantCulture);
		}

		public static string Hex64(ulong value)
		{
			return "0x" + value.ToString("x16", CultureInfo.InvariantCulture);
		}

		public static CrashEventInfo Locate(IList<CrashModule> modules, ulong address, uint exceptionCode, int threadId)
		{
			CrashEventInfo crashEventInfo = new CrashEventInfo
			{
				ExceptionCode = "0x" + exceptionCode.ToString("x8", CultureInfo.InvariantCulture),
				ThreadId = threadId,
				FromDebugger = true
			};
			foreach (CrashModule item in modules ?? new List<CrashModule>())
			{
				if (address >= item.Start && address - item.Start < item.Size)
				{
					crashEventInfo.Module = item.Name;
					crashEventInfo.ModulePath = item.Path;
					crashEventInfo.Offset = Hex64(address - item.Start);
					return crashEventInfo;
				}
			}
			crashEventInfo.Module = "?";
			crashEventInfo.Offset = Hex64(address);
			return crashEventInfo;
		}

		public static bool IsManaged(CrashEventInfo info)
		{
			if (info.Module == "?")
			{
				return !info.Access.StartsWith("execute", StringComparison.Ordinal);
			}
			string text = info.ModulePath.Replace('/', '\\');
			if (!string.Equals(info.Module, "coreclr.dll", StringComparison.OrdinalIgnoreCase) && !string.Equals(info.Module, "clrjit.dll", StringComparison.OrdinalIgnoreCase) && text.IndexOf("\\dotnet\\", StringComparison.OrdinalIgnoreCase) < 0)
			{
				return text.IndexOf("\\BepInEx\\", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return true;
		}

		public static CrashEventInfo WithManagedException(CrashEventInfo info, CrashSession session)
		{
			if (info != null && info.RuntimeMessage.Length == 0 && session != null && session.ExceptionLines.Count > 0)
			{
				info.RuntimeMessage = string.Join("\n", session.ExceptionLines);
			}
			return info;
		}

		public static CrashEventInfo Merge(CrashEventInfo fromDebugger, CrashEventInfo fromEvents)
		{
			if (fromDebugger == null)
			{
				return fromEvents ?? new CrashEventInfo();
			}
			if (fromEvents != null && fromDebugger.RuntimeMessage.Length == 0)
			{
				fromDebugger.RuntimeMessage = fromEvents.RuntimeMessage;
			}
			return fromDebugger;
		}

		public static CrashEventInfo ParseEvents(string xml, int processId, DateTime exitTimeUtc)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Expected O, but got Unknown
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Expected O, but got Unknown
			//IL_00a8: Expected O, but got Unknown
			CrashEventInfo crashEventInfo = new CrashEventInfo();
			if (string.IsNullOrWhiteSpace(xml))
			{
				return crashEventInfo;
			}
			XmlDocument val = new XmlDocument();
			try
			{
				val.LoadXml("<Events>" + xml + "</Events>");
			}
			catch (XmlException)
			{
				return crashEventInfo;
			}
			string text = processId.ToString("x", CultureInfo.InvariantCulture);
			foreach (XmlNode childNode in ((XmlNode)val.DocumentElement).ChildNodes)
			{
				string text2 = Attribute(Find(childNode, "System", "Provider"), "Name");
				string systemTime = Attribute(Find(childNode, "System", "TimeCreated"), "SystemTime");
				List<string> list = DataValues(childNode);
				if (text2 == "Application Error" && !crashEventInfo.HasFault && list.Count >= 9)
				{
					string text3 = list[8].Trim().ToLowerInvariant();
					if (text3.StartsWith("0x", StringComparison.Ordinal))
					{
						text3 = text3.Substring(2);
					}
					if (!(text3.TrimStart(new char[1] { '0' }) != text.TrimStart(new char[1] { '0' })))
					{
						crashEventInfo.Module = list[3];
						crashEventInfo.ExceptionCode = Prefixed(list[6]);
						crashEventInfo.Offset = Prefixed(list[7]);
						crashEventInfo.ModulePath = ((list.Count > 11) ? list[11] : string.Empty);
					}
				}
				else if (text2 == ".NET Runtime" && crashEventInfo.RuntimeMessage.Length == 0 && list.Count > 0 && IsNear(systemTime, exitTimeUtc))
				{
					crashEventInfo.RuntimeMessage = list[0].Trim();
				}
			}
			return crashEventInfo;
		}

		public static List<string> Tail(IList<string> lines, int count)
		{
			List<string> list = new List<string>();
			if (lines == null)
			{
				return list;
			}
			for (int i = Math.Max(0, lines.Count - count); i < lines.Count; i++)
			{
				list.Add(lines[i]);
			}
			return list;
		}

		public static string Summary(CrashSession session, uint exitCode, CrashEventInfo info, TimeSpan played, CrashStrings strings, bool dumpSaved = false)
		{
			strings = strings ?? CrashStrings.EnglishOnly;
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(strings.Format("summary", DescribeExitCode(exitCode, strings), Hex(exitCode)));
			if (info.HasFault)
			{
				stringBuilder.Append(' ').Append(strings.Format("where", info.Module + " + " + info.Offset));
			}
			if (played > TimeSpan.Zero)
			{
				stringBuilder.Append(' ').Append(strings.Format("ran", Duration(played, strings)));
			}
			if (info.Hung)
			{
				stringBuilder.Append(' ').Append(strings.Format("hang", Duration(TimeSpan.FromSeconds(info.HangSeconds), strings)));
			}
			else if (!string.IsNullOrEmpty(session.CleanExit))
			{
				stringBuilder.Append(' ').Append(strings.Get("quitting"));
			}
			if (dumpSaved)
			{
				stringBuilder.Append(' ').Append(strings.Get("dumpSaved"));
			}
			return stringBuilder.ToString();
		}

		public static string ThreadText(CrashSession session, CrashEventInfo info, CrashStrings strings)
		{
			if (info.ThreadId == 0)
			{
				return string.Empty;
			}
			string text = info.ThreadId.ToString(CultureInfo.InvariantCulture);
			if (session.MainThreadId == 0)
			{
				return text;
			}
			return text + ", " + strings.Get((info.ThreadId == session.MainThreadId) ? "gameThread" : "otherThread");
		}

		public static string Report(CrashSession session, uint exitCode, CrashEventInfo info, DateTime exitTime, TimeSpan played, IList<string> logTail, string dumpPath = null)
		{
			CrashStrings englishOnly = CrashStrings.EnglishOnly;
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine(Summary(session, exitCode, info, played, englishOnly));
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("Time: " + exitTime.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture));
			stringBuilder.AppendLine("Exit code: " + Hex(exitCode) + ", " + DescribeExitCode(exitCode, englishOnly));
			if (info.HasFault)
			{
				string text = ((!info.FromDebugger) ? ", from the Windows event log" : (info.Early ? ", seen by the crash watcher when it was raised, the game closed right after" : ", seen by the crash watcher"));
				stringBuilder.AppendLine("Module: " + info.Module + " + " + info.Offset + ", exception " + info.ExceptionCode + text);
				if (info.Access.Length > 0)
				{
					stringBuilder.AppendLine("Access: " + info.Access);
				}
				if (info.ModulePath.Length > 0)
				{
					stringBuilder.AppendLine("Module path: " + info.ModulePath);
				}
				if (info.ThreadId != 0)
				{
					stringBuilder.AppendLine("Thread: " + ThreadText(session, info, englishOnly));
				}
			}
			else if (!info.Hung)
			{
				stringBuilder.AppendLine("No crash record: the crash watcher saw no exception and the Windows event log has none for this process.");
			}
			if (info.Hung)
			{
				stringBuilder.AppendLine("Hang: the game began to quit at " + session.CleanExit + " and was still running " + info.HangSeconds.ToString(CultureInfo.InvariantCulture) + " s later" + (string.IsNullOrEmpty(dumpPath) ? string.Empty : "; the memory dump shows every thread at the moment it hung"));
			}
			stringBuilder.AppendLine("Game: " + session.GameVersion + ", CatLib " + session.CatLibVersion + (string.IsNullOrEmpty(session.WatcherVersion) ? string.Empty : (", crash watcher " + session.WatcherVersion)) + ", started " + session.Started + ", language " + session.Language);
			if (session.IsNewerFormat)
			{
				stringBuilder.AppendLine("Session format " + session.Format.ToString(CultureInfo.InvariantCulture) + " is newer than this crash watcher reads (" + 1.ToString(CultureInfo.InvariantCulture) + "), some details may be missing: update CatLib Crash Watcher");
			}
			if (!string.IsNullOrEmpty(session.GameBuild))
			{
				stringBuilder.AppendLine("Game build check: " + session.GameBuild);
			}
			if (!string.IsNullOrEmpty(dumpPath))
			{
				stringBuilder.AppendLine("Memory dump: " + dumpPath);
			}
			if (info.RuntimeMessage.Length > 0)
			{
				stringBuilder.AppendLine();
				stringBuilder.AppendLine(".NET message:");
				foreach (string item in SplitLines(info.RuntimeMessage))
				{
					stringBuilder.AppendLine("  " + item);
				}
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("Mods:");
			foreach (string mod in session.Mods)
			{
				stringBuilder.AppendLine("  " + mod);
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("Last game events:");
			foreach (string item2 in Tail(session.Events, 15))
			{
				stringBuilder.AppendLine("  " + item2);
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("End of the BepInEx log:");
			foreach (string item3 in logTail ?? new List<string>())
			{
				stringBuilder.AppendLine("  " + item3);
			}
			return stringBuilder.ToString();
		}

		public static string Details(CrashSession session, uint exitCode, CrashEventInfo info, DateTime exitTime, CrashStrings strings)
		{
			strings = strings ?? CrashStrings.EnglishOnly;
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine(strings.Get("detailTime") + ": " + exitTime.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture));
			stringBuilder.AppendLine(strings.Get("detailExitCode") + ": " + Hex(exitCode));
			if (info.HasFault)
			{
				stringBuilder.AppendLine(strings.Get("detailModule") + ": " + info.Module + " + " + info.Offset + ", " + strings.Get("detailException") + " " + info.ExceptionCode);
				string text = ThreadText(session, info, strings);
				if (text.Length > 0)
				{
					stringBuilder.AppendLine(strings.Get("detailThread") + ": " + text);
				}
			}
			string text2 = RuntimeHeadline(info.RuntimeMessage);
			if (text2.Length > 0)
			{
				stringBuilder.AppendLine(".NET: " + text2);
			}
			stringBuilder.AppendLine(strings.Get("detailGame") + " " + session.GameVersion + ", CatLib " + session.CatLibVersion);
			if (session.Mods.Count > 0)
			{
				stringBuilder.AppendLine(strings.Get("detailMods") + ": " + string.Join(", ", ModNames(session.Mods)));
			}
			List<string> list = Tail(session.Events, 6);
			if (list.Count > 0)
			{
				stringBuilder.AppendLine(strings.Get("detailEvents") + ":");
				foreach (string item in list)
				{
					stringBuilder.AppendLine("  " + item);
				}
			}
			return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
		}

		public static string RuntimeHeadline(string message)
		{
			if (string.IsNullOrWhiteSpace(message))
			{
				return string.Empty;
			}
			List<string> list = SplitLines(message);
			foreach (string item in list)
			{
				if (item.StartsWith("Exception Info:", StringComparison.OrdinalIgnoreCase))
				{
					return Shorten(item.Substring("Exception Info:".Length).Trim(), 200);
				}
			}
			foreach (string item2 in list)
			{
				if (item2.StartsWith("Description:", StringComparison.OrdinalIgnoreCase))
				{
					return Shorten(item2.Substring("Description:".Length).Trim(), 200);
				}
			}
			return Shorten((list.Count == 0) ? string.Empty : list[0], 200);
		}

		public static List<string> ModNames(IEnumerable<string> mods)
		{
			List<string> list = new List<string>();
			foreach (string mod in mods)
			{
				int num = mod.LastIndexOf(" (", StringComparison.Ordinal);
				list.Add((num > 0) ? mod.Substring(0, num) : mod);
			}
			return list;
		}

		public static List<string> SplitLines(string text)
		{
			List<string> list = new List<string>();
			string[] array = (text ?? string.Empty).Replace("\r\n", "\n").Replace('\r', '\n').Split(new char[1] { '\n' });
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].TrimEnd(Array.Empty<char>());
				if (text2.Length > 0)
				{
					list.Add(text2);
				}
			}
			return list;
		}

		public static string Shorten(string text, int length)
		{
			if (text != null && text.Length > length)
			{
				return text.Substring(0, length - 3) + "...";
			}
			return text ?? string.Empty;
		}

		public static string Duration(TimeSpan span, CrashStrings strings)
		{
			strings = strings ?? CrashStrings.EnglishOnly;
			if (span.TotalHours >= 1.0)
			{
				return strings.Format("hoursMinutes", ((int)span.TotalHours).ToString(CultureInfo.InvariantCulture), span.Minutes.ToString(CultureInfo.InvariantCulture));
			}
			if (span.TotalMinutes >= 1.0)
			{
				return strings.Format("minutes", ((int)span.TotalMinutes).ToString(CultureInfo.InvariantCulture));
			}
			return strings.Format("seconds", ((int)span.TotalSeconds).ToString(CultureInfo.InvariantCulture));
		}

		private static string Prefixed(string value)
		{
			string text = (value ?? string.Empty).Trim();
			if (!text.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
			{
				return "0x" + text;
			}
			return text;
		}

		private static bool IsNear(string systemTime, DateTime exitTimeUtc)
		{
			if (!DateTime.TryParse(systemTime, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var result))
			{
				return false;
			}
			return Math.Abs((result - exitTimeUtc).TotalSeconds) <= 120.0;
		}

		private static XmlNode Find(XmlNode node, string first, string second)
		{
			XmlNode val = Child(node, first);
			if (val != null)
			{
				return Child(val, second);
			}
			return null;
		}

		private static XmlNode Child(XmlNode node, string name)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			foreach (XmlNode childNode in node.ChildNodes)
			{
				XmlNode val = childNode;
				if (val.LocalName == name)
				{
					return val;
				}
			}
			return null;
		}

		private static string Attribute(XmlNode node, string name)
		{
			object obj;
			if (node == null)
			{
				obj = null;
			}
			else
			{
				XmlAttributeCollection attributes = node.Attributes;
				if (attributes == null)
				{
					obj = null;
				}
				else
				{
					XmlAttribute obj2 = attributes[name];
					obj = ((obj2 != null) ? ((XmlNode)obj2).Value : null);
				}
			}
			if (obj == null)
			{
				obj = string.Empty;
			}
			return (string)obj;
		}

		private static List<string> DataValues(XmlNode eventNode)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			List<string> list = new List<string>();
			XmlNode val = Child(eventNode, "EventData");
			if (val == null)
			{
				return list;
			}
			foreach (XmlNode childNode in val.ChildNodes)
			{
				XmlNode val2 = childNode;
				if (val2.LocalName == "Data")
				{
					list.Add(val2.InnerText ?? string.Empty);
				}
			}
			return list;
		}
	}
	internal sealed class CrashStrings
	{
		public const string CatalogPrefix = "crash.";

		public const string PhrasePrefix = "phrase.";

		public const int PhraseCount = 14;

		public static readonly IReadOnlyDictionary<string, string> English = new Dictionary<string, string>(StringComparer.Ordinal)
		{
			["title"] = "Cat Mail Co: crash report",
			["openFolder"] = "Open the report folder",
			["copy"] = "Copy the report",
			["showDetails"] = "Details",
			["hideDetails"] = "Hide details",
			["footer"] = "The report and logs are saved in {0}",
			["help"] = "If this keeps happening, send the report folder to the mod authors.",
			["copied"] = "The report is copied",
			["summary"] = "The game closed unexpectedly: {0} ({1}).",
			["where"] = "Where: {0}.",
			["ran"] = "The game ran for {0}.",
			["quitting"] = "It happened while the game was quitting.",
			["hang"] = "The game stopped responding while quitting and was closed after {0}.",
			["dumpSaved"] = "A memory dump is saved with the report.",
			["hoursMinutes"] = "{0} h {1} min",
			["minutes"] = "{0} min",
			["seconds"] = "{0} s",
			["detailTime"] = "Time",
			["detailExitCode"] = "Exit code",
			["detailModule"] = "Module",
			["detailException"] = "exception",
			["detailThread"] = "Thread",
			["gameThread"] = "the game thread",
			["otherThread"] = "another thread",
			["detailGame"] = "Game",
			["detailMods"] = "Mods",
			["detailEvents"] = "Last events",
			["exit.normal"] = "normal exit",
			["exit.closedOutside"] = "the game was closed from outside, for example from the Task Manager",
			["exit.accessViolation"] = "access to an invalid memory address (access violation)",
			["exit.heapCorruption"] = "the memory heap is damaged (heap corruption)",
			["exit.failFast"] = "fail fast or a stack buffer overrun",
			["exit.stackOverflow"] = "stack overflow",
			["exit.illegalInstruction"] = "illegal instruction",
			["exit.divideByZero"] = "integer division by zero",
			["exit.consoleClosed"] = "the game was closed through its console window",
			["exit.dotnetException"] = "an unhandled .NET exception",
			["exit.dotnetFailFast"] = ".NET fail fast",
			["exit.unknown"] = "unknown reason",
			["phrase.1"] = "Your game got meowed :(",
			["phrase.2"] = "Cats don't like water, and the game didn't like this",
			["phrase.3"] = "Someone knocked the game off the table",
			["phrase.4"] = "A parcel fell off the top shelf",
			["phrase.5"] = "A cat sat on the keyboard",
			["phrase.6"] = "The game curled up and fell asleep",
			["phrase.7"] = "The boat sailed off without the game",
			["phrase.8"] = "Nine lives, and one of them is gone",
			["phrase.9"] = "The yarn got all tangled up",
			["phrase.10"] = "Hiss! Something went wrong",
			["phrase.11"] = "The game chased a laser dot and got lost",
			["phrase.12"] = "Oops, the mail got wet",
			["phrase.13"] = "The game is hiding under the sofa",
			["phrase.14"] = "Somebody stepped on a tail"
		};

		private readonly IDictionary<string, string> _texts;

		public static CrashStrings EnglishOnly { get; } = new CrashStrings(null);

		public static IEnumerable<string> Keys => English.Keys;

		public CrashStrings(IDictionary<string, string> texts)
		{
			_texts = texts ?? new Dictionary<string, string>(StringComparer.Ordinal);
		}

		public string Get(string key)
		{
			if (_texts.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value))
			{
				return value;
			}
			if (!English.TryGetValue(key, out var value2))
			{
				return key;
			}
			return value2;
		}

		public string Format(string key, params object[] arguments)
		{
			try
			{
				return string.Format(CultureInfo.InvariantCulture, Get(key), arguments);
			}
			catch (FormatException)
			{
				string value;
				return English.TryGetValue(key, out value) ? string.Format(CultureInfo.InvariantCulture, value, arguments) : key;
			}
		}

		public string Phrase(Random random)
		{
			return Get("phrase." + (random.Next(14) + 1).ToString(CultureInfo.InvariantCulture));
		}
	}
}
namespace CatLib.CrashWatcher
{
	internal static class ClipboardText
	{
		public const int Attempts = 10;

		public static bool Set(IntPtr owner, string text)
		{
			for (int i = 0; i < 10; i++)
			{
				if (NativeMethods.OpenClipboard(owner))
				{
					try
					{
						return Put(text ?? string.Empty);
					}
					finally
					{
						NativeMethods.CloseClipboard();
					}
				}
				Thread.Sleep(50);
			}
			return false;
		}

		private static bool Put(string text)
		{
			NativeMethods.EmptyClipboard();
			char[] array = text.ToCharArray();
			IntPtr intPtr = NativeMethods.GlobalAlloc(2u, new UIntPtr((uint)((array.Length + 1) * 2)));
			if (intPtr == IntPtr.Zero)
			{
				return false;
			}
			IntPtr intPtr2 = NativeMethods.GlobalLock(intPtr);
			if (intPtr2 == IntPtr.Zero)
			{
				NativeMethods.GlobalFree(intPtr);
				return false;
			}
			Marshal.Copy(array, 0, intPtr2, array.Length);
			Marshal.WriteInt16(intPtr2, array.Length * 2, 0);
			NativeMethods.GlobalUnlock(intPtr);
			if (NativeMethods.SetClipboardData(13u, intPtr) == IntPtr.Zero)
			{
				NativeMethods.GlobalFree(intPtr);
				return false;
			}
			return true;
		}
	}
	internal static class CrashEvents
	{
		public const string Query = "*[System[(EventID=1000 or EventID=1026) and TimeCreated[timediff(@SystemTime) <= 600000]]]";

		public const int MaxEvents = 30;

		public const int WaitMilliseconds = 6000;

		public const int PollMilliseconds = 500;

		public static CrashEventInfo Collect(int processId, DateTime exitTimeUtc, bool expectFault, WatcherLog log)
		{
			CrashEventInfo crashEventInfo = new CrashEventInfo();
			int num = 0;
			while (true)
			{
				try
				{
					crashEventInfo = CrashText.ParseEvents(Read(), processId, exitTimeUtc);
				}
				catch (Exception ex)
				{
					log.Write("Reading the Windows event log failed: " + ex.Message);
					return crashEventInfo;
				}
				if (crashEventInfo.HasFault || !expectFault || num >= 6000)
				{
					break;
				}
				Thread.Sleep(500);
				num += 500;
			}
			return crashEventInfo;
		}

		public static bool LooksLikeFault(uint exitCode)
		{
			if (exitCode < 3221225472u && exitCode != 3762504530u)
			{
				return exitCode == 2148734499u;
			}
			return true;
		}

		private static string Read()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Expected O, but got Unknown
			StringBuilder stringBuilder = new StringBuilder();
			EventLogReader val = new EventLogReader(new EventLogQuery("Application", (PathType)1, "*[System[(EventID=1000 or EventID=1026) and TimeCreated[timediff(@SystemTime) <= 600000]]]")
			{
				ReverseDirection = true
			});
			try
			{
				for (int i = 0; i < 30; i++)
				{
					EventRecord val2 = val.ReadEvent();
					try
					{
						if (val2 == null)
						{
							break;
						}
						stringBuilder.Append(val2.ToXml());
						continue;
					}
					finally
					{
						((IDisposable)val2)?.Dispose();
					}
				}
			}
			finally
			{
				((IDisposable)val)?.Dispose();
			}
			return stringBuilder.ToString();
		}
	}
	internal sealed class CrashWindow
	{
		public const int OpenFolderId = 100;

		public const int CopyId = 101;

		private readonly CrashReport _report;

		private readonly CrashStrings _strings;

		private readonly string _gamePath;

		private readonly WatcherLog _log;

		public CrashWindow(CrashReport report, CrashStrings strings, string gamePath, WatcherLog log)
		{
			_report = report;
			_strings = strings ?? CrashStrings.EnglishOnly;
			_gamePath = gamePath;
			_log = log;
		}

		public void Show(string phrase)
		{
			string text = _report.Summary + "\n\n" + _strings.Get("help");
			try
			{
				ShowTaskDialog(phrase, text);
			}
			catch (Exception ex)
			{
				_log.Write("The task dialog is unavailable, using a message box: " + ex.Message);
				NativeMethods.MessageBox(IntPtr.Zero, phrase + "\n\n" + text + "\n\n" + _report.Details + "\n\n" + _strings.Format("footer", _report.Folder), _strings.Get("title"), 24u);
			}
		}

		private void ShowTaskDialog(string phrase, string content)
		{
			(int, string)[] array = new(int, string)[2]
			{
				(100, _strings.Get("openFolder")),
				(101, _strings.Get("copy"))
			};
			int num = 4 + IntPtr.Size;
			IntPtr intPtr = Marshal.AllocHGlobal(num * array.Length);
			IntPtr[] array2 = new IntPtr[array.Length];
			IntPtr intPtr2 = GameIcon();
			NativeMethods.TaskDialogCallback taskDialogCallback = OnNotification;
			try
			{
				for (int i = 0; i < array.Length; i++)
				{
					array2[i] = Marshal.StringToHGlobalUni(array[i].Item2);
					Marshal.WriteInt32(intPtr, i * num, array[i].Item1);
					Marshal.WriteIntPtr(intPtr, i * num + 4, array2[i]);
				}
				NativeMethods.TaskDialogConfig config = new NativeMethods.TaskDialogConfig
				{
					Flags = (8 | ((!(intPtr2 == IntPtr.Zero)) ? 2 : 0)),
					CommonButtons = 32,
					WindowTitle = _strings.Get("title"),
					MainIcon = ((intPtr2 == IntPtr.Zero) ? NativeMethods.WarningIcon : intPtr2),
					MainInstruction = phrase,
					Content = content,
					ButtonCount = (uint)array.Length,
					Buttons = intPtr,
					DefaultButton = 8,
					ExpandedInformation = _report.Details,
					ExpandedControlText = _strings.Get("hideDetails"),
					CollapsedControlText = _strings.Get("showDetails"),
					Footer = _strings.Format("footer", _report.Folder),
					Callback = taskDialogCallback
				};
				config.Size = (uint)Marshal.SizeOf(typeof(NativeMethods.TaskDialogConfig));
				int button;
				int radioButton;
				bool verification;
				int num2 = NativeMethods.TaskDialogIndirect(ref config, out button, out radioButton, out verification);
				if (num2 < 0)
				{
					Marshal.ThrowExceptionForHR(num2);
				}
			}
			finally
			{
				GC.KeepAlive(taskDialogCallback);
				IntPtr[] array3 = array2;
				foreach (IntPtr intPtr3 in array3)
				{
					if (intPtr3 != IntPtr.Zero)
					{
						Marshal.FreeHGlobal(intPtr3);
					}
				}
				Marshal.FreeHGlobal(intPtr);
				if (intPtr2 != IntPtr.Zero)
				{
					NativeMethods.DestroyIcon(intPtr2);
				}
			}
		}

		private int OnNotification(IntPtr window, int notification, IntPtr wParam, IntPtr lParam, IntPtr data)
		{
			try
			{
				switch (notification)
				{
				case 0:
					NativeMethods.SetWindowPos(window, NativeMethods.TopMostWindow, 0, 0, 0, 0, 3u);
					NativeMethods.SetForegroundWindow(window);
					return 0;
				default:
					return 0;
				case 2:
					switch (wParam.ToInt32())
					{
					case 100:
						OpenFolder();
						return 1;
					case 101:
						if (ClipboardText.Set(window, _report.Text))
						{
							SetFooter(window, _strings.Get("copied") + ". " + _strings.Format("footer", _report.Folder));
						}
						return 1;
					default:
						return 0;
					}
				}
			}
			catch (Exception ex)
			{
				_log.Write("The crash window action failed: " + ex.Message);
				return 1;
			}
		}

		private void OpenFolder()
		{
			Process.Start(new ProcessStartInfo("explorer.exe", "\"" + _report.Folder + "\"")
			{
				UseShellExecute = false
			});
		}

		private static void SetFooter(IntPtr window, string text)
		{
			IntPtr intPtr = Marshal.StringToHGlobalUni(text);
			try
			{
				NativeMethods.SendMessage(window, 1132, new IntPtr(2), intPtr);
			}
			finally
			{
				Marshal.FreeHGlobal(intPtr);
			}
		}

		private IntPtr GameIcon()
		{
			if (string.IsNullOrEmpty(_gamePath))
			{
				return IntPtr.Zero;
			}
			IntPtr[] array = new IntPtr[1];
			IntPtr[] array2 = new IntPtr[1];
			try
			{
				if (NativeMethods.ExtractIconEx(_gamePath, 0, array, array2, 1u) == 0)
				{
					return IntPtr.Zero;
				}
			}
			catch (Exception)
			{
				return IntPtr.Zero;
			}
			if (array2[0] != IntPtr.Zero)
			{
				NativeMethods.DestroyIcon(array2[0]);
			}
			return array[0];
		}
	}
	internal sealed class DebugOutcome
	{
		public GameExit Exit { get; set; }

		public CrashEventInfo Crash { get; set; }

		public string DumpPath { get; set; }

		public int PassedExceptions { get; set; }

		public int EarlyDumps { get; set; }
	}
	internal static class GameDebugger
	{
		public const uint DumpType = 6500u;

		public const uint BreakpointCode = 2147483651u;

		public const uint Wow64BreakpointCode = 1073741855u;

		public const int MaxEarlyDumps = 10;

		public static readonly TimeSpan EarlyDumpPause = TimeSpan.FromSeconds(5.0);

		public static readonly uint[] FatalCodes = new uint[7] { 3221225477u, 3221225501u, 3221225622u, 3221225725u, 3221226356u, 3221226505u, 3221226528u };

		public static DebugOutcome Watch(int processId, string dumpPath, WatcherLog log)
		{
			IntPtr intPtr = NativeMethods.OpenProcess(1053776u, inherit: false, processId);
			if (intPtr == IntPtr.Zero)
			{
				log.Write("Could not open the game for dumps (" + new Win32Exception().Message + "), waiting without them");
				return null;
			}
			if (!NativeMethods.DebugActiveProcess(processId))
			{
				log.Write("Could not follow the game as a debugger (" + new Win32Exception().Message + "), waiting without dumps");
				NativeMethods.CloseHandle(intPtr);
				return null;
			}
			NativeMethods.DebugSetProcessKillOnExit(killOnExit: false);
			DebugOutcome debugOutcome = new DebugOutcome
			{
				Exit = new GameExit
				{
					ImagePath = GameProcess.ImagePath(intPtr)
				}
			};
			IntPtr intPtr2 = Marshal.AllocHGlobal(176);
			bool flag = false;
			bool flag2 = false;
			string text = (string.IsNullOrEmpty(dumpPath) ? null : Path.ChangeExtension(dumpPath, ".early.dmp"));
			CrashEventInfo crashEventInfo = null;
			DateTime dateTime = DateTime.MinValue;
			try
			{
				while (!flag2)
				{
					if (!NativeMethods.WaitForDebugEvent(intPtr2, uint.MaxValue))
					{
						log.Write("Following the game stopped (" + new Win32Exception().Message + "), waiting without dumps");
						NativeMethods.DebugActiveProcessStop(processId);
						break;
					}
					int num = Marshal.ReadInt32(intPtr2, 0);
					int processId2 = Marshal.ReadInt32(intPtr2, 4);
					int num2 = Marshal.ReadInt32(intPtr2, 8);
					uint continueStatus = 65538u;
					switch (num)
					{
					case 1:
					{
						uint num3 = (uint)Marshal.ReadInt32(intPtr2, 16);
						bool flag3 = Marshal.ReadInt32(intPtr2, 168) != 0;
						if (flag3 && !flag && (num3 == 2147483651u || num3 == 1073741855))
						{
							flag = true;
							break;
						}
						if (flag3)
						{
							continueStatus = 2147549185u;
							debugOutcome.PassedExceptions++;
							if (text == null || debugOutcome.Crash != null || Array.IndexOf(FatalCodes, num3) < 0 || debugOutcome.EarlyDumps >= 10 || !(DateTime.UtcNow - dateTime >= EarlyDumpPause))
							{
								break;
							}
							CrashEventInfo crashEventInfo2 = Describe(intPtr, num2, num3, intPtr2, log);
							if (!CrashText.IsManaged(crashEventInfo2))
							{
								dateTime = DateTime.UtcNow;
								debugOutcome.EarlyDumps++;
								crashEventInfo = crashEventInfo2;
								crashEventInfo.Early = true;
								log.Write($"The game raised {CrashText.Hex(num3)} in {crashEventInfo.Module} + {crashEventInfo.Offset} on thread {num2}, keeping a dump in case it does not recover");
								if (!TryWriteDump(intPtr, processId, num2, intPtr2, text, log))
								{
									crashEventInfo = null;
								}
							}
							break;
						}
						continueStatus = 2147549185u;
						if (debugOutcome.Crash == null)
						{
							debugOutcome.Crash = Describe(intPtr, num2, num3, intPtr2, log);
							log.Write($"The game crashed with {CrashText.Hex(num3)} in {debugOutcome.Crash.Module} + {debugOutcome.Crash.Offset} on thread {num2}");
							if (!string.IsNullOrEmpty(dumpPath) && TryWriteDump(intPtr, processId, num2, intPtr2, dumpPath, log))
							{
								debugOutcome.DumpPath = dumpPath;
							}
						}
						break;
					}
					case 3:
					case 6:
					{
						IntPtr intPtr3 = Marshal.ReadIntPtr(intPtr2, 16);
						if (intPtr3 != IntPtr.Zero)
						{
							NativeMethods.CloseHandle(intPtr3);
						}
						break;
					}
					case 5:
						debugOutcome.Exit.ExitCode = (uint)Marshal.ReadInt32(intPtr2, 16);
						flag2 = true;
						break;
					}
					NativeMethods.ContinueDebugEvent(processId2, num2, continueStatus);
				}
				if (!flag2)
				{
					NativeMethods.WaitForSingleObject(intPtr, uint.MaxValue);
					if (NativeMethods.GetExitCodeProcess(intPtr, out var exitCode))
					{
						debugOutcome.Exit.ExitCode = exitCode;
					}
				}
				NativeMethods.WaitForSingleObject(intPtr, 5000u);
				GameProcess.FillTimes(intPtr, debugOutcome.Exit);
				if (debugOutcome.Crash == null && crashEventInfo != null && debugOutcome.Exit.ExitCode == ParseCode(crashEventInfo.ExceptionCode))
				{
					debugOutcome.Crash = crashEventInfo;
					if (Move(text, dumpPath, log))
					{
						debugOutcome.DumpPath = dumpPath;
					}
					log.Write("The game closed with the exception it raised in " + crashEventInfo.Module + " + " + crashEventInfo.Offset + ", the dump of that moment is the crash dump");
				}
				return debugOutcome;
			}
			finally
			{
				Marshal.FreeHGlobal(intPtr2);
				NativeMethods.CloseHandle(intPtr);
				if (text != null && File.Exists(text))
				{
					try
					{
						File.Delete(text);
					}
					catch (Exception)
					{
					}
				}
			}
		}

		private static uint ParseCode(string code)
		{
			string text = (code ?? string.Empty).Trim();
			if (text.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
			{
				text = text.Substring(2);
			}
			if (!uint.TryParse(text, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result))
			{
				return 0u;
			}
			return result;
		}

		private static bool Move(string source, string target, WatcherLog log)
		{
			try
			{
				if (File.Exists(target))
				{
					File.Delete(target);
				}
				File.Move(source, target);
				return true;
			}
			catch (Exception ex)
			{
				log.Write("Could not keep the dump of the raised exception: " + ex.Message);
				return false;
			}
		}

		public static List<CrashModule> Modules(IntPtr process)
		{
			List<CrashModule> list = new List<CrashModule>();
			IntPtr[] array = new IntPtr[1024];
			if (!NativeMethods.EnumProcessModulesEx(process, array, (uint)(array.Length * IntPtr.Size), out var needed, 3u))
			{
				return list;
			}
			int num = Math.Min(array.Length, (int)(needed / (uint)IntPtr.Size));
			StringBuilder stringBuilder = new StringBuilder(1024);
			for (int i = 0; i < num; i++)
			{
				if (NativeMethods.GetModuleInformation(process, array[i], out var info, (uint)Marshal.SizeOf(typeof(NativeMethods.ModuleInfo))))
				{
					stringBuilder.Clear();
					NativeMethods.GetModuleFileNameEx(process, array[i], stringBuilder, (uint)stringBuilder.Capacity);
					list.Add(new CrashModule(stringBuilder.ToString(), (ulong)info.BaseOfDll.ToInt64(), info.SizeOfImage));
				}
			}
			return list;
		}

		private static CrashEventInfo Describe(IntPtr process, int thread, uint exceptionCode, IntPtr debugEvent, WatcherLog log)
		{
			ulong address = (ulong)Marshal.ReadInt64(debugEvent, 32);
			CrashEventInfo crashEventInfo;
			try
			{
				crashEventInfo = CrashText.Locate(Modules(process), address, exceptionCode, thread);
			}
			catch (Exception ex)
			{
				crashEventInfo = CrashText.Locate(null, address, exceptionCode, thread);
				log.Write("Could not list the game modules: " + ex.Message);
			}
			int num = Marshal.ReadInt32(debugEvent, 40);
			if (exceptionCode == 3221225477u && num >= 2)
			{
				crashEventInfo.Access = CrashText.AccessText((ulong)Marshal.ReadInt64(debugEvent, 48), (ulong)Marshal.ReadInt64(debugEvent, 56));
			}
			return crashEventInfo;
		}

		public static bool TryWriteSnapshot(int processId, string path, WatcherLog log)
		{
			IntPtr intPtr = NativeMethods.OpenProcess(1104u, inherit: false, processId);
			if (intPtr == IntPtr.Zero)
			{
				log.Write("Could not open the game for a dump of the hang (" + new Win32Exception().Message + ")");
				return false;
			}
			try
			{
				using (FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None))
				{
					if (!NativeMethods.MiniDumpWriteDump(intPtr, processId, fileStream.SafeFileHandle.DangerousGetHandle(), 6500u, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero))
					{
						throw new Win32Exception();
					}
				}
				log.Write($"Memory dump of the hang written to {path}, {new FileInfo(path).Length / 1024} KiB");
				return true;
			}
			catch (Exception ex)
			{
				log.Write("The memory dump of the hang could not be written: " + ex.Message);
				try
				{
					File.Delete(path);
				}
				catch (Exception)
				{
				}
				return false;
			}
			finally
			{
				NativeMethods.CloseHandle(intPtr);
			}
		}

		private static bool TryWriteDump(IntPtr process, int processId, int thread, IntPtr debugEvent, string path, WatcherLog log)
		{
			try
			{
				WriteDump(process, processId, thread, debugEvent, path);
				log.Write($"Memory dump written to {path}, {new FileInfo(path).Length / 1024} KiB");
				return true;
			}
			catch (Exception ex)
			{
				log.Write("The memory dump could not be written: " + ex.Message);
				try
				{
					File.Delete(path);
				}
				catch (Exception)
				{
				}
				return false;
			}
		}

		private static void WriteDump(IntPtr process, int processId, int thread, IntPtr debugEvent, string dumpPath)
		{
			IntPtr intPtr = Marshal.AllocHGlobal(152);
			IntPtr hglobal = Marshal.AllocHGlobal(1248);
			IntPtr intPtr2 = Marshal.AllocHGlobal(2 * IntPtr.Size);
			IntPtr intPtr3 = Marshal.AllocHGlobal(16);
			IntPtr intPtr4 = NativeMethods.OpenThread(72u, inherit: false, thread);
			try
			{
				byte[] array = new byte[152];
				Marshal.Copy(debugEvent + 16, array, 0, array.Length);
				Array.Clear(array, 8, 8);
				Marshal.Copy(array, 0, intPtr, array.Length);
				IntPtr intPtr5 = new IntPtr((hglobal.ToInt64() + 15) & -16);
				Marshal.Copy(new byte[1232], 0, intPtr5, 1232);
				Marshal.WriteInt32(intPtr5, 48, 1048607);
				bool flag = intPtr4 != IntPtr.Zero && NativeMethods.GetThreadContext(intPtr4, intPtr5);
				Marshal.WriteIntPtr(intPtr2, 0, intPtr);
				Marshal.WriteIntPtr(intPtr2, IntPtr.Size, flag ? intPtr5 : IntPtr.Zero);
				Marshal.WriteInt32(intPtr3, 0, thread);
				Marshal.WriteInt64(intPtr3, 4, intPtr2.ToInt64());
				Marshal.WriteInt32(intPtr3, 12, 0);
				using FileStream fileStream = new FileStream(dumpPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
				if (!NativeMethods.MiniDumpWriteDump(process, processId, fileStream.SafeFileHandle.DangerousGetHandle(), 6500u, flag ? intPtr3 : IntPtr.Zero, IntPtr.Zero, IntPtr.Zero))
				{
					throw new Win32Exception();
				}
			}
			finally
			{
				if (intPtr4 != IntPtr.Zero)
				{
					NativeMethods.CloseHandle(intPtr4);
				}
				Marshal.FreeHGlobal(intPtr3);
				Marshal.FreeHGlobal(intPtr2);
				Marshal.FreeHGlobal(hglobal);
				Marshal.FreeHGlobal(intPtr);
			}
		}
	}
	internal sealed class GameExit
	{
		public uint ExitCode { get; set; }

		public DateTime Started { get; set; }

		public DateTime Exited { get; set; }

		public string ImagePath { get; set; } = string.Empty;

		public TimeSpan Played
		{
			get
			{
				if (!(Exited > Started))
				{
					return TimeSpan.Zero;
				}
				return Exited - Started;
			}
		}
	}
	internal static class GameProcess
	{
		public static GameExit WaitForExit(int processId)
		{
			IntPtr intPtr = NativeMethods.OpenProcess(1052672u, inherit: false, processId);
			if (intPtr == IntPtr.Zero)
			{
				throw new Win32Exception();
			}
			try
			{
				GameExit gameExit = new GameExit
				{
					ImagePath = ImagePath(intPtr)
				};
				NativeMethods.WaitForSingleObject(intPtr, uint.MaxValue);
				if (!NativeMethods.GetExitCodeProcess(intPtr, out var exitCode))
				{
					throw new Win32Exception();
				}
				gameExit.ExitCode = exitCode;
				FillTimes(intPtr, gameExit);
				return gameExit;
			}
			finally
			{
				NativeMethods.CloseHandle(intPtr);
			}
		}

		public static void FillTimes(IntPtr handle, GameExit exit)
		{
			if (NativeMethods.GetProcessTimes(handle, out var creation, out var exit2, out var _, out var _) && exit2 > 0)
			{
				exit.Started = DateTime.FromFileTimeUtc(creation).ToLocalTime();
				exit.Exited = DateTime.FromFileTimeUtc(exit2).ToLocalTime();
			}
			else
			{
				exit.Exited = DateTime.Now;
			}
		}

		public static string ImagePath(IntPtr handle)
		{
			uint size = 1024u;
			StringBuilder stringBuilder = new StringBuilder((int)size);
			if (!NativeMethods.QueryFullProcessImageName(handle, 0u, stringBuilder, ref size))
			{
				return string.Empty;
			}
			return stringBuilder.ToString();
		}
	}
	internal sealed class HangWatcher
	{
		public static readonly TimeSpan Limit = TimeSpan.FromSeconds(8.0);

		public static readonly TimeSpan Poll = TimeSpan.FromSeconds(1.0);

		private readonly int _processId;

		private readonly string _sessionFile;

		private readonly string _dumpPath;

		private readonly WatcherLog _log;

		private readonly ManualResetEvent _stop = new ManualResetEvent(initialState: false);

		private Thread _thread;

		private long _seenLength = -1L;

		public DateTime? QuitStarted { get; private set; }

		public bool Hung { get; private set; }

		public string DumpPath { get; private set; }

		public HangWatcher(int processId, string sessionFile, string dumpPath, WatcherLog log)
		{
			_processId = processId;
			_sessionFile = sessionFile;
			_dumpPath = dumpPath;
			_log = log;
		}

		public void Start()
		{
			_thread = new Thread(Run)
			{
				IsBackground = true,
				Name = "CatLib hang watch"
			};
			_thread.Start();
		}

		public void Stop()
		{
			_stop.Set();
			_thread?.Join(TimeSpan.FromMinutes(2.0));
		}

		public int HungSeconds(DateTime exited)
		{
			if (!Hung || !QuitStarted.HasValue)
			{
				return 0;
			}
			TimeSpan limit = Limit;
			return Math.Max((int)limit.TotalSeconds, (int)Math.Round((exited - QuitStarted.Value).TotalSeconds));
		}

		private void Run()
		{
			try
			{
				while (!_stop.WaitOne(Poll))
				{
					if (!QuitStarted.HasValue)
					{
						if (IsQuitting())
						{
							QuitStarted = DateTime.Now;
							WatcherLog log = _log;
							object arg = _processId;
							TimeSpan limit = Limit;
							log.Write($"The game (process {arg}) began to quit, it is reported as hung if it still runs in {limit.TotalSeconds} s");
						}
					}
					else if (!(DateTime.Now - QuitStarted.Value < Limit))
					{
						Hung = true;
						WatcherLog log2 = _log;
						object arg2 = _processId;
						TimeSpan limit = Limit;
						log2.Write($"The game (process {arg2}) still runs {limit.TotalSeconds} s after it began to quit, it looks hung");
						if (_dumpPath != null && GameDebugger.TryWriteSnapshot(_processId, _dumpPath, _log))
						{
							DumpPath = _dumpPath;
						}
						break;
					}
				}
			}
			catch (Exception ex)
			{
				_log.Write("Watching the game for a hang failed: " + ex.Message);
			}
		}

		private bool IsQuitting()
		{
			try
			{
				FileInfo fileInfo = new FileInfo(_sessionFile);
				if (!fileInfo.Exists || fileInfo.Length == _seenLength)
				{
					return false;
				}
				_seenLength = fileInfo.Length;
				string value = "clean=";
				foreach (string item in ReportWriter.ReadLines(_sessionFile))
				{
					if (item.StartsWith(value, StringComparison.Ordinal))
					{
						return true;
					}
				}
			}
			catch (Exception)
			{
			}
			return false;
		}
	}
	internal static class NativeMethods
	{
		public struct ModuleInfo
		{
			public IntPtr BaseOfDll;

			public uint SizeOfImage;

			public IntPtr EntryPoint;
		}

		public delegate int TaskDialogCallback(IntPtr window, int notification, IntPtr wParam, IntPtr lParam, IntPtr data);

		[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1)]
		public struct TaskDialogConfig
		{
			public uint Size;

			public IntPtr Parent;

			public IntPtr Instance;

			public int Flags;

			public int CommonButtons;

			[MarshalAs(UnmanagedType.LPWStr)]
			public string WindowTitle;

			public IntPtr MainIcon;

			[MarshalAs(UnmanagedType.LPWStr)]
			public string MainInstruction;

			[MarshalAs(UnmanagedType.LPWStr)]
			public string Content;

			public uint ButtonCount;

			public IntPtr Buttons;

			public int DefaultButton;

			public uint RadioButtonCount;

			public IntPtr RadioButtons;

			public int DefaultRadioButton;

			[MarshalAs(UnmanagedType.LPWStr)]
			public string VerificationText;

			[MarshalAs(UnmanagedType.LPWStr)]
			public string ExpandedInformation;

			[MarshalAs(UnmanagedType.LPWStr)]
			public string ExpandedControlText;

			[MarshalAs(UnmanagedType.LPWStr)]
			public string CollapsedControlText;

			public IntPtr FooterIcon;

			[MarshalAs(UnmanagedType.LPWStr)]
			public string Footer;

			[MarshalAs(UnmanagedType.FunctionPtr)]
			public TaskDialogCallback Callback;

			public IntPtr CallbackData;

			public uint Width;
		}

		public const uint Synchronize = 1048576u;

		public const uint ProcessQueryLimitedInformation = 4096u;

		public const uint Infinite = uint.MaxValue;

		public const uint UnicodeText = 13u;

		public const uint MovableMemory = 2u;

		public const uint TopMost = 8u;

		public const uint NoSize = 1u;

		public const uint NoMove = 2u;

		public const uint IconError = 16u;

		public const int TaskDialogCreated = 0;

		public const int TaskDialogButtonClicked = 2;

		public const int SetElementText = 1132;

		public const int FooterElement = 2;

		public const int AllowCancellation = 8;

		public const int UseMainIconHandle = 2;

		public const int CloseButton = 32;

		public const int CloseResult = 8;

		public const int KeepOpen = 1;

		public const int Ok = 0;

		public const uint ProcessQueryInformation = 1024u;

		public const uint ProcessVmRead = 16u;

		public const uint ProcessDuplicateHandle = 64u;

		public const uint ThreadGetContext = 8u;

		public const uint ThreadQueryInformation = 64u;

		public const uint DebugContinue = 65538u;

		public const uint DebugExceptionNotHandled = 2147549185u;

		public const int ExceptionDebugEvent = 1;

		public const int CreateProcessDebugEvent = 3;

		public const int ExitProcessDebugEvent = 5;

		public const int LoadDllDebugEvent = 6;

		public const int DebugEventSize = 176;

		public const int ExceptionRecordSize = 152;

		public const int ContextSize = 1232;

		public const int ContextFlagsOffset = 48;

		public const int ContextAll = 1048607;

		public const uint ListModulesAll = 3u;

		public static readonly IntPtr WarningIcon = new IntPtr(65535);

		public static readonly IntPtr TopMostWindow = new IntPtr(-1);

		[DllImport("kernel32.dll", SetLastError = true)]
		public static extern IntPtr OpenProcess(uint access, bool inherit, int processId);

		[DllImport("kernel32.dll", SetLastError = true)]
		public static extern uint WaitForSingleObject(IntPtr handle, uint milliseconds);

		[DllImport("kernel32.dll", SetLastError = true)]
		public static extern bool GetExitCodeProcess(IntPtr process, out uint exitCode);

		[DllImport("kernel32.dll", SetLastError = true)]
		public static extern bool GetProcessTimes(IntPtr process, out long creation, out long exit, out long kernel, out long user);

		[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
		public static extern bool QueryFullProcessImageName(IntPtr process, uint flags, StringBuilder name, ref uint size);

		[DllImport("kernel32.dll", SetLastError = true)]
		public static extern bool CloseHandle(IntPtr handle);

		[DllImport("kernel32.dll", SetLastError = true)]
		public static extern bool DebugActiveProcess(int processId);

		[DllImport("kernel32.dll", SetLastError = true)]
		public static extern bool DebugActiveProcessStop(int processId);

		[DllImport("kernel32.dll", SetLastError = true)]
		public static extern bool DebugSetProcessKillOnExit(bool killOnExit);

		[DllImport("kernel32.dll", SetLastError = true)]
		public static extern bool WaitForDebugEvent(IntPtr debugEvent, uint milliseconds);

		[DllImport("kernel32.dll", SetLastError = true)]
		public static extern bool ContinueDebugEvent(int processId, int threadId, uint continueStatus);

		[DllImport("kernel32.dll", SetLastError = true)]
		public static extern IntPtr OpenThread(uint access, bool inherit, int threadId);

		[DllImport("kernel32.dll", SetLastError = true)]
		public static extern bool GetThreadContext(IntPtr thread, IntPtr context);

		[DllImport("kernel32.dll", EntryPoint = "K32EnumProcessModulesEx", SetLastError = true)]
		public static extern bool EnumProcessModulesEx(IntPtr process, [Out] IntPtr[] modules, uint size, out uint needed, uint filter);

		[DllImport("kernel32.dll", EntryPoint = "K32GetModuleInformation", SetLastError = true)]
		public static extern bool GetModuleInformation(IntPtr process, IntPtr module, out ModuleInfo info, uint size);

		[DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "K32GetModuleFileNameExW", SetLastError = true)]
		public static extern uint GetModuleFileNameEx(IntPtr process, IntPtr module, StringBuilder name, uint size);

		[DllImport("dbghelp.dll", SetLastError = true)]
		public static extern bool MiniDumpWriteDump(IntPtr process, int processId, IntPtr file, uint type, IntPtr exceptionParam, IntPtr userStreamParam, IntPtr callbackParam);

		[DllImport("kernel32.dll", SetLastError = true)]
		public static extern IntPtr GlobalAlloc(uint flags, UIntPtr bytes);

		[DllImport("kernel32.dll", SetLastError = true)]
		public static extern IntPtr GlobalLock(IntPtr memory);

		[DllImport("kernel32.dll", SetLastError = true)]
		public static extern bool GlobalUnlock(IntPtr memory);

		[DllImport("kernel32.dll", SetLastError = true)]
		public static extern IntPtr GlobalFree(IntPtr memory);

		[DllImport("user32.dll", SetLastError = true)]
		public static extern bool OpenClipboard(IntPtr owner);

		[DllImport("user32.dll", SetLastError = true)]
		public static extern bool EmptyClipboard();

		[DllImport("user32.dll", SetLastError = true)]
		public static extern IntPtr SetClipboardData(uint format, IntPtr memory);

		[DllImport("user32.dll", SetLastError = true)]
		public static extern bool CloseClipboard();

		[DllImport("user32.dll", SetLastError = true)]
		public static extern bool SetWindowPos(IntPtr window, IntPtr after, int x, int y, int width, int height, uint flags);

		[DllImport("user32.dll")]
		public static extern bool SetForegroundWindow(IntPtr window);

		[DllImport("user32.dll", CharSet = CharSet.Unicode)]
		public static extern IntPtr SendMessage(IntPtr window, int message, IntPtr wParam, IntPtr lParam);

		[DllImport("user32.dll", CharSet = CharSet.Unicode)]
		public static extern int MessageBox(IntPtr owner, string text, string caption, uint type);

		[DllImport("user32.dll")]
		public static extern bool DestroyIcon(IntPtr icon);

		[DllImport("user32.dll")]
		public static extern bool SetProcessDPIAware();

		[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
		public static extern uint ExtractIconEx(string file, int index, IntPtr[] large, IntPtr[] small, uint count);

		[DllImport("comctl32.dll", CharSet = CharSet.Unicode)]
		public static extern int TaskDialogIndirect(ref TaskDialogConfig config, out int button, out int radioButton, out bool verification);
	}
	internal static class Program
	{
		public const string ProcessArgument = "--pid";

		public const string SessionArgument = "--session";

		public const string PreviewArgument = "--preview";

		public const string DumpArgument = "--dump";

		public const string LogFileName = "watcher.log";

		public static string WatcherVersion
		{
			get
			{
				Version version = typeof(Program).Assembly.GetName().Version;
				if (!(version == null))
				{
					return version.Major + "." + version.Minor + "." + version.Build;
				}
				return "unknown";
			}
		}

		[STAThread]
		private static int Main(string[] args)
		{
			int result = 0;
			string text = null;
			bool flag = false;
			bool dump = false;
			for (int i = 0; i < args.Length; i++)
			{
				if (args[i] == "--pid" && i + 1 < args.Length)
				{
					int.TryParse(args[++i], NumberStyles.Integer, CultureInfo.InvariantCulture, out result);
				}
				else if (args[i] == "--session" && i + 1 < args.Length)
				{
					text = args[++i];
				}
				else if (args[i] == "--preview")
				{
					flag = true;
				}
				else if (args[i] == "--dump")
				{
					dump = true;
				}
			}
			if (string.IsNullOrEmpty(text) || (result <= 0 && !flag))
			{
				return 2;
			}
			string text2 = Path.GetDirectoryName(Path.GetFullPath(text)) ?? ".";
			WatcherLog watcherLog = new WatcherLog(Path.Combine(text2, "watcher.log"));
			try
			{
				return flag ? Preview(text, text2, watcherLog) : Watch(result, text, text2, dump, watcherLog);
			}
			catch (Exception arg)
			{
				watcherLog.Write($"The crash watcher failed: {arg}");
				return 1;
			}
		}

		private static int Watch(int processId, string sessionFile, string reportsDirectory, bool dump, WatcherLog log)
		{
			string text = Path.Combine(reportsDirectory, "crash_" + processId.ToString(CultureInfo.InvariantCulture) + ".dmp");
			string text2 = Path.ChangeExtension(text, ".hang.dmp");
			HangWatcher hangWatcher = new HangWatcher(processId, sessionFile, dump ? text2 : null, log);
			hangWatcher.Start();
			DebugOutcome debugOutcome = (dump ? GameDebugger.Watch(processId, text, log) : null);
			GameExit gameExit = debugOutcome?.Exit ?? GameProcess.WaitForExit(processId);
			hangWatcher.Stop();
			CrashSession crashSession = CrashSession.Parse(ReportWriter.ReadLines(sessionFile));
			crashSession.WatcherVersion = WatcherVersion;
			if (crashSession.ProcessId == 0)
			{
				crashSession.ProcessId = processId;
			}
			if (crashSession.IsNewerFormat)
			{
				log.Write($"The session file has format {crashSession.Format}, this crash watcher {WatcherVersion} reads format {1}: update CatLib Crash Watcher, the report may miss details");
			}
			int num = QuitSeconds(crashSession, gameExit);
			if (num >= 0)
			{
				log.Write($"The game (process {processId}) closed with {CrashText.Hex(gameExit.ExitCode)} {num} s after it began to quit");
			}
			if (CrashText.IsNormalExit(gameExit.ExitCode))
			{
				if (hangWatcher.Hung)
				{
					log.Write($"The game (process {processId}) closed normally after all, the dump of the hang is not kept");
				}
				TryDelete(sessionFile, log);
				TryDelete(text, log);
				TryDelete(text2, log);
				return 0;
			}
			log.Write(string.Format("The game (process {0}) exited with {1}{2}{3}, writing a report", processId, CrashText.Hex(gameExit.ExitCode), (debugOutcome == null) ? string.Empty : $" after {debugOutcome.PassedExceptions} handled exception(s)", hangWatcher.Hung ? ", it was hung while quitting" : string.Empty));
			bool expectFault = debugOutcome?.Crash == null && CrashEvents.LooksLikeFault(gameExit.ExitCode);
			CrashEventInfo fromEvents = CrashEvents.Collect(processId, gameExit.Exited.ToUniversalTime(), expectFault, log);
			CrashEventInfo crashEventInfo = CrashText.WithManagedException(CrashText.Merge(debugOutcome?.Crash, fromEvents), crashSession);
			string text3 = debugOutcome?.DumpPath;
			if (hangWatcher.Hung && !crashEventInfo.HasFault)
			{
				crashEventInfo.HangSeconds = ((num > 0) ? num : hangWatcher.HungSeconds(gameExit.Exited));
				text3 = text3 ?? hangWatcher.DumpPath;
			}
			CrashReport crashReport = ReportWriter.Write(reportsDirectory, sessionFile, crashSession, gameExit, crashEventInfo, text3, log);
			TryDelete(sessionFile, log);
			if (!string.Equals(crashReport.DumpPath, text2, StringComparison.OrdinalIgnoreCase))
			{
				TryDelete(text2, log);
			}
			log.Write("Report written to " + crashReport.Folder);
			CrashStrings strings = crashSession.Strings;
			new CrashWindow(crashReport, strings, gameExit.ImagePath, log).Show(strings.Phrase(new Random()));
			return 0;
		}

		private static int Preview(string sessionFile, string reportsDirectory, WatcherLog log)
		{
			CrashSession crashSession = CrashSession.Parse(ReportWriter.ReadLines(sessionFile));
			crashSession.WatcherVersion = WatcherVersion;
			GameExit gameExit = new GameExit
			{
				ExitCode = 3221225477u,
				Started = DateTime.Now.AddMinutes(-42.0),
				Exited = DateTime.Now
			};
			CrashEventInfo crashEventInfo = new CrashEventInfo
			{
				Module = "example.dll",
				Offset = "0x0000000000001234",
				ExceptionCode = "0xc0000005"
			};
			CrashStrings strings = crashSession.Strings;
			crashEventInfo.ThreadId = crashSession.MainThreadId;
			CrashReport crashReport = new CrashReport();
			crashReport.Folder = reportsDirectory;
			crashReport.Summary = CrashText.Summary(crashSession, gameExit.ExitCode, crashEventInfo, gameExit.Played, strings, dumpSaved: true);
			crashReport.Details = CrashText.Details(crashSession, gameExit.ExitCode, crashEventInfo, gameExit.Exited, strings);
			crashReport.Text = CrashText.Report(crashSession, gameExit.ExitCode, crashEventInfo, gameExit.Exited, gameExit.Played, new string[0]);
			new CrashWindow(crashReport, strings, string.Empty, log).Show(strings.Phrase(new Random()));
			return 0;
		}

		private static int QuitSeconds(CrashSession session, GameExit exit)
		{
			if (string.IsNullOrEmpty(session.CleanExit) || !DateTime.TryParseExact(session.CleanExit, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var result))
			{
				return -1;
			}
			return Math.Max(0, (int)Math.Round((exit.Exited - result).TotalSeconds));
		}

		private static void TryDelete(string path, WatcherLog log)
		{
			try
			{
				if (File.Exists(path))
				{
					File.Delete(path);
				}
			}
			catch (Exception ex)
			{
				log.Write("Could not remove " + path + ": " + ex.Message);
			}
		}
	}
	internal sealed class CrashReport
	{
		public string Folder { get; set; } = string.Empty;

		public string Text { get; set; } = string.Empty;

		public string Details { get; set; } = string.Empty;

		public string Summary { get; set; } = string.Empty;

		public string DumpPath { get; set; }
	}
	internal static class ReportWriter
	{
		public const int KeptReports = 10;

		public const int KeptDumps = 3;

		public const string DumpFileName = "crash.dmp";

		public const string ReportFileName = "report.txt";

		public const string SessionFileName = "session.txt";

		public const string FolderTimeFormat = "yyyy-MM-dd_HH-mm-ss";

		public static CrashReport Write(string reportsDirectory, string sessionFile, CrashSession session, GameExit exit, CrashEventInfo info, string ownDump, WatcherLog log)
		{
			string text = Path.Combine(reportsDirectory, exit.Exited.ToString("yyyy-MM-dd_HH-mm-ss", CultureInfo.InvariantCulture) + "_" + session.ProcessId.ToString(CultureInfo.InvariantCulture));
			Directory.CreateDirectory(text);
			string text2 = CopyInto(session.BepInExLog, text, log);
			CopyInto(session.PlayerLog, text, log);
			CopyInto(sessionFile, text, log, "session.txt");
			if (text2 == null)
			{
				text2 = session.BepInExLog;
			}
			List<string> logTail = CrashText.Tail(ReadLines(text2), 40);
			string text3 = MoveDump(ownDump, text, log);
			bool dumpSaved = text3 != null;
			text3 = text3 ?? FindDump(session.ProcessId);
			string text4 = CrashText.Report(session, exit.ExitCode, info, exit.Exited, exit.Played, logTail, text3);
			File.WriteAllText(Path.Combine(text, "report.txt"), text4, Encoding.UTF8);
			Prune(reportsDirectory, text, log);
			CrashStrings strings = session.Strings;
			return new CrashReport
			{
				Folder = text,
				Text = text4,
				DumpPath = text3,
				Summary = CrashText.Summary(session, exit.ExitCode, info, exit.Played, strings, dumpSaved),
				Details = CrashText.Details(session, exit.ExitCode, info, exit.Exited, strings)
			};
		}

		public static List<string> ReadLines(string path)
		{
			List<string> list = new List<string>();
			if (string.IsNullOrEmpty(path) || !File.Exists(path))
			{
				return list;
			}
			try
			{
				using FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
				using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true);
				string item;
				while ((item = streamReader.ReadLine()) != null)
				{
					list.Add(item);
				}
			}
			catch (IOException)
			{
			}
			catch (UnauthorizedAccessException)
			{
			}
			return list;
		}

		private static string CopyInto(string source, string folder, WatcherLog log, string name = null)
		{
			if (string.IsNullOrEmpty(source) || !File.Exists(source))
			{
				return null;
			}
			string text = Path.Combine(folder, name ?? Path.GetFileName(source));
			try
			{
				using (FileStream fileStream = new FileStream(source, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
				{
					using FileStream destination = new FileStream(text, FileMode.Create, FileAccess.Write);
					fileStream.CopyTo(destination);
				}
				return text;
			}
			catch (Exception ex)
			{
				log.Write("Could not copy " + source + ": " + ex.Message);
				return null;
			}
		}

		private static string MoveDump(string source, string folder, WatcherLog log)
		{
			if (string.IsNullOrEmpty(source) || !File.Exists(source))
			{
				return null;
			}
			string text = Path.Combine(folder, "crash.dmp");
			try
			{
				File.Move(source, text);
				return text;
			}
			catch (Exception ex)
			{
				log.Write("Could not move the memory dump into the report folder: " + ex.Message);
				return source;
			}
		}

		private static string FindDump(int processId)
		{
			try
			{
				string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CrashDumps");
				if (!Directory.Exists(path))
				{
					return null;
				}
				return Directory.GetFiles(path, "*." + processId.ToString(CultureInfo.InvariantCulture) + ".dmp").FirstOrDefault();
			}
			catch (Exception)
			{
				return null;
			}
		}

		private static void Prune(string reportsDirectory, string current, WatcherLog log)
		{
			List<string> source = (from path in Directory.GetDirectories(reportsDirectory)
				where File.Exists(Path.Combine(path, "report.txt"))
				select path).OrderByDescending<string, string>((string path) => Path.GetFileName(path), StringComparer.Ordinal).ToList();
			foreach (string item in source.Skip(3).Take(Math.Max(0, 7)))
			{
				string text = Path.Combine(item, "crash.dmp");
				try
				{
					if (File.Exists(text))
					{
						File.Delete(text);
					}
				}
				catch (Exception ex)
				{
					log.Write("Could not remove the old memory dump " + text + ": " + ex.Message);
				}
			}
			foreach (string item2 in source.Skip(10))
			{
				if (!string.Equals(item2, current, StringComparison.OrdinalIgnoreCase))
				{
					try
					{
						Directory.Delete(item2, recursive: true);
					}
					catch (Exception ex2)
					{
						log.Write("Could not remove the old report " + item2 + ": " + ex2.Message);
					}
				}
			}
		}
	}
	internal sealed class WatcherLog
	{
		public const long MaxBytes = 262144L;

		private readonly string _path;

		public WatcherLog(string path)
		{
			_path = path;
		}

		public void Write(string message)
		{
			try
			{
				if (File.Exists(_path) && new FileInfo(_path).Length > 262144)
				{
					File.Delete(_path);
				}
				File.AppendAllText(_path, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture) + " " + message + Environment.NewLine, Encoding.UTF8);
			}
			catch (IOException)
			{
			}
			catch (UnauthorizedAccessException)
			{
			}
		}
	}
}