Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Valheim Log Exporter v1.0.1
BepInEx/plugins/ValheimLogExporter/ValheimLogExporter.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
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