Decompiled source of Valheim Log Exporter v1.0.1

BepInEx/plugins/ValheimLogExporter/ValheimLogExporter.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Microsoft.Win32.SafeHandles;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Splatform;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Valheim Log Exporter")]
[assembly: AssemblyDescription("Create a ZIP with Valheim logs and selected world files.")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyVersion("1.0.1.0")]
[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 ValheimLogExporter
{
	public sealed class FileReceipt
	{
		public string path;

		public long bytes;

		public string sha256;
	}
	public static class ArchiveCore
	{
		public static string Hash(string file)
		{
			using FileStream stream = File.OpenRead(file);
			return Hash(stream);
		}

		public static string Hash(Stream stream)
		{
			using SHA256 sHA = SHA256.Create();
			return BitConverter.ToString(sHA.ComputeHash(stream)).Replace("-", "").ToLowerInvariant();
		}

		public static string Hash(string file, Func<bool> cancel, Action<string> progress)
		{
			using FileStream input = File.OpenRead(file);
			return Hash(input, cancel, progress);
		}

		public static string Hash(Stream input, Func<bool> cancel, Action<string> progress)
		{
			using SHA256 sHA = SHA256.Create();
			byte[] array = new byte[65536];
			long num = 0L;
			while (true)
			{
				if (cancel != null && cancel())
				{
					throw new OperationCanceledException();
				}
				int num2 = input.Read(array, 0, array.Length);
				if (num2 == 0)
				{
					break;
				}
				sHA.TransformBlock(array, 0, num2, array, 0);
				num = checked(num + num2);
				progress?.Invoke("Verifying " + num + " bytes");
			}
			sHA.TransformFinalBlock(new byte[0], 0, 0);
			return BitConverter.ToString(sHA.Hash).Replace("-", "").ToLowerInvariant();
		}

		public static void CopyBounded(Stream input, Stream output, long length, Func<bool> cancel, Action<string> progress, string label)
		{
			if (length < 0)
			{
				throw new IOException("Invalid copy byte count.");
			}
			byte[] array = new byte[65536];
			long num = 0L;
			while (num < length)
			{
				if (cancel != null && cancel())
				{
					throw new OperationCanceledException();
				}
				int num2 = input.Read(array, 0, (int)Math.Min(array.Length, length - num));
				if (num2 == 0)
				{
					throw new EndOfStreamException("Source shortened during bounded capture.");
				}
				output.Write(array, 0, num2);
				num += num2;
				progress?.Invoke(label + ": " + num + "/" + length + " bytes");
			}
			if (cancel != null && cancel())
			{
				throw new OperationCanceledException();
			}
		}

		public static void RejectLinks(string path)
		{
			string text = Path.GetFullPath(path);
			while (!string.IsNullOrEmpty(text))
			{
				if ((File.Exists(text) || Directory.Exists(text)) && (File.GetAttributes(text) & FileAttributes.ReparsePoint) != FileAttributes.None)
				{
					throw new IOException("Linked input or output paths are unsupported.");
				}
				text = Path.GetDirectoryName(text);
			}
		}

		public static string Relative(string root, string path)
		{
			string text = Path.GetFullPath(root).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar;
			string fullPath = Path.GetFullPath(path);
			if (!fullPath.StartsWith(text, (Path.DirectorySeparatorChar == '\\') ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
			{
				throw new IOException("File is outside the selected input directory.");
			}
			return SafeRelative(fullPath.Substring(text.Length).Replace('\\', '/'));
		}

		public static string SafeRelative(string path)
		{
			if (string.IsNullOrEmpty(path) || path.StartsWith("/") || path.Contains("\\") || path.Contains(":"))
			{
				throw new IOException("Unsafe archive path.");
			}
			string[] array = path.Split('/');
			foreach (string text in array)
			{
				if (text == ".." || text == "." || text.Length == 0 || text.EndsWith(".") || text.EndsWith(" ") || text.Any((char c) => c < ' ' || "<>\"|?*".IndexOf(c) >= 0))
				{
					throw new IOException("Unsafe archive path.");
				}
				string text2 = text.Split('.')[0].ToUpperInvariant();
				switch (text2)
				{
				default:
					if (text2.Length != 4 || (!text2.StartsWith("COM") && !text2.StartsWith("LPT")) || text2[3] < '1' || text2[3] > '9')
					{
						continue;
					}
					break;
				case "CON":
				case "PRN":
				case "AUX":
				case "NUL":
					break;
				}
				throw new IOException("Reserved archive path.");
			}
			return path;
		}

		public static IEnumerable<string> Files(string root)
		{
			RejectLinks(root);
			string[] files = Directory.GetFiles(root);
			foreach (string text in files)
			{
				RejectLinks(text);
				yield return text;
			}
			files = Directory.GetDirectories(root);
			foreach (string root2 in files)
			{
				foreach (string item in Files(root2))
				{
					yield return item;
				}
			}
		}

		public static void Space(string output, long inputBytes)
		{
			RejectLinks(output);
			string fullPath = Path.GetFullPath(output);
			long availableFreeSpace = new DriveInfo((Environment.OSVersion.Platform == PlatformID.Win32NT) ? Path.GetPathRoot(fullPath) : fullPath).AvailableFreeSpace;
			if (inputBytes < 0 || inputBytes > 3074457345528780117L || availableFreeSpace < inputBytes * 3 + 268435456)
			{
				throw new IOException("Not enough free space for staging and the export ZIP.");
			}
		}

		public static byte[] ReadMounted(Func<bool> mount, Func<byte[]> read, Action unmount)
		{
			bool flag = false;
			try
			{
				flag = mount();
				if (!flag)
				{
					throw new IOException("Cloud read mount failed.");
				}
				return read();
			}
			finally
			{
				if (flag)
				{
					unmount();
				}
			}
		}

		public static void CopyExact(string from, string to, long expected)
		{
			RejectLinks(from);
			RejectLinks(to);
			Directory.CreateDirectory(Path.GetDirectoryName(to));
			using (FileStream fileStream = new FileStream(from, FileMode.Open, FileAccess.Read, FileShare.Read))
			{
				using FileStream fileStream2 = new FileStream(to, FileMode.CreateNew, FileAccess.Write, FileShare.None);
				if (fileStream.Length != expected)
				{
					throw new IOException("Source size changed before staging.");
				}
				fileStream.CopyTo(fileStream2);
				if (fileStream.Length != expected || fileStream2.Length != expected)
				{
					throw new IOException("Source changed while staging.");
				}
			}
			if (Hash(from) != Hash(to))
			{
				throw new IOException("Source and staged hashes differ.");
			}
		}

		public static void ZipVerified(string staging, string partial, string final, Func<bool> cancel, Action<string> progress, Action<Action> admit = null, Action partialCreated = null, Action published = null)
		{
			RejectLinks(staging);
			RejectLinks(partial);
			RejectLinks(final);
			if (File.Exists(final))
			{
				throw new IOException("Export already exists.");
			}
			List<FileReceipt> list = new List<FileReceipt>();
			long num = 0L;
			foreach (string item in Files(staging))
			{
				if (list.Count >= 100000)
				{
					throw new IOException("Archive file count limit reached.");
				}
				if (cancel())
				{
					throw new OperationCanceledException();
				}
				long length = new FileInfo(item).Length;
				num = checked(num + length);
				list.Add(new FileReceipt
				{
					path = Relative(staging, item),
					bytes = length,
					sha256 = Hash(item, cancel, progress)
				});
			}
			Space(Path.GetDirectoryName(Path.GetFullPath(partial)), num);
			using (FileStream fileStream = new FileStream(partial, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None))
			{
				partialCreated?.Invoke();
				using (ZipArchive zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Create, leaveOpen: true))
				{
					foreach (FileReceipt item2 in list)
					{
						if (cancel())
						{
							throw new OperationCanceledException();
						}
						ZipArchiveEntry zipArchiveEntry = zipArchive.CreateEntry(item2.path, item2.path.EndsWith(".zip", StringComparison.OrdinalIgnoreCase) ? CompressionLevel.NoCompression : CompressionLevel.Optimal);
						using FileStream fileStream2 = File.OpenRead(Path.Combine(staging, item2.path));
						using Stream output = zipArchiveEntry.Open();
						if (fileStream2.Length != item2.bytes)
						{
							throw new IOException("Staged file changed before compression.");
						}
						CopyBounded(fileStream2, output, item2.bytes, cancel, progress, "Compressing " + item2.path);
						if (fileStream2.Length != item2.bytes)
						{
							throw new IOException("Staged file changed during compression.");
						}
					}
				}
				fileStream.Flush(flushToDisk: true);
			}
			using (FileStream stream = File.OpenRead(partial))
			{
				using ZipArchive zipArchive2 = new ZipArchive(stream, ZipArchiveMode.Read);
				if (zipArchive2.Entries.Count != list.Count)
				{
					throw new IOException("ZIP entry count mismatch.");
				}
				HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
				foreach (FileReceipt item3 in list)
				{
					if (cancel())
					{
						throw new OperationCanceledException();
					}
					ZipArchiveEntry entry = zipArchive2.GetEntry(item3.path);
					if (entry == null || !hashSet.Add(entry.FullName) || entry.Length != item3.bytes)
					{
						throw new IOException("ZIP entry mismatch.");
					}
					using Stream input = entry.Open();
					if (Hash(input, cancel, progress) != item3.sha256)
					{
						throw new IOException("ZIP hash mismatch.");
					}
				}
			}
			if (cancel())
			{
				throw new OperationCanceledException();
			}
			Action action = delegate
			{
				File.Move(partial, final);
				if (published != null)
				{
					published();
				}
			};
			if (admit == null)
			{
				action();
			}
			else
			{
				admit(action);
			}
		}

		public static int VerifyWorld(string directory, uint generation, Action<string> warning = null, long? worldUid = null)
		{
			string text = "_main." + generation;
			string[] array = new string[4] { ".ok", ".fwl2", ".db2", ".chunks" };
			foreach (string text2 in array)
			{
				string text3 = Path.Combine(directory, text + text2);
				if (!File.Exists(text3) || new FileInfo(text3).Length == 0L)
				{
					throw new IOException("Committed world generation is missing " + text2);
				}
			}
			using (BinaryReader binaryReader = new BinaryReader(File.OpenRead(Path.Combine(directory, text + ".ok"))))
			{
				if (binaryReader.BaseStream.Length != 4 || binaryReader.ReadInt32() != 42)
				{
					throw new IOException("Invalid save commit marker.");
				}
			}
			using (BinaryReader binaryReader2 = new BinaryReader(File.OpenRead(Path.Combine(directory, text + ".fwl2"))))
			{
				int num = binaryReader2.ReadInt32();
				if (num < 4 || num != binaryReader2.BaseStream.Length - 4 || binaryReader2.ReadInt32() != 42)
				{
					throw new IOException("Invalid world metadata envelope.");
				}
				binaryReader2.ReadString();
				binaryReader2.ReadString();
				binaryReader2.ReadInt32();
				long num2 = binaryReader2.ReadInt64();
				if (worldUid.HasValue && worldUid.Value != num2)
				{
					throw new IOException("Staged metadata belongs to another world.");
				}
			}
			using (BinaryReader binaryReader3 = new BinaryReader(File.OpenRead(Path.Combine(directory, text + ".db2"))))
			{
				if (binaryReader3.ReadInt32() != 42)
				{
					throw new IOException("Unsupported DB2 format.");
				}
			}
			using BinaryReader binaryReader4 = new BinaryReader(File.OpenRead(Path.Combine(directory, text + ".chunks")));
			if (binaryReader4.ReadUInt16() != 42)
			{
				throw new IOException("Unsupported chunk mapping format.");
			}
			int num3 = binaryReader4.ReadInt32();
			int num4 = binaryReader4.ReadInt32();
			if (num3 < 0 || num4 < 0 || binaryReader4.BaseStream.Length != 10 + (long)num4 * 11L)
			{
				throw new IOException("Invalid chunk mapping size.");
			}
			long num5 = 0L;
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			for (int j = 0; j < num4; j++)
			{
				ushort num6 = binaryReader4.ReadUInt16();
				byte b = binaryReader4.ReadByte();
				uint num7 = binaryReader4.ReadUInt32();
				int num8 = binaryReader4.ReadInt32();
				if (num8 < 0)
				{
					throw new IOException("Invalid chunk object count.");
				}
				num5 += num8;
				string text4 = (num6 >> 8).ToString("x2") + "_" + (num6 & 0xFF).ToString("x2") + "__" + b + "_" + num7 + ".chunk";
				string text5 = Path.Combine(directory, text4);
				if (!hashSet.Add(text4) || !File.Exists(text5) || new FileInfo(text5).Length == 0L)
				{
					throw new IOException("Referenced world chunk missing or duplicate: " + text4);
				}
			}
			if (num5 != num3)
			{
				warning?.Invoke("Chunk mapping header object count differs from serialized chunks (" + num3 + " versus " + num5 + "). Native resizing can cause this; all referenced chunk files are present.");
			}
			return num4;
		}
	}
	internal sealed class Capture : IDisposable
	{
		internal readonly string Staging;

		internal readonly string Final;

		internal readonly Dictionary<string, object> Manifest = new Dictionary<string, object>();

		internal readonly List<string> Issues = new List<string>();

		private readonly List<PinnedFile> files = new List<PinnedFile>();

		private readonly HashSet<string> destinations = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private readonly List<FileReceipt> receipts = new List<FileReceipt>();

		private readonly List<Tuple<string, string>> pluginFiles = new List<Tuple<string, string>>();

		private bool partialOwned;

		private bool localWorldRequested;

		internal bool WorldRequested;

		internal bool WorldIncluded;

		internal bool Published { get; private set; }

		internal Capture(string output, SessionContext session, string character, string journals, string[] chatHooks, string[] diagnostics)
		{
			ArchiveCore.RejectLinks(output);
			Directory.CreateDirectory(output);
			session.Refresh();
			DateTimeOffset now = DateTimeOffset.Now;
			Final = LogsetNames.Reserve(output, character, session.WorldName, now);
			Staging = Path.Combine(output, ".capture-" + Guid.NewGuid().ToString("N"));
			try
			{
				Directory.CreateDirectory(Staging);
				Manifest["schema"] = "valheim.logexporter/1";
				Manifest["version"] = "1.0.1";
				Manifest["createdUtc"] = now.UtcDateTime.ToString("o");
				Manifest["createdLocal"] = now.ToString("o");
				Manifest["process"] = ExportPlugin.ProcessId;
				Manifest["exportId"] = Guid.NewGuid().ToString("N");
				Manifest["characterName"] = character;
				Manifest["worldName"] = session.WorldName;
				Manifest["session"] = session.Capture();
				Manifest["chatHooks"] = chatHooks;
				Manifest["recentEwpDiagnostics"] = diagnostics;
				Manifest["issues"] = Issues;
				Manifest["captureScope"] = "Retained logs from byte 0 to pinned endpoints, selected EWP/EWD files and loaded plugin identities. Files can span worlds and sessions. No character save files or plugin binaries are included. Buffered tails and deleted files cannot be recovered.";
				Manifest["chatScope"] = "Say and shout are recorded independently of EWP. local-request is an attempted send, client-received is a received chat call, server-routed is a native routed packet. Native permission fan-out can send separate packets to multiple recipients; route IDs and targets are retained. Recording does not certify delivery. Whisper and ping are excluded.";
				Manifest["journalRetention"] = 32 + " segments per journal, rotated at " + 4194304L + " bytes. Only Log Exporter journal files are rotated.";
				Manifest["world"] = new
				{
					requested = false,
					included = false
				};
				Manifest["server"] = new
				{
					requested = false,
					included = false
				};
				Try("BepInEx logs", delegate
				{
					foreach (string item in Directory.EnumerateFiles(Paths.BepInExRootPath, "LogOutput*", SearchOption.TopDirectoryOnly))
					{
						if (item.EndsWith(".log", StringComparison.OrdinalIgnoreCase) || item.Contains(".log."))
						{
							Pin(item, "logs/bepinex/" + Path.GetFileName(item), exact: false);
						}
					}
				});
				AddTree(journals, "logs/exporter", (string p) => p.EndsWith(".jsonl", StringComparison.OrdinalIgnoreCase), exact: false);
				List<object> list = new List<object>();
				PluginInfo[] array = Chainloader.PluginInfos.Values.ToArray();
				foreach (PluginInfo val in array)
				{
					Assembly assembly = (((Object)(object)val.Instance == (Object)null) ? null : ((object)val.Instance).GetType().Assembly);
					list.Add(new
					{
						guid = val.Metadata.GUID,
						name = val.Metadata.Name,
						version = val.Metadata.Version.ToString(),
						assemblyName = ((assembly == null) ? null : assembly.GetName().Name),
						mvid = ((assembly == null) ? null : assembly.ManifestModule.ModuleVersionId.ToString("D"))
					});
					if (assembly != null && !string.IsNullOrEmpty(assembly.Location))
					{
						pluginFiles.Add(Tuple.Create(val.Metadata.GUID, assembly.Location));
					}
					if (val.Metadata.GUID == "expand_world_prefabs" || val.Metadata.GUID == "expand_world_data" || val.Metadata.GUID == "com.jpvalheim.valheimlogexporter")
					{
						BaseUnityPlugin instance = val.Instance;
						if ((Object)(object)instance != (Object)null && File.Exists(instance.Config.ConfigFilePath))
						{
							Pin(instance.Config.ConfigFilePath, "config/" + Path.GetFileName(instance.Config.ConfigFilePath), exact: true);
						}
					}
				}
				Manifest["plugins"] = list;
				Manifest["pluginIdentityScope"] = "Loaded assembly MVIDs and separately observed disk SHA-256 hashes; neither proves successful plugin initialization.";
				Assembly assembly2 = typeof(ZNet).Assembly;
				Manifest["nativeAssembly"] = new
				{
					name = assembly2.GetName().Name,
					mvid = assembly2.ManifestModule.ModuleVersionId.ToString("D"),
					assemblyVersion = assembly2.GetName().Version.ToString()
				};
				string root = Path.Combine(Paths.ConfigPath, "expand_world");
				if (Chainloader.PluginInfos.TryGetValue("expand_world_prefabs", out var value) && (Object)(object)value.Instance != (Object)null)
				{
					Assembly assembly3 = ((object)value.Instance).GetType().Assembly;
					Try("EWP directory", delegate
					{
						string text = (assembly3.GetType("Service.Yaml", throwOnError: false)?.GetField("BaseDirectory", BindingFlags.Static | BindingFlags.Public))?.GetValue(null) as string;
						if (!string.IsNullOrWhiteSpace(text))
						{
							root = text;
						}
					});
					Try("EWP in-memory data", delegate
					{
						if (!((assembly3.GetType("Service.DataStorage", throwOnError: false)?.GetField("Database", BindingFlags.Static | BindingFlags.NonPublic))?.GetValue(null) is Dictionary<string, string> dictionary))
						{
							throw new InvalidOperationException("This EWP build does not expose the expected data dictionary.");
						}
						File.WriteAllText(Path.Combine(Staging, "ewp-in-memory.json"), JsonConvert.SerializeObject((object)new Dictionary<string, string>(dictionary), (Formatting)1));
						Manifest["ewpDataScope"] = "Read-only main-thread copy of EWP's profile-global data dictionary. Disk files and world save are captured separately; no atomic transaction is claimed.";
					});
				}
				AddTree(Path.Combine(root, "logs"), "logs/ewp", (string p) => true, exact: false);
				AddTree(root, "config/expand_world", IsProjectFile, exact: true, Path.Combine(root, "logs"));
				AddTree(Path.Combine(Paths.ConfigPath, "data"), "config/data", IsProjectFile, exact: true);
				AddTree(Path.Combine(Paths.ConfigPath, "PlanBuild"), "config/PlanBuild", IsProjectFile, exact: true);
				AddTree(Path.Combine(Paths.ConfigPath, "blueprints"), "config/blueprints", IsProjectFile, exact: true);
				Manifest["ewpDirectory"] = root;
			}
			catch
			{
				try
				{
					Dispose();
				}
				catch
				{
				}
				throw;
			}
		}

		private static bool IsProjectFile(string p)
		{
			string text = Path.GetExtension(p).ToLowerInvariant();
			switch (text)
			{
			default:
				return text == ".vbuild";
			case ".yaml":
			case ".yml":
			case ".cfg":
			case ".blueprint":
				return true;
			}
		}

		private void Try(string label, Action capture)
		{
			try
			{
				capture();
			}
			catch (Exception ex)
			{
				Issues.Add(label + ": " + ex.Message);
			}
		}

		private void Pin(string source, string relative, bool exact)
		{
			if (files.Count >= 10000)
			{
				throw new IOException("Report input limit reached (10,000 files).");
			}
			if (destinations.Add(relative))
			{
				Try(relative, delegate
				{
					files.Add(new PinnedFile(source, relative, exact));
				});
			}
		}

		private void AddTree(string root, string logical, Func<string, bool> include, bool exact, string excluded = null)
		{
			Try(logical, delegate
			{
				if (!Directory.Exists(root))
				{
					return;
				}
				foreach (string item in ArchiveCore.Files(root))
				{
					if ((excluded == null || !Path.GetFullPath(item).StartsWith(Path.GetFullPath(excluded).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)) && include(item))
					{
						Pin(item, logical + "/" + ArchiveCore.Relative(root, item), exact);
					}
				}
			});
		}

		internal void IncludeWorld(bool requested, Action<string> progress)
		{
			WorldRequested = requested;
			localWorldRequested = requested;
			if (!requested)
			{
				return;
			}
			string text = Path.Combine(Staging, "world");
			try
			{
				object value = WorldSnapshot.Stage(text, Path.GetDirectoryName(Final), progress);
				Manifest["world"] = value;
				WorldIncluded = true;
			}
			catch (Exception ex)
			{
				Manifest["world"] = new
				{
					requested = true,
					included = false,
					reason = ex.Message
				};
				Issues.Add("World snapshot omitted: " + ex.Message);
				if (Directory.Exists(text))
				{
					Directory.Delete(text, recursive: true);
				}
			}
		}

		internal void Stage(Func<bool> cancel, Action<string> progress)
		{
			try
			{
				foreach (PinnedFile file in files)
				{
					if (cancel())
					{
						throw new OperationCanceledException();
					}
					progress("Collecting " + file.Relative);
					try
					{
						receipts.Add(file.Copy(Staging, cancel));
					}
					catch (OperationCanceledException)
					{
						throw;
					}
					catch (Exception ex2)
					{
						Issues.Add(file.Relative + ": " + ex2.Message);
						string path = Path.Combine(Staging, file.Relative);
						if (File.Exists(path))
						{
							File.Delete(path);
						}
					}
				}
				List<object> list = new List<object>();
				foreach (Tuple<string, string> pluginFile in pluginFiles)
				{
					if (cancel())
					{
						throw new OperationCanceledException();
					}
					try
					{
						list.Add(new
						{
							guid = pluginFile.Item1,
							fileName = Path.GetFileName(pluginFile.Item2),
							sha256 = ArchiveCore.Hash(pluginFile.Item2, cancel, null)
						});
					}
					catch (OperationCanceledException)
					{
						throw;
					}
					catch (Exception ex4)
					{
						Issues.Add("Plugin disk identity " + pluginFile.Item1 + ": " + ex4.Message);
					}
				}
				Manifest["pluginDiskFiles"] = list;
				Manifest["pinnedInputs"] = receipts;
				Manifest["stagedUtc"] = DateTime.UtcNow.ToString("o");
			}
			finally
			{
				foreach (PinnedFile file2 in files)
				{
					file2.Dispose();
				}
				files.Clear();
			}
		}

		internal string WriteZip(object serverResult, Func<bool> cancel, Action<string> progress)
		{
			Manifest["server"] = serverResult ?? new
			{
				requested = false,
				included = false
			};
			if (WorldRequested && !WorldIncluded && File.Exists(Path.Combine(Staging, "server.zip")))
			{
				Try("Server world files", delegate
				{
					WorldIncluded = WorldEvidence.InArchive(Path.Combine(Staging, "server.zip"));
				});
			}
			if (WorldRequested && !localWorldRequested)
			{
				Manifest["world"] = new
				{
					requested = true,
					included = WorldIncluded,
					location = (WorldIncluded ? "server.zip" : null)
				};
			}
			Manifest["worldRequested"] = WorldRequested;
			Manifest["worldIncluded"] = WorldIncluded;
			if (WorldRequested && !WorldIncluded)
			{
				Issues.Add("Requested world files are missing. This ZIP is incomplete.");
			}
			File.WriteAllText(Path.Combine(Staging, "manifest.json"), JsonConvert.SerializeObject((object)Manifest, (Formatting)1));
			progress("Writing ZIP...");
			ArchiveCore.ZipVerified(Staging, Final + ".partial", Final, cancel, delegate(string p)
			{
				progress(p.StartsWith("Verifying", StringComparison.Ordinal) ? "Verifying ZIP..." : "Writing ZIP...");
			}, null, delegate
			{
				partialOwned = true;
			}, delegate
			{
				partialOwned = false;
				Published = true;
			});
			return Final;
		}

		public void Dispose()
		{
			try
			{
				foreach (PinnedFile file in files)
				{
					file.Dispose();
				}
				files.Clear();
				if (partialOwned && File.Exists(Final + ".partial"))
				{
					File.Delete(Final + ".partial");
				}
				if (Directory.Exists(Staging))
				{
					Directory.Delete(Staging, recursive: true);
				}
			}
			finally
			{
				LogsetNames.Release(Final);
			}
		}
	}
	internal sealed class ChatPacket
	{
		internal Vector3 position;

		internal int type;

		internal UserInfo user;

		internal string text;

		internal bool positioned;

		internal static bool RecordedType(int type)
		{
			if (type != 1)
			{
				return type == 2;
			}
			return true;
		}

		internal static ChatPacket Read(bool positioned, ZPackage original)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Expected O, but got Unknown
			ZPackage val = new ZPackage(original.GetArray());
			ChatPacket chatPacket = new ChatPacket
			{
				positioned = positioned,
				position = (positioned ? val.ReadVector3() : Vector3.zero),
				type = val.ReadInt()
			};
			if (!RecordedType(chatPacket.type))
			{
				return null;
			}
			chatPacket.user = new UserInfo();
			chatPacket.user.Deserialize(ref val);
			chatPacket.text = val.ReadString();
			if (val.GetPos() != val.Size())
			{
				throw new IOException("Unexpected chat packet tail.");
			}
			return chatPacket;
		}
	}
	internal sealed class ChatRecorder : IDisposable
	{
		private static ChatRecorder active;

		private static readonly int SayHash = StringExtensionMethods.GetStableHashCode("Say");

		private static readonly int GlobalChatHash = StringExtensionMethods.GetStableHashCode("ChatMessage");

		private readonly Harmony harmony = new Harmony("com.jpvalheim.valheimlogexporter.chat");

		private readonly Journal journal;

		private readonly SessionContext session;

		private readonly Action<string> warning;

		private readonly HashSet<string> routed = new HashSet<string>();

		private readonly Queue<string> order = new Queue<string>();

		private bool reported;

		internal readonly List<string> Hooks = new List<string>();

		internal string Failure { get; private set; }

		internal ChatRecorder(Journal journal, SessionContext session, Action<string> warning)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected O, but got Unknown
			this.journal = journal;
			this.session = session;
			this.warning = warning;
			active = this;
			Patch(typeof(Chat), "SendText", "Sending");
			Patch(typeof(Chat), "OnNewChatMessage", "Received");
			Patch(typeof(ZRoutedRpc), "RouteRPC", "Routed");
			Patch(typeof(ZRoutedRpc), "HandleRoutedRPC", "Routed");
		}

		private void Patch(Type type, string method, string handler)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			try
			{
				MethodInfo methodInfo = AccessTools.Method(type, method, (Type[])null, (Type[])null);
				if (methodInfo == null)
				{
					throw new MissingMethodException(type.Name, method);
				}
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ChatRecorder), handler, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Hooks.Add(type.Name + "." + method + ": installed");
			}
			catch (Exception ex)
			{
				Hooks.Add(type.Name + "." + method + ": unavailable: " + ex.Message);
				warning("Chat recording hook unavailable: " + type.Name + "." + method + ": " + ex.Message);
			}
		}

		internal void Reset()
		{
			routed.Clear();
			order.Clear();
		}

		internal static bool RecordedType(int type)
		{
			return ChatPacket.RecordedType(type);
		}

		private void Write(string source, long peer, Vector3 position, int type, UserInfo user, string text, object route = null)
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			if (RecordedType(type))
			{
				session.Refresh();
				journal.Write("chat", session.Id, new
				{
					source = source,
					channel = ((type == 2) ? "shout" : "say"),
					senderPeerId = peer,
					playerName = user?.Name,
					platformUserId = ((object)Unsafe.As<PlatformUserID, PlatformUserID>(ref user?.UserId)/*cast due to .constrained prefix*/).ToString(),
					position = SessionContext.Position(position),
					text = text,
					route = route
				});
			}
		}

		private static void Safely(Action<ChatRecorder> record)
		{
			ChatRecorder chatRecorder = active;
			if (chatRecorder == null)
			{
				return;
			}
			try
			{
				record(chatRecorder);
			}
			catch (Exception ex)
			{
				if (!chatRecorder.reported)
				{
					chatRecorder.reported = true;
					chatRecorder.Failure = ex.Message;
					chatRecorder.warning("Chat recording failed: " + ex.Message);
				}
			}
		}

		private static void Sending(Type type, string text)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected I4, but got Unknown
			if (RecordedType((int)type) && Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				Safely(delegate(ChatRecorder r)
				{
					//IL_0015: Unknown result type (might be due to invalid IL or missing references)
					//IL_001b: Unknown result type (might be due to invalid IL or missing references)
					//IL_0031: Expected I4, but got Unknown
					r.Write("local-request", ZNet.GetUID(), ((Component)Player.m_localPlayer).transform.position, (int)type, UserInfo.GetLocalUser(), text);
				});
			}
		}

		private static void Received(long senderID, Vector3 pos, Type type, UserInfo sender, string text)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: 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_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected I4, but got Unknown
			if (RecordedType((int)type) && (!Object.op_Implicit((Object)(object)ZNet.instance) || senderID != ZNet.GetUID()))
			{
				Safely(delegate(ChatRecorder r)
				{
					//IL_000d: Unknown result type (might be due to invalid IL or missing references)
					//IL_0013: Unknown result type (might be due to invalid IL or missing references)
					//IL_002a: Expected I4, but got Unknown
					r.Write("client-received", senderID, pos, (int)type, sender, text);
				});
			}
		}

		private static void Routed(RoutedRPCData __0)
		{
			if (!Object.op_Implicit((Object)(object)ZNet.instance) || !ZNet.instance.IsServer() || __0.m_senderPeerID == ZNet.GetUID())
			{
				return;
			}
			bool say = __0.m_methodHash == SayHash && !((ZDOID)(ref __0.m_targetZDO)).IsNone();
			bool shout = __0.m_methodHash == GlobalChatHash && ((ZDOID)(ref __0.m_targetZDO)).IsNone();
			if (!say && !shout)
			{
				return;
			}
			Safely(delegate(ChatRecorder r)
			{
				//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
				//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
				//IL_0102: Unknown result type (might be due to invalid IL or missing references)
				//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
				//IL_015b: Unknown result type (might be due to invalid IL or missing references)
				//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
				//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
				r.session.Refresh();
				string item = r.session.Id + ":" + __0.m_senderPeerID + ":" + __0.m_msgID;
				if (r.routed.Add(item))
				{
					r.order.Enqueue(item);
					if (r.order.Count > 512)
					{
						r.routed.Remove(r.order.Dequeue());
					}
					ChatPacket chatPacket = ChatPacket.Read(shout, __0.m_parameters);
					if (chatPacket != null)
					{
						Vector3 position = chatPacket.position;
						if (say && ZDOMan.instance != null)
						{
							ZDO zDO = ZDOMan.instance.GetZDO(__0.m_targetZDO);
							if (zDO != null)
							{
								position = zDO.GetPosition();
							}
						}
						r.Write("server-routed", __0.m_senderPeerID, position, chatPacket.type, chatPacket.user, chatPacket.text, new
						{
							messageId = __0.m_msgID,
							targetPeerId = __0.m_targetPeerID,
							targetZdo = ((object)Unsafe.As<ZDOID, ZDOID>(ref __0.m_targetZDO)/*cast due to .constrained prefix*/).ToString(),
							positionKnown = (shout || (ZDOMan.instance != null && ZDOMan.instance.GetZDO(__0.m_targetZDO) != null))
						});
					}
				}
			});
		}

		public void Dispose()
		{
			harmony.UnpatchSelf();
			if (active == this)
			{
				active = null;
			}
			Reset();
		}
	}
	internal sealed class Diagnostics : ILogListener, IDisposable
	{
		private readonly Journal journal;

		private readonly Func<string> session;

		private readonly object gate = new object();

		private readonly Queue<string> recent = new Queue<string>();

		internal string Failure { get; private set; }

		internal Diagnostics(Journal journal, Func<string> session)
		{
			this.journal = journal;
			this.session = session;
		}

		public void LogEvent(object sender, LogEventArgs e)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			if ((e.Level & 7) == 0)
			{
				return;
			}
			string sourceName = e.Source.SourceName;
			string text = Convert.ToString(e.Data);
			string text2 = (sourceName + " " + text).ToLowerInvariant();
			if (!text2.Contains("expand world") && !text2.Contains("expand_world") && !text2.Contains("ewp"))
			{
				return;
			}
			lock (gate)
			{
				recent.Enqueue(sourceName + ": " + text);
				while (recent.Count > 8)
				{
					recent.Dequeue();
				}
			}
			try
			{
				journal.Write("diagnostics", session(), new
				{
					source = sourceName,
					level = ((object)e.Level/*cast due to .constrained prefix*/).ToString(),
					message = text
				});
			}
			catch (Exception ex)
			{
				Failure = "Diagnostic journal unavailable: " + ex.Message;
			}
		}

		internal string[] Capture()
		{
			lock (gate)
			{
				return recent.ToArray();
			}
		}

		internal void Reset()
		{
			lock (gate)
			{
				recent.Clear();
			}
		}

		public void Dispose()
		{
		}
	}
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInPlugin("com.jpvalheim.valheimlogexporter", "Valheim Log Exporter", "1.0.1")]
	public sealed class ExportPlugin : BaseUnityPlugin
	{
		internal const string PluginVersion = "1.0.1";

		internal static readonly string ProcessId = Guid.NewGuid().ToString("N");

		private readonly SessionContext session = new SessionContext();

		private string output;

		private string journalDirectory;

		private string status = "Ready to create a ZIP.";

		private string lastExport;

		private string lastSession;

		private volatile string progress;

		private string remoteProgress;

		private string observedSession;

		private Journal journal;

		private ChatRecorder chat;

		private Diagnostics diagnostics;

		private NetworkExport network;

		private NativeExportMenu menu;

		private ConfigEntry<bool> automaticallyOpen;

		private bool includeServer = true;

		private bool includeWorld = true;

		private bool busy;

		private bool disposed;

		private bool menuFailed;

		private bool waitingServer;

		private Capture capture;

		private CancellationTokenSource cancellation;

		private Task stageJob;

		private Task<string> zipJob;

		private object serverResult;

		private string captureSession;

		private readonly Queue<string> notices = new Queue<string>();

		private void Awake()
		{
			output = Path.Combine(Paths.BepInExRootPath, "ValheimLogExporter", "Exports");
			journalDirectory = Path.Combine(Paths.BepInExRootPath, "ValheimLogExporter", "logs");
			automaticallyOpen = ((BaseUnityPlugin)this).Config.Bind<bool>("Export", "Automatically open export folder", true, "Open the folder after a ZIP is saved. Client only.");
			try
			{
				journal = new Journal(journalDirectory, ProcessId);
				diagnostics = new Diagnostics(journal, () => session.Id);
				Logger.Listeners.Add((ILogListener)(object)diagnostics);
				chat = new ChatRecorder(journal, session, delegate(string text)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)text);
				});
				journal.Write("events", session.Id, new
				{
					action = "startup",
					version = "1.0.1",
					chatHooks = chat.Hooks
				});
			}
			catch (Exception ex)
			{
				status = "Chat recording unavailable. Check LogOutput.log.";
				((BaseUnityPlugin)this).Logger.LogError((object)("Chat journal could not start: " + ex.Message));
			}
			network = new NetworkExport(session, (bool world, string character) => Prepare(world, character), delegate(string p)
			{
				remoteProgress = p;
			}, delegate(string p)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)p);
			});
			TryReadLast();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Open Log Exporter from the pause menu; logexporter export also works from the console.");
		}

		private void Start()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			new ConsoleCommand("logexporter", "Create a log ZIP: logexporter export.", (ConsoleEvent)delegate(ConsoleEventArgs args)
			{
				if (!disposed)
				{
					if (((args.Args.Length > 1) ? args.Args[1].ToLowerInvariant() : "help") == "export")
					{
						BeginExport(includeWorld, includeServer);
					}
					else
					{
						args.Context.AddString("Use logexporter export.");
					}
				}
			}, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}

		private void Update()
		{
			if (disposed)
			{
				return;
			}
			try
			{
				session.Refresh();
				if (observedSession != session.Id)
				{
					observedSession = session.Id;
					chat?.Reset();
					diagnostics?.Reset();
					try
					{
						journal?.Write("events", session.Id, new
						{
							action = "session",
							context = session.Capture()
						});
					}
					catch (Exception ex)
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)ex.Message);
					}
				}
			}
			catch (Exception ex2)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Session context unavailable: " + ex2.Message));
			}
			network.Tick();
			PollExport();
			if (!Application.isBatchMode && (!Object.op_Implicit((Object)(object)ZNet.instance) || !ZNet.instance.IsDedicated()) && Object.op_Implicit((Object)(object)Chat.instance))
			{
				while (notices.Count != 0)
				{
					((Terminal)Chat.instance).AddString(notices.Dequeue().Replace('<', '<').Replace('>', '>'));
				}
			}
		}

		private void LateUpdate()
		{
			if (disposed || Application.isBatchMode || (Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsDedicated()) || menuFailed)
			{
				return;
			}
			try
			{
				if (menu == null)
				{
					menu = new NativeExportMenu(ReadView, delegate
					{
						BeginExport(includeWorld, includeServer);
					}, OpenFolder, Cancel, delegate(bool value)
					{
						automaticallyOpen.Value = value;
					}, delegate(bool value)
					{
						includeServer = value;
					}, delegate(bool value)
					{
						includeWorld = value;
					});
				}
				menu.Tick();
			}
			catch (Exception ex)
			{
				menuFailed = true;
				menu?.Dispose();
				menu = null;
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Log Exporter window unavailable; use logexporter export in the console. " + ex.Message));
			}
		}

		private string ExportBlocked()
		{
			if (!Object.op_Implicit((Object)(object)ZNet.instance) || ZNet.instance.GetWorld() == null)
			{
				return "Load a world before creating a ZIP.";
			}
			if (!Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				return "Load your character before creating a ZIP.";
			}
			return null;
		}

		private NativeExportMenu.View ReadView()
		{
			bool flag = Object.op_Implicit((Object)(object)ZNet.instance) && !ZNet.instance.IsServer();
			return new NativeExportMenu.View
			{
				Status = (busy ? (progress ?? status) : status),
				Busy = busy,
				CanCancel = (busy && cancellation != null && !cancellation.IsCancellationRequested),
				CanOpenFolder = Directory.Exists(output),
				AutomaticallyOpenFolder = automaticallyOpen.Value,
				IncludeServer = includeServer,
				IncludeWorld = includeWorld,
				IsRemote = flag,
				Availability = ((!flag) ? "This world is hosted locally." : (network.Available ? "Server logs ready." : "Server logs not ready; local ZIP available.")),
				Blocked = ExportBlocked(),
				ChatStatus = ((chat == null || chat.Failure != null || chat.Hooks.Any((string h) => !h.EndsWith(": installed", StringComparison.Ordinal))) ? "Chat recording needs attention. Check LogOutput.log." : "Includes logs, say/shout, and project files."),
				LastArchive = ((lastExport == null) ? "" : (Path.GetFileName(lastExport) + ((lastSession == session.Id) ? "" : " · previous session")))
			};
		}

		private Capture Prepare(bool world, string character = null)
		{
			Capture capture = new Capture(output, session, character ?? Player.m_localPlayer.GetPlayerName(), journalDirectory, (chat != null) ? chat.Hooks.ToArray() : new string[1] { "Chat recorder did not start." }, diagnostics?.Capture() ?? new string[0]);
			if (chat == null || chat.Hooks.Any((string h) => !h.EndsWith(": installed", StringComparison.Ordinal)))
			{
				capture.Issues.Add("One or more chat recorder hooks are unavailable; inspect chatHooks in manifest.json.");
			}
			if (chat?.Failure != null)
			{
				capture.Issues.Add("Chat recording error: " + chat.Failure);
			}
			if (diagnostics?.Failure != null)
			{
				capture.Issues.Add(diagnostics.Failure);
			}
			capture.IncludeWorld(world, delegate(string p)
			{
				progress = p;
			});
			return capture;
		}

		private void BeginExport(bool world, bool server)
		{
			if (!busy && !disposed)
			{
				string text = ExportBlocked();
				if (text != null)
				{
					status = text;
					SayLocal(text);
					return;
				}
				busy = true;
				progress = (world ? "Saving and collecting world files...\nValheim may pause while the world is saved." : "Collecting chat and logs...");
				cancellation = new CancellationTokenSource();
				((MonoBehaviour)this).StartCoroutine(BeginAfterFrame(world, server));
			}
		}

		private IEnumerator BeginAfterFrame(bool world, bool server)
		{
			yield return null;
			yield return null;
			if (disposed)
			{
				yield break;
			}
			if (cancellation.IsCancellationRequested)
			{
				Finish(null, "Export cancelled.");
				yield break;
			}
			try
			{
				session.Refresh();
				captureSession = session.Id;
				remoteProgress = null;
				bool flag = Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer();
				capture = Prepare(world && flag);
				capture.WorldRequested = world;
				waitingServer = false;
				serverResult = new
				{
					requested = false,
					included = false
				};
				if (server && Object.op_Implicit((Object)(object)ZNet.instance) && !flag)
				{
					waitingServer = true;
					if (!network.Request(capture.Staging, world, delegate(RemoteResult result)
					{
						serverResult = result;
						waitingServer = false;
					}))
					{
						waitingServer = false;
						serverResult = new RemoteResult
						{
							worldRequested = world,
							reason = network.Availability
						};
					}
				}
				else if (world && !flag)
				{
					capture.Issues.Add("No world host is available; the report contains local evidence only.");
				}
				Capture current = capture;
				CancellationToken token = cancellation.Token;
				stageJob = Task.Run(delegate
				{
					current.Stage(() => token.IsCancellationRequested, delegate(string p)
					{
						progress = p;
					});
				});
			}
			catch (Exception ex)
			{
				Finish(null, "Export could not start: " + ex.Message);
			}
		}

		private void PollExport()
		{
			if (!busy)
			{
				return;
			}
			if (zipJob != null)
			{
				if (zipJob.IsCompleted)
				{
					try
					{
						string result = zipJob.GetAwaiter().GetResult();
						Finish(result, null);
					}
					catch (Exception ex)
					{
						Finish((capture != null && capture.Published) ? capture.Final : null, (ex is OperationCanceledException) ? "Export cancelled." : ("Export failed: " + ex.Message));
					}
				}
			}
			else
			{
				if (stageJob == null || !stageJob.IsCompleted)
				{
					return;
				}
				try
				{
					stageJob.GetAwaiter().GetResult();
				}
				catch (Exception ex2)
				{
					network.CancelRequest();
					if (!waitingServer)
					{
						Finish(null, (ex2 is OperationCanceledException) ? "Export cancelled." : ("Export failed: " + ex2.Message));
					}
					return;
				}
				if (waitingServer)
				{
					progress = (cancellation.IsCancellationRequested ? "Cancelling export..." : (remoteProgress ?? "Waiting for server evidence. Local evidence is staged."));
					return;
				}
				if (cancellation.IsCancellationRequested)
				{
					Finish(null, "Export cancelled.");
					return;
				}
				Capture current = capture;
				CancellationToken token = cancellation.Token;
				object remote = serverResult;
				zipJob = Task.Run(() => current.WriteZip(remote, () => token.IsCancellationRequested, delegate(string p)
				{
					progress = p;
				}));
			}
		}

		private void Finish(string path, string error)
		{
			busy = false;
			progress = null;
			RemoteResult remoteResult = serverResult as RemoteResult;
			if (path != null)
			{
				lastExport = path;
				lastSession = captureSession;
				bool flag = capture != null && capture.WorldRequested && !capture.WorldIncluded;
				status = (flag ? "ZIP saved without world files. See manifest.json." : (((capture != null && capture.Issues.Count != 0) || (remoteResult != null && !remoteResult.included)) ? "ZIP saved with missing files. See manifest.json." : "ZIP saved. Share it with the person helping you."));
				if (capture != null)
				{
					foreach (string issue in capture.Issues)
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)issue);
					}
				}
				if (remoteResult != null && remoteResult.reason != null)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("Server export: " + remoteResult.reason));
				}
				TryRemember(path);
				if (automaticallyOpen.Value)
				{
					Reveal(path);
				}
			}
			else
			{
				status = error ?? "Export stopped. Previous ZIPs are unchanged.";
			}
			notices.Enqueue("Log Exporter: " + status + ((path == null) ? "" : ("\n" + Path.GetFileName(path))));
			((BaseUnityPlugin)this).Logger.LogInfo((object)status);
			network.CancelRequest();
			try
			{
				capture?.Dispose();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Staging cleanup failed: " + ex.Message));
			}
			capture = null;
			stageJob = null;
			zipJob = null;
			cancellation?.Dispose();
			cancellation = null;
			serverResult = null;
			waitingServer = false;
		}

		private void Cancel()
		{
			cancellation?.Cancel();
			network.CancelRequest();
			progress = "Cancelling export. Previous ZIPs are unchanged...";
		}

		private void SayLocal(string text)
		{
			notices.Enqueue(text);
			((BaseUnityPlugin)this).Logger.LogInfo((object)text);
		}

		private void TryRemember(string path)
		{
			try
			{
				string text = Path.Combine(output, ".last-export");
				string text2 = text + ".tmp";
				ArchiveCore.RejectLinks(text);
				ArchiveCore.RejectLinks(text2);
				File.WriteAllText(text2, Path.GetFileName(path));
				if (File.Exists(text))
				{
					File.Delete(text);
				}
				File.Move(text2, text);
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Last export marker could not be saved: " + ex.Message));
			}
		}

		private void TryReadLast()
		{
			try
			{
				string path = Path.Combine(output, ".last-export");
				ArchiveCore.RejectLinks(path);
				if (!File.Exists(path))
				{
					return;
				}
				string text = File.ReadAllText(path).Trim();
				if (!(text != Path.GetFileName(text)) && text.EndsWith(".zip", StringComparison.Ordinal))
				{
					string path2 = Path.Combine(output, text);
					ArchiveCore.RejectLinks(path2);
					if (File.Exists(path2))
					{
						lastExport = path2;
					}
				}
			}
			catch
			{
			}
		}

		private void Reveal(string file)
		{
			if (Application.isBatchMode || (Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsDedicated()))
			{
				return;
			}
			try
			{
				if (Environment.OSVersion.Platform == PlatformID.Win32NT)
				{
					Process.Start(new ProcessStartInfo("explorer.exe", "/select,\"" + Path.GetFullPath(file) + "\"")
					{
						UseShellExecute = true
					});
				}
				else
				{
					Process.Start(new ProcessStartInfo(Path.GetDirectoryName(Path.GetFullPath(file)))
					{
						UseShellExecute = true
					});
				}
			}
			catch (Exception ex)
			{
				status += "\nFolder could not open. Use Open folder.";
				((BaseUnityPlugin)this).Logger.LogWarning((object)("ZIP saved; folder could not open: " + ex.Message + ". Open " + output + " manually."));
			}
		}

		private void OpenFolder()
		{
			if (lastExport != null && File.Exists(lastExport))
			{
				Reveal(lastExport);
			}
			else if (Directory.Exists(output) && !Application.isBatchMode)
			{
				try
				{
					Process.Start(new ProcessStartInfo(output)
					{
						UseShellExecute = true
					});
				}
				catch (Exception ex)
				{
					status = "Open " + output + " manually.";
					((BaseUnityPlugin)this).Logger.LogWarning((object)ex.Message);
				}
			}
		}

		private void OnDestroy()
		{
			disposed = true;
			cancellation?.Cancel();
			Task task = network?.PendingDownloadWork;
			Release(network);
			Release(menu);
			Release(chat);
			if (diagnostics != null)
			{
				Logger.Listeners.Remove((ILogListener)(object)diagnostics);
			}
			Release(diagnostics);
			Release(journal);
			Capture current = capture;
			Task task2 = Task.WhenAll(new Task[2]
			{
				zipJob ?? stageJob,
				task
			}.Where((Task t) => t != null));
			if (task2 != null && !task2.IsCompleted)
			{
				task2.ContinueWith(delegate
				{
					try
					{
						current?.Dispose();
					}
					catch
					{
					}
				});
				return;
			}
			try
			{
				current?.Dispose();
			}
			catch
			{
			}
		}

		private void Release(IDisposable item)
		{
			try
			{
				item?.Dispose();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Log Exporter cleanup: " + ex.Message));
			}
		}
	}
	internal sealed class Journal : IDisposable
	{
		internal const long SegmentBytes = 4194304L;

		internal const int RetainedSegments = 32;

		private readonly string directory;

		private readonly string process;

		private readonly object gate = new object();

		private readonly Dictionary<string, StreamWriter> writers = new Dictionary<string, StreamWriter>();

		private int sequence;

		private bool disposed;

		internal Journal(string directory, string process)
		{
			this.directory = directory;
			this.process = process;
			ArchiveCore.RejectLinks(directory);
			Directory.CreateDirectory(directory);
		}

		internal void Write(string kind, string session, object data)
		{
			if (kind != "chat" && kind != "events" && kind != "diagnostics")
			{
				throw new ArgumentException("Unknown journal.");
			}
			lock (gate)
			{
				if (disposed)
				{
					return;
				}
				if (writers.TryGetValue(kind, out var value) && value.BaseStream.Length >= 4194304)
				{
					value.Dispose();
					writers.Remove(kind);
					value = null;
				}
				if (value == null)
				{
					string path = directory;
					string[] obj = new string[8]
					{
						kind,
						"-",
						DateTime.UtcNow.ToString("yyyyMMddTHHmmssfff"),
						"-",
						process,
						"-",
						null,
						null
					};
					int num = ++sequence;
					obj[6] = num.ToString();
					obj[7] = ".jsonl";
					value = new StreamWriter(new FileStream(Path.Combine(path, string.Concat(obj)), FileMode.CreateNew, FileAccess.Write, FileShare.Read), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false))
					{
						AutoFlush = true
					};
					writers.Add(kind, value);
					HashSet<string> hashSet = new HashSet<string>(writers.Values.Select((StreamWriter w) => ((FileStream)w.BaseStream).Name), StringComparer.Ordinal);
					foreach (string item in Directory.GetFiles(directory, kind + "-*.jsonl").OrderByDescending(File.GetLastWriteTimeUtc).Skip(32))
					{
						if (!hashSet.Contains(item))
						{
							ArchiveCore.RejectLinks(item);
							File.Delete(item);
						}
					}
				}
				value.WriteLine(JsonConvert.SerializeObject((object)new
				{
					utc = DateTime.UtcNow.ToString("o"),
					process = process,
					session = session,
					kind = kind,
					data = data
				}));
			}
		}

		public void Dispose()
		{
			lock (gate)
			{
				disposed = true;
				foreach (StreamWriter value in writers.Values)
				{
					value.Dispose();
				}
				writers.Clear();
			}
		}
	}
	internal static class LogsetNames
	{
		private static readonly HashSet<string> reserved = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		internal static string Reserve(string directory, string character, string world, DateTimeOffset time)
		{
			if (string.IsNullOrWhiteSpace(character))
			{
				throw new IOException("The exporting character name is unavailable.");
			}
			if (string.IsNullOrWhiteSpace(world))
			{
				throw new IOException("The world name is unavailable.");
			}
			string text = SafeName(character);
			string text2 = SafeName(world);
			string text3 = time.ToString("yyyy-MM-dd HH-mm-ss", CultureInfo.InvariantCulture);
			int num = Math.Min(240, 259 - Path.GetFullPath(directory).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).Length - 1 - ".partial".Length);
			lock (reserved)
			{
				for (int i = 1; i < 10000; i++)
				{
					string text4 = ((i == 1) ? "" : (" (" + i.ToString(CultureInfo.InvariantCulture) + ")"));
					string text5 = text;
					string text6 = text2;
					string text7 = " - " + text3 + text4 + ".zip";
					if (num < "a on b".Length + text7.Length)
					{
						throw new IOException("The export folder path is too long. Use a shorter profile path.");
					}
					string text8 = text5 + " on " + text6 + text7;
					while (text8.Length > num || Encoding.UTF8.GetByteCount(text8) > 240)
					{
						if (text5.Length >= text6.Length && text5.Length > 1)
						{
							text5 = Shorter(text5);
						}
						else
						{
							text6 = Shorter(text6);
						}
						text8 = text5 + " on " + text6 + text7;
					}
					string text9 = Path.Combine(directory, text8);
					if (!reserved.Contains(text9) && !File.Exists(text9) && !Directory.Exists(text9) && !File.Exists(text9 + ".partial") && !Directory.Exists(text9 + ".partial"))
					{
						reserved.Add(text9);
						return text9;
					}
				}
			}
			throw new IOException("Could not choose a unique ZIP filename.");
		}

		internal static void Release(string path)
		{
			lock (reserved)
			{
				reserved.Remove(path);
			}
		}

		private static string SafeName(string value)
		{
			string text = new string(value.Select((char c) => (!char.IsControl(c) && "<>:\"/\\|?*".IndexOf(c) < 0) ? c : '_').ToArray()).Trim().TrimEnd('.', ' ');
			if (text.Length != 0)
			{
				return text;
			}
			return "_";
		}

		private static string Shorter(string value)
		{
			int num = value.Length - 1;
			if (num > 0 && char.IsHighSurrogate(value[num - 1]))
			{
				num--;
			}
			string text = value.Substring(0, num).TrimEnd('.', ' ');
			if (text.Length != 0)
			{
				return text;
			}
			return "_";
		}
	}
	internal sealed class NativeExportMenu : IDisposable
	{
		internal struct View
		{
			internal string Status;

			internal string Availability;

			internal string Blocked;

			internal string LastArchive;

			internal string ChatStatus;

			internal bool Busy;

			internal bool CanCancel;

			internal bool CanOpenFolder;

			internal bool IsRemote;

			internal bool AutomaticallyOpenFolder;

			internal bool IncludeServer;

			internal bool IncludeWorld;
		}

		private static NativeExportMenu active;

		private readonly Func<View> read;

		private readonly Action export;

		private readonly Action openFolder;

		private readonly Action cancel;

		private readonly Action<bool> setAutomaticallyOpenFolder;

		private readonly Action<bool> setIncludeServer;

		private readonly Action<bool> setIncludeWorld;

		private Toggle folderToggle;

		private Toggle serverToggle;

		private Toggle worldToggle;

		private readonly Harmony harmony;

		private Menu menu;

		private Button entry;

		private Button exportButton;

		private Button folderButton;

		private Button cancelButton;

		private Button backButton;

		private GameObject panel;

		private GameObject entryRow;

		private UIGroupHandler panelGroup;

		private SoftPanel backdrop;

		private TMP_Text statusText;

		private TMP_Text archiveText;

		private TMP_Text availabilityText;

		private Image heroGlow;

		private Image sweep;

		private GameObject progressTrack;

		private bool open;

		private bool menuWasVisible;

		private readonly Dictionary<TMP_Text, Func<string>> labels = new Dictionary<TMP_Text, Func<string>>();

		private int closedFrame = -1;

		private bool disposed;

		internal bool Visible
		{
			get
			{
				if (open && Object.op_Implicit((Object)(object)panel))
				{
					return panel.activeInHierarchy;
				}
				return false;
			}
		}

		private void RefreshLabels()
		{
			KeyValuePair<TMP_Text, Func<string>>[] array = labels.ToArray();
			for (int i = 0; i < array.Length; i++)
			{
				KeyValuePair<TMP_Text, Func<string>> keyValuePair = array[i];
				if (Object.op_Implicit((Object)(object)keyValuePair.Key))
				{
					keyValuePair.Key.text = keyValuePair.Value();
				}
				else
				{
					labels.Remove(keyValuePair.Key);
				}
			}
		}

		internal NativeExportMenu(Func<View> read, Action export, Action openFolder, Action cancel, Action<bool> setAutomaticallyOpenFolder, Action<bool> setIncludeServer, Action<bool> setIncludeWorld)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Expected O, but got Unknown
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Expected O, but got Unknown
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Expected O, but got Unknown
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Expected O, but got Unknown
			this.read = read;
			this.export = export;
			this.openFolder = openFolder;
			this.cancel = cancel;
			this.setAutomaticallyOpenFolder = setAutomaticallyOpenFolder;
			this.setIncludeServer = setIncludeServer;
			this.setIncludeWorld = setIncludeWorld;
			active = this;
			harmony = new Harmony("com.jpvalheim.valheimlogexporter.native-menu");
			try
			{
				harmony.Patch((MethodBase)AccessTools.Method(typeof(Menu), "Update", (Type[])null, (Type[])null), new HarmonyMethod(typeof(NativeExportMenu), "BeforeMenuUpdate", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(Menu), "UpdateNavigation", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(NativeExportMenu), "AfterNavigation", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(Menu), "Hide", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(NativeExportMenu), "AfterHide", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			catch
			{
				harmony.UnpatchSelf();
				if (active == this)
				{
					active = null;
				}
				throw;
			}
		}

		internal void Tick()
		{
			//IL_02dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0282: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b7: Unknown result type (might be due to invalid IL or missing references)
			if (disposed)
			{
				return;
			}
			Menu val = Menu.instance;
			if (!Object.op_Implicit((Object)(object)val))
			{
				val = null;
			}
			if (menu != val)
			{
				Remove();
				menu = val;
				if (Object.op_Implicit((Object)(object)menu))
				{
					Install();
				}
			}
			bool flag = Object.op_Implicit((Object)(object)menu) && ((Component)menu.m_root).gameObject.activeInHierarchy;
			if (flag && !menuWasVisible)
			{
				RefreshLabels();
			}
			menuWasVisible = flag;
			if (!Object.op_Implicit((Object)(object)menu) || !Object.op_Implicit((Object)(object)panel))
			{
				return;
			}
			if (!((Component)menu.m_root).gameObject.activeInHierarchy)
			{
				Close(restore: false);
			}
			else if (open)
			{
				View view = read();
				folderToggle.SetIsOnWithoutNotify(view.AutomaticallyOpenFolder);
				serverToggle.SetIsOnWithoutNotify(view.IncludeServer);
				((Selectable)serverToggle).interactable = !view.Busy && view.IsRemote;
				worldToggle.SetIsOnWithoutNotify(view.IncludeWorld);
				((Selectable)worldToggle).interactable = !view.Busy;
				statusText.text = Preview((!view.Busy && view.Blocked != null) ? view.Blocked : view.Status, 220);
				availabilityText.text = view.Availability;
				archiveText.text = view.LastArchive;
				((Selectable)exportButton).interactable = !view.Busy && view.Blocked == null;
				((Component)folderButton).gameObject.SetActive(!view.Busy);
				((Selectable)folderButton).interactable = view.CanOpenFolder;
				((Component)cancelButton).gameObject.SetActive(view.CanCancel);
				progressTrack.SetActive(view.Busy);
				TMP_Text[] componentsInChildren = ((Component)exportButton).GetComponentsInChildren<TMP_Text>(true);
				for (int i = 0; i < componentsInChildren.Length; i++)
				{
					componentsInChildren[i].text = (view.Busy ? "Creating ZIP" : "Create ZIP");
				}
				if (view.Busy)
				{
					float num = 0.5f + 0.5f * Mathf.Sin(Time.unscaledTime * 2.5f);
					((Graphic)heroGlow).color = new Color(1f, 0.66f, 0.2f, 0.08f + 0.08f * num);
					((Graphic)sweep).rectTransform.anchoredPosition = new Vector2(-240f + Mathf.PingPong(Time.unscaledTime * 140f, 480f), 0f);
				}
				else
				{
					((Graphic)heroGlow).color = new Color(1f, 0.66f, 0.2f, 0.12f);
				}
				LinkButtons();
				GameObject val2 = (Object.op_Implicit((Object)(object)EventSystem.current) ? EventSystem.current.currentSelectedGameObject : null);
				if (panelGroup.IsActive && !UnifiedPopup.IsVisible() && !TextInput.IsVisible() && (!Object.op_Implicit((Object)(object)val2) || !val2.activeInHierarchy || !val2.transform.IsChildOf(panel.transform) || (Object.op_Implicit((Object)(object)val2.GetComponent<Selectable>()) && !val2.GetComponent<Selectable>().IsInteractable())))
				{
					((Selectable)backButton).Select();
				}
			}
		}

		private void Install()
		{
			if (Object.op_Implicit((Object)(object)menu.m_settingsButton) && Object.op_Implicit((Object)(object)menu.m_quitDialog) && Object.op_Implicit((Object)(object)menu.menuEntriesParent))
			{
				if (!Object.op_Implicit((Object)(object)((Component)menu.menuEntriesParent).GetComponent<VerticalLayoutGroup>()))
				{
					throw new InvalidOperationException("The native pause-menu vertical layout is unavailable.");
				}
				Transform val = ((Component)menu.m_settingsButton).transform;
				while (Object.op_Implicit((Object)(object)val.parent) && (Object)(object)val.parent != (Object)(object)menu.menuEntriesParent)
				{
					val = val.parent;
				}
				if ((Object)(object)val.parent != (Object)(object)menu.menuEntriesParent || ((Component)val).GetComponentsInChildren<Button>(true).Length != 1)
				{
					throw new InvalidOperationException("The native Settings menu row could not be identified.");
				}
				entryRow = Object.Instantiate<GameObject>(((Component)val).gameObject, (Transform)(object)menu.menuEntriesParent, false);
				((Object)entryRow).name = "ValheimLogExporterRow";
				entryRow.SetActive(false);
				StripScripts(entryRow);
				entry = entryRow.GetComponentInChildren<Button>(true);
				PrepareButton(entry, () => "Log Exporter", Open);
				entryRow.transform.SetSiblingIndex(val.GetSiblingIndex() + 1);
				entryRow.SetActive(true);
				LayoutRebuilder.ForceRebuildLayoutImmediate(menu.menuEntriesParent);
				AddEntryNavigation();
			}
		}

		private void BuildPanel()
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Expected O, but got Unknown
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_035c: Unknown result type (might be due to invalid IL or missing references)
			//IL_036a: Unknown result type (might be due to invalid IL or missing references)
			//IL_038c: Unknown result type (might be due to invalid IL or missing references)
			//IL_039e: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0442: Unknown result type (might be due to invalid IL or missing references)
			//IL_044c: Unknown result type (might be due to invalid IL or missing references)
			//IL_047b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0482: Expected O, but got Unknown
			//IL_04ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_04d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_04e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_04fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0510: Unknown result type (might be due to invalid IL or missing references)
			//IL_0535: Unknown result type (might be due to invalid IL or missing references)
			//IL_056e: Unknown result type (might be due to invalid IL or missing references)
			//IL_059b: Unknown result type (might be due to invalid IL or missing references)
			//IL_05a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_05b3: Unknown result type (might be due to invalid IL or missing references)
			TMP_Text componentInChildren = ((Component)menu.m_quitDialog).GetComponentInChildren<TMP_Text>(true);
			Button settingsButton = menu.m_settingsButton;
			if (!Object.op_Implicit((Object)(object)componentInChildren) || !Object.op_Implicit((Object)(object)settingsButton))
			{
				throw new InvalidOperationException("Native export dialog templates are unavailable.");
			}
			panel = new GameObject("ValheimLogExporterDialog", new Type[1] { typeof(RectTransform) });
			panel.SetActive(false);
			panel.layer = ((Component)menu.m_quitDialog).gameObject.layer;
			panel.transform.SetParent(menu.m_quitDialog.parent, false);
			if (!Object.op_Implicit((Object)(object)panel.GetComponentInParent<Canvas>()))
			{
				throw new InvalidOperationException("The native menu canvas is unavailable.");
			}
			backdrop = new SoftPanel(panel.AddComponent<Image>());
			RectTransform component = panel.GetComponent<RectTransform>();
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(0.5f, 0.5f);
			component.anchorMax = val;
			component.anchorMin = val;
			component.pivot = new Vector2(0.5f, 0.5f);
			component.anchoredPosition = Vector2.zero;
			component.sizeDelta = new Vector2(760f, 688f);
			panel.AddComponent<ExportPanelFit>().Target = component;
			AddText(((Component)settingsButton).GetComponentInChildren<TMP_Text>(true), "Title", () => "Valheim Log Exporter", 31f, 32f, 42f, (TextAlignmentOptions)514);
			AddText(componentInChildren, "Introduction", () => "Create one ZIP to share for troubleshooting.", 20f, 91f, 32f, (TextAlignmentOptions)514);
			AddText(componentInChildren, "Contents", () => read().ChatStatus, 18f, 130f, 36f, (TextAlignmentOptions)514);
			serverToggle = BuildToggle("IncludeServer", "Include server logs", 190f, delegate(bool value)
			{
				setIncludeServer(value);
			});
			worldToggle = BuildToggle("IncludeWorld", "Include world files", 234f, delegate(bool value)
			{
				setIncludeWorld(value);
			});
			folderToggle = BuildToggle("OpenFolder", "Open folder after export", 278f, delegate(bool value)
			{
				setAutomaticallyOpenFolder(value);
			});
			Toggle[] obj = new Toggle[3] { serverToggle, worldToggle, folderToggle };
			float num = Mathf.Clamp(((IEnumerable<Toggle>)(object)obj).Max((Toggle t) => ((Component)((Component)t).transform.Find(((Object)t).name + "Label")).GetComponent<TMP_Text>().preferredWidth + 60f), 240f, 610f);
			Toggle[] array = (Toggle[])(object)obj;
			for (int num2 = 0; num2 < array.Length; num2++)
			{
				((Component)array[num2]).GetComponent<RectTransform>().sizeDelta = new Vector2(num, 44f);
			}
			exportButton = PanelButton(settingsButton, "Export", () => "Create ZIP", 0f, 320f, delegate
			{
				if (((Selectable)exportButton).interactable)
				{
					export();
				}
			});
			RectTransform component2 = ((Component)exportButton).GetComponent<RectTransform>();
			component2.sizeDelta = new Vector2(560f, 88f);
			component2.anchoredPosition = new Vector2((component2.pivot.x - 0.5f) * 560f, -320f - (1f - component2.pivot.y) * 88f);
			TMP_Text[] componentsInChildren = ((Component)exportButton).GetComponentsInChildren<TMP_Text>(true);
			for (int num2 = 0; num2 < componentsInChildren.Length; num2++)
			{
				componentsInChildren[num2].fontSize = 32f;
			}
			heroGlow = PanelImage(((Component)exportButton).transform, "ExportBacking", new Color(1f, 0.66f, 0.2f, 0.12f));
			((Component)heroGlow).transform.SetAsFirstSibling();
			Outline obj2 = ((Component)heroGlow).gameObject.AddComponent<Outline>();
			((Shadow)obj2).effectColor = new Color(0.95f, 0.65f, 0.25f, 0.7f);
			((Shadow)obj2).effectDistance = Vector2.one;
			GameObject val2 = new GameObject("ExportProgress", new Type[2]
			{
				typeof(RectTransform),
				typeof(Image)
			});
			val2.layer = panel.layer;
			val2.transform.SetParent(panel.transform, false);
			progressTrack = val2;
			RectTransform component3 = val2.GetComponent<RectTransform>();
			((Vector2)(ref val))..ctor(0.5f, 1f);
			component3.anchorMax = val;
			component3.anchorMin = val;
			component3.pivot = new Vector2(0.5f, 0.5f);
			component3.sizeDelta = new Vector2(560f, 4f);
			component3.anchoredPosition = new Vector2(0f, -424f);
			((Graphic)val2.GetComponent<Image>()).color = new Color(0.55f, 0.38f, 0.16f, 0.35f);
			((Graphic)val2.GetComponent<Image>()).raycastTarget = false;
			sweep = PanelImage(val2.transform, "ProgressLight", new Color(1f, 0.72f, 0.3f, 0.95f));
			RectTransform rectTransform = ((Graphic)sweep).rectTransform;
			((Vector2)(ref val))..ctor(0.5f, 0.5f);
			rectTransform.anchorMax = val;
			rectTransform.anchorMin = val;
			rectTransform.sizeDelta = new Vector2(80f, 4f);
			progressTrack.SetActive(false);
			statusText = AddText(componentInChildren, "Status", () => "Ready to create a ZIP.", 20f, 440f, 56f, (TextAlignmentOptions)514);
			statusText.enableAutoSizing = true;
			statusText.fontSizeMin = 16f;
			statusText.fontSizeMax = 20f;
			archiveText = AddText(componentInChildren, "LastArchive", () => "", 18f, 500f, 44f, (TextAlignmentOptions)514);
			archiveText.enableAutoSizing = true;
			archiveText.fontSizeMin = 14f;
			archiveText.fontSizeMax = 18f;
			availabilityText = AddText(componentInChildren, "ServerStatus", () => "", 16f, 552f, 32f, (TextAlignmentOptions)514);
			folderButton = PanelButton(settingsButton, "OpenFolder", () => "Open folder", -165f, 600f, openFolder);
			cancelButton = PanelButton(settingsButton, "CancelExport", () => "Cancel export", -165f, 600f, cancel);
			backButton = PanelButton(settingsButton, "Back", () => "Back", 165f, 600f, delegate
			{
				Close(restore: true);
			});
			CanvasGroup obj3 = panel.GetComponent<CanvasGroup>() ?? panel.AddComponent<CanvasGroup>();
			obj3.alpha = 1f;
			obj3.interactable = true;
			obj3.blocksRaycasts = true;
			obj3.ignoreParentGroups = true;
			panelGroup = panel.AddComponent<UIGroupHandler>();
			int num3 = 0;
			UIGroupHandler[] componentsInChildren2 = ((Component)menu).GetComponentsInChildren<UIGroupHandler>(true);
			foreach (UIGroupHandler val3 in componentsInChildren2)
			{
				num3 = Math.Max(num3, val3.m_groupPriority);
			}
			panelGroup.m_groupPriority = num3 + 1;
			panelGroup.m_defaultElement = ((Component)backButton).gameObject;
			AddText(componentInChildren, "Version", () => "Valheim Log Exporter · 1.0.1", 14f, 660f, 20f, (TextAlignmentOptions)514);
		}

		private Image PanelImage(Transform parent, string name, Color color)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name, new Type[2]
			{
				typeof(RectTransform),
				typeof(Image)
			})
			{
				layer = panel.layer
			};
			val.transform.SetParent(parent, false);
			RectTransform component = val.GetComponent<RectTransform>();
			component.anchorMin = Vector2.zero;
			component.anchorMax = Vector2.one;
			Vector2 offsetMin = (component.offsetMax = Vector2.zero);
			component.offsetMin = offsetMin;
			Image component2 = val.GetComponent<Image>();
			((Graphic)component2).color = color;
			((Graphic)component2).raycastTarget = false;
			return component2;
		}

		private Toggle BuildToggle(string name, string caption, float top, Action<bool> changed)
		{
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Expected O, but got Unknown
			//IL_0199: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_02dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0307: Unknown result type (might be due to invalid IL or missing references)
			//IL_0312: Unknown result type (might be due to invalid IL or missing references)
			//IL_0317: Unknown result type (might be due to invalid IL or missing references)
			//IL_0318: Unknown result type (might be due to invalid IL or missing references)
			//IL_031f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0326: Unknown result type (might be due to invalid IL or missing references)
			//IL_032d: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_03df: Unknown result type (might be due to invalid IL or missing references)
			//IL_0233: Unknown result type (might be due to invalid IL or missing references)
			//IL_023a: Unknown result type (might be due to invalid IL or missing references)
			//IL_024d: Unknown result type (might be due to invalid IL or missing references)
			//IL_025c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0272: Unknown result type (might be due to invalid IL or missing references)
			Toggle? obj = (Object.op_Implicit((Object)(object)menu.m_settingsPrefab) ? ((IEnumerable<Toggle>)menu.m_settingsPrefab.GetComponentsInChildren<Toggle>(true)).FirstOrDefault((Func<Toggle, bool>)((Toggle t) => Object.op_Implicit((Object)(object)t.graphic) && Object.op_Implicit((Object)(object)((Selectable)t).targetGraphic))) : null);
			if (!Object.op_Implicit((Object)(object)obj))
			{
				throw new InvalidOperationException("Native settings checkbox template is unavailable.");
			}
			Toggle val = Object.Instantiate<Toggle>(obj, panel.transform, false);
			((Object)val).name = name;
			((Component)val).gameObject.SetActive(false);
			val.onValueChanged = new ToggleEvent();
			val.group = null;
			StripScripts(((Component)val).gameObject);
			ContentSizeFitter[] componentsInChildren = ((Component)val).GetComponentsInChildren<ContentSizeFitter>(true);
			for (int num = 0; num < componentsInChildren.Length; num++)
			{
				((Behaviour)componentsInChildren[num]).enabled = false;
			}
			LayoutGroup[] componentsInChildren2 = ((Component)val).GetComponentsInChildren<LayoutGroup>(true);
			for (int num = 0; num < componentsInChildren2.Length; num++)
			{
				((Behaviour)componentsInChildren2[num]).enabled = false;
			}
			TMP_Text[] componentsInChildren3 = ((Component)val).GetComponentsInChildren<TMP_Text>(true);
			for (int num = 0; num < componentsInChildren3.Length; num++)
			{
				((Component)componentsInChildren3[num]).gameObject.SetActive(false);
			}
			Graphic[] array = ((IEnumerable<Graphic>)(object)new Graphic[2]
			{
				((Selectable)val).targetGraphic,
				val.graphic
			}).Distinct().ToArray();
			Vector2[] array2 = ((IEnumerable<Graphic>)array).Select((Func<Graphic, Vector2>)delegate(Graphic g)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_000b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0019: Unknown result type (might be due to invalid IL or missing references)
				//IL_001e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0026: Unknown result type (might be due to invalid IL or missing references)
				Rect rect = g.rectTransform.rect;
				float width = ((Rect)(ref rect)).width;
				rect = g.rectTransform.rect;
				return new Vector2(width, ((Rect)(ref rect)).height);
			}).ToArray();
			RectTransform component = ((Component)val).GetComponent<RectTransform>();
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(0.5f, 1f);
			component.anchorMax = val2;
			component.anchorMin = val2;
			component.pivot = new Vector2(0.5f, 0.5f);
			component.sizeDelta = new Vector2(610f, 44f);
			component.anchoredPosition = new Vector2(0f, 0f - top);
			((Transform)component).localScale = Vector3.one;
			for (int num2 = 0; num2 < array.Length; num2++)
			{
				Graphic val3 = array[num2];
				RectTransform rectTransform = val3.rectTransform;
				if (!((Object)(object)rectTransform == (Object)(object)component))
				{
					((Transform)rectTransform).SetParent((Transform)(object)component, false);
					((Vector2)(ref val2))..ctor(0f, 0.5f);
					rectTransform.anchorMax = val2;
					rectTransform.anchorMin = val2;
					rectTransform.pivot = new Vector2(0.5f, 0.5f);
					rectTransform.sizeDelta = array2[num2];
					rectTransform.anchoredPosition = new Vector2(20f, 0f);
					val3.raycastTarget = false;
				}
			}
			GameObject val4 = new GameObject("ExportToggleHitArea", new Type[3]
			{
				typeof(RectTransform),
				typeof(Image),
				typeof(LayoutElement)
			})
			{
				layer = panel.layer
			};
			val4.transform.SetParent((Transform)(object)component, false);
			val4.transform.SetAsFirstSibling();
			RectTransform component2 = val4.GetComponent<RectTransform>();
			component2.anchorMin = Vector2.zero;
			component2.anchorMax = Vector2.one;
			val2 = (component2.offsetMin = (component2.offsetMax = Vector2.zero));
			Image component3 = val4.GetComponent<Image>();
			((Graphic)component3).color = Color.clear;
			((Graphic)component3).raycastTarget = true;
			((Graphic)component3).canvasRenderer.cullTransparentMesh = false;
			val4.GetComponent<LayoutElement>().ignoreLayout = true;
			TMP_Text obj2 = AddText(((Component)menu.m_quitDialog).GetComponentInChildren<TMP_Text>(true), name + "Label", () => caption, 18f, 0f, 48f, (TextAlignmentOptions)4097);
			obj2.transform.SetParent((Transform)(object)component, false);
			RectTransform rectTransform2 = obj2.rectTransform;
			rectTransform2.anchorMin = Vector2.zero;
			rectTransform2.anchorMax = Vector2.one;
			rectTransform2.offsetMin = new Vector2(48f, 0f);
			rectTransform2.offsetMax = new Vector2(-12f, 0f);
			obj2.textWrappingMode = (TextWrappingModes)0;
			CanvasGroup[] componentsInChildren4 = ((Component)val).GetComponentsInChildren<CanvasGroup>(true);
			foreach (CanvasGroup obj3 in componentsInChildren4)
			{
				obj3.alpha = 1f;
				obj3.interactable = true;
				obj3.blocksRaycasts = true;
			}
			((Behaviour)val).enabled = true;
			((Selectable)val).interactable = true;
			((UnityEvent<bool>)(object)val.onValueChanged).AddListener((UnityAction<bool>)delegate(bool value)
			{
				changed(value);
			});
			((Component)val).gameObject.SetActive(true);
			return val;
		}

		private TMP_Text AddText(TMP_Text template, string name, Func<string> content, float size, float top, float height, TextAlignmentOptions align)
		{
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			TMP_Text val = Object.Instantiate<TMP_Text>(template, panel.transform, false);
			((Object)val).name = name;
			StripScripts(((Component)val).gameObject);
			((Behaviour)val).enabled = true;
			((Component)val).gameObject.SetActive(true);
			ContentSizeFitter[] componentsInChildren = ((Component)val).GetComponentsInChildren<ContentSizeFitter>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				((Behaviour)componentsInChildren[i]).enabled = false;
			}
			labels[val] = content;
			val.text = content();
			val.fontSize = size;
			val.enableAutoSizing = false;
			val.alignment = align;
			val.textWrappingMode = (TextWrappingModes)1;
			val.overflowMode = (TextOverflowModes)1;
			((Graphic)val).raycastTarget = false;
			val.richText = false;
			RectTransform rectTransform = val.rectTransform;
			rectTransform.anchorMin = new Vector2(0f, 1f);
			rectTransform.anchorMax = new Vector2(1f, 1f);
			rectTransform.pivot = new Vector2(0.5f, 1f);
			rectTransform.offsetMin = new Vector2(55f, 0f - top - height);
			rectTransform.offsetMax = new Vector2(-55f, 0f - top);
			((Transform)rectTransform).localScale = Vector3.one;
			return val;
		}

		private Button PanelButton(Button source, string name, Func<string> label, float x, float top, Action action)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			RectTransform component = ((Component)source).GetComponent<RectTransform>();
			Rect rect = component.rect;
			float width = ((Rect)(ref rect)).width;
			rect = component.rect;
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(width, ((Rect)(ref rect)).height);
			Button obj = CloneButton(source, panel.transform, name, label, action);
			ContentSizeFitter component2 = ((Component)obj).GetComponent<ContentSizeFitter>();
			if (Object.op_Implicit((Object)(object)component2))
			{
				((Behaviour)component2).enabled = false;
			}
			RectTransform component3 = ((Component)obj).GetComponent<RectTransform>();
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(0.5f, 1f);
			component3.anchorMax = val2;
			component3.anchorMin = val2;
			component3.sizeDelta = val;
			((Transform)component3).localScale = Vector3.one;
			component3.anchoredPosition = new Vector2(x + (component3.pivot.x - 0.5f) * val.x, 0f - top - (1f - component3.pivot.y) * val.y);
			return obj;
		}

		private static Button CloneButton(Button source, Transform parent, string name, Func<string> label, Action action)
		{
			Button obj = Object.Instantiate<Button>(source, parent, false);
			((Object)obj).name = name;
			((Component)obj).gameObject.SetActive(false);
			StripScripts(((Component)obj).gameObject);
			PrepareButton(obj, label, action);
			return obj;
		}

		private static void PrepareButton(Button button, Func<string> label, Action action)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Expected O, but got Unknown
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Expected O, but got Unknown
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			button.onClick = new ButtonClickedEvent();
			((UnityEvent)button.onClick).AddListener((UnityAction)delegate
			{
				action();
			});
			CanvasGroup[] componentsInChildren = ((Component)button).GetComponentsInChildren<CanvasGroup>(true);
			foreach (CanvasGroup obj in componentsInChildren)
			{
				obj.alpha = 1f;
				obj.interactable = true;
				obj.blocksRaycasts = true;
			}
			Graphic[] componentsInChildren2 = ((Component)button).GetComponentsInChildren<Graphic>(true);
			for (int num = 0; num < componentsInChildren2.Length; num++)
			{
				componentsInChildren2[num].raycastTarget = false;
			}
			GameObject val = new GameObject("ExportHitArea", new Type[3]
			{
				typeof(RectTransform),
				typeof(Image),
				typeof(LayoutElement)
			});
			val.layer = ((Component)button).gameObject.layer;
			val.transform.SetParent(((Component)button).transform, false);
			val.transform.SetAsFirstSibling();
			RectTransform component = val.GetComponent<RectTransform>();
			component.anchorMin = Vector2.zero;
			component.anchorMax = Vector2.one;
			Vector2 offsetMin = (component.offsetMax = Vector2.zero);
			component.offsetMin = offsetMin;
			Image component2 = val.GetComponent<Image>();
			((Graphic)component2).color = Color.clear;
			((Graphic)component2).raycastTarget = true;
			component2.alphaHitTestMinimumThreshold = 0f;
			val.GetComponent<LayoutElement>().ignoreLayout = true;
			((Graphic)component2).canvasRenderer.cullTransparentMesh = false;
			((Selectable)button).interactable = true;
			((Component)button).gameObject.SetActive(true);
			TMP_Text[] componentsInChildren3 = ((Component)button).GetComponentsInChildren<TMP_Text>(true);
			foreach (TMP_Text val2 in componentsInChildren3)
			{
				if (active != null)
				{
					active.labels[val2] = label;
				}
				val2.text = label();
				val2.textWrappingMode = (TextWrappingModes)0;
			}
		}

		private static void StripScripts(GameObject target)
		{
			Behaviour[] componentsInChildren = target.GetComponentsInChildren<Behaviour>(true);
			foreach (Behaviour val in componentsInChildren)
			{
				if (!(val is Graphic) && !(val is Button) && !(val is Toggle) && !(val is Animator) && !(val is ButtonSfx) && !(val is ButtonTextColor) && !(val is LayoutGroup) && !(val is LayoutElement) && !(val is ContentSizeFitter) && !(val is CanvasGroup) && !(val is Canvas) && !(val is GraphicRaycaster) && !(val is Mask) && !(val is RectMask2D))
				{
					val.enabled = false;
					Object.Destroy((Object)(object)val);
				}
			}
		}

		private void Open()
		{
			if (open || !Object.op_Implicit((Object)(object)menu) || !((Component)menu.m_menuDialog).gameObject.activeSelf)
			{
				return;
			}
			if (!Object.op_Implicit((Object)(object)panel))
			{
				try
				{
					BuildPanel();
				}
				catch (Exception ex)
				{
					RemovePanel();
					Debug.LogWarning((object)("Log Exporter could not open its window: " + ex.Message));
					return;
				}
			}
			RefreshLabels();
			open = true;
			((Component)menu.m_menuDialog).gameObject.SetActive(false);
			panel.SetActive(true);
			panel.transform.SetAsLastSibling();
			Tick();
			if (((Selectable)exportButton).interactable)
			{
				((Selectable)exportButton).Select();
			}
			else
			{
				((Selectable)backButton).Select();
			}
		}

		private void Close(bool restore)
		{
			if (!open)
			{
				return;
			}
			open = false;
			closedFrame = Time.frameCount;
			if (Object.op_Implicit((Object)(object)panel))
			{
				panel.SetActive(false);
			}
			if (restore && Object.op_Implicit((Object)(object)menu) && ((Component)menu.m_root).gameObject.activeInHierarchy)
			{
				((Component)menu.m_menuDialog).gameObject.SetActive(true);
				if (Object.op_Implicit((Object)(object)entry))
				{
					((Selectable)entry).Select();
				}
			}
		}

		private static bool BeforeMenuUpdate(Menu __instance)
		{
			NativeExportMenu nativeExportMenu = active;
			if (nativeExportMenu == null || (Object)(object)nativeExportMenu.menu != (Object)(object)__instance)
			{
				return true;
			}
			if (nativeExportMenu.closedFrame == Time.frameCount)
			{
				return false;
			}
			if (!nativeExportMenu.open)
			{
				return true;
			}
			if (!((Component)__instance.m_root).gameObject.activeInHierarchy || (Object.op_Implicit((Object)(object)Game.instance) && Game.instance.IsShuttingDown()))
			{
				nativeExportMenu.Close(restore: false);
				return true;
			}
			if (UnifiedPopup.IsVisible() || TextInput.IsVisible())
			{
				return true;
			}
			if (ZInput.GetKeyDown((KeyCode)27, true) || ZInput.GetButtonDown("JoyButtonB") || ZInput.GetButtonDown("JoyMenu"))
			{
				nativeExportMenu.Close(restore: true);
				return false;
			}
			return true;
		}

		private static void AfterNavigation(Menu __instance)
		{
			if (active != null && (Object)(object)active.menu == (Object)(object)__instance)
			{
				active.AddEntryNavigation();
			}
		}

		private static void AfterHide(Menu __instance)
		{
			if (active != null && (Object)(object)active.menu == (Object)(object)__instance)
			{
				active.Close(restore: false);
			}
		}

		private void AddEntryNavigation()
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)entry) || !Object.op_Implicit((Object)(object)menu.m_settingsButton))
			{
				return;
			}
			Button settingsButton = menu.m_settingsButton;
			Navigation navigation = ((Selectable)settingsButton).navigation;
			Selectable selectOnDown = ((Navigation)(ref navigation)).selectOnDown;
			if (!((Object)(object)selectOnDown == (Object)(object)entry))
			{
				Navigation navigation2 = ((Selectable)entry).navigation;
				((Navigation)(ref navigation2)).mode = (Mode)4;
				((Navigation)(ref navigation2)).selectOnUp = (Selectable)(object)settingsButton;
				((Navigation)(ref navigation2)).selectOnDown = selectOnDown;
				((Selectable)entry).navigation = navigation2;
				navigation2 = ((Selectable)settingsButton).navigation;
				((Navigation)(ref navigation2)).selectOnDown = (Selectable)(object)entry;
				((Selectable)settingsButton).navigation = navigation2;
				if (Object.op_Implicit((Object)(object)selectOnDown))
				{
					navigation2 = selectOnDown.navigation;
					((Navigation)(ref navigation2)).selectOnUp = (Selectable)(object)entry;
					selectOnDown.navigation = navigation2;
				}
			}
		}

		private void LinkButtons()
		{
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_0230: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_01be: Unknown result type (might be due to invalid IL or missing references)
			List<Selectable>[] array = new List<Selectable>[5]
			{
				new List<Selectable> { (Selectable)(object)serverToggle },
				new List<Selectable> { (Selectable)(object)worldToggle },
				new List<Selectable> { (Selectable)(object)folderToggle },
				new List<Selectable> { (Selectable)(object)exportButton },
				new List<Selectable>()
			};
			Button[] array2 = (Button[])(object)new Button[3] { folderButton, cancelButton, backButton };
			foreach (Button val in array2)
			{
				if (((Component)val).gameObject.activeSelf && ((Selectable)val).interactable)
				{
					array[4].Add((Selectable)(object)val);
				}
			}
			List<Selectable>[] array3 = array;
			for (int i = 0; i < array3.Length; i++)
			{
				array3[i].RemoveAll((Selectable s) => !Object.op_Implicit((Object)(object)s) || !s.IsInteractable());
			}
			for (int num = 0; num < array.Length; num++)
			{
				for (int num2 = 0; num2 < array[num].Count; num2++)
				{
					List<Selectable> list = array[num];
					Selectable val2 = list[num2];
					Navigation navigation = val2.navigation;
					((Navigation)(ref navigation)).mode = (Mode)4;
					((Navigation)(ref navigation)).selectOnLeft = list[(num2 + list.Count - 1) % list.Count];
					((Navigation)(ref navigation)).selectOnRight = list[(num2 + 1) % list.Count];
					for (int num3 = -1; num3 <= 1; num3 += 2)
					{
						Selectable val3 = val2;
						float num4 = float.MaxValue;
						for (int num5 = 1; num5 < array.Length; num5++)
						{
							List<Selectable> list2 = array[(num + num3 * num5 + array.Length) % array.Length];
							if (list2.Count == 0)
							{
								continue;
							}
							foreach (Selectable item in list2)
							{
								float num6 = Math.Abs(((Component)item).GetComponent<RectTransform>().anchoredPosition.x - ((Component)val2).GetComponent<RectTransform>().anchoredPosition.x);
								if (num6 < num4)
								{
									num4 = num6;
									val3 = item;
								}
							}
							break;
						}
						if (num3 < 0)
						{
							((Navigation)(ref navigation)).selectOnUp = val3;
						}
						else
						{
							((Navigation)(ref navigation)).selectOnDown = val3;
						}
					}
					val2.navigation = navigation;
				}
			}
		}

		private static string Preview(string value, int length)
		{
			value = value ?? "";
			if (value.Length <= length)
			{
				return value;
			}
			if (char.IsHighSurrogate(value[length - 1]))
			{
				length--;
			}
			return value.Substring(0, length) + "… See LogOutput.log.";
		}

		private void Remove()
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown resul