Decompiled source of FiresDiscordIntegration v1.0.1

FiresDiscordIntegration.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using FiresDiscordIntegration.ClientLogRelay;
using FiresDiscordIntegration.ClientLogRelay.Consumers;
using FiresDiscordIntegration.ClientLogRelay.Interactions;
using FiresDiscordIntegration.ClientLogRelay.Transport;
using FiresDiscordIntegration.ClientLogRelay.Webhook;
using FiresDiscordIntegration.Discord;
using FiresDiscordIntegration.Utilities;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Splatform;
using TMPro;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("FiresDiscordIntegration")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("FiresDiscordIntegration")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("c1f16343-d521-42e6-a7a7-1b3aa4d63f4c")]
[assembly: AssemblyFileVersion("1.0.3.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.3.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 FiresDiscordIntegration
{
	[BepInPlugin("com.Fire.FiresDiscordIntegration", "FiresDiscordIntegration", "1.0.1")]
	public class FiresDiscordIntegrationPlugin : BaseUnityPlugin
	{
		public const string PluginGUID = "com.Fire.FiresDiscordIntegration";

		public const string PluginName = "FiresDiscordIntegration";

		public const string PluginVersion = "1.0.1";

		public static FiresDiscordIntegrationPlugin Instance;

		public static readonly Harmony Harmony = new Harmony("com.Fire.FiresDiscordIntegration");

		public ConfigSync configSync;

		private void Awake()
		{
			Instance = this;
			configSync = new ConfigSync("com.Fire.FiresDiscordIntegration")
			{
				DisplayName = "FiresDiscordIntegration",
				CurrentVersion = "1.0.1",
				MinimumRequiredVersion = "1.0.1"
			};
			ConfigManager.Instance.Initialize(((BaseUnityPlugin)this).Config);
			DiscordIntegrationConfig.Initialize(((BaseUnityPlugin)this).Config);
			DiscordIntegrationConfig.BindToSync(configSync);
			Harmony.PatchAll();
			try
			{
				DiscordBotIdentity.ResolveUsername("FiresDiscordIntegration");
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[FiresDiscordIntegration] BotIdentity pre-warm failed: " + ex.Message));
			}
			WrapperBatExtractor.ExtractIfMissing();
			Debug.Log((object)"[FiresDiscordIntegration] v1.0.1 loaded.");
		}

		private void OnDestroy()
		{
			if (configSync != null)
			{
				configSync = null;
			}
			Instance = null;
		}
	}
	public sealed class ConfigManager
	{
		private static ConfigManager _instance;

		public ConfigEntry<bool> configVerboseLogging;

		public static ConfigManager Instance => _instance ?? (_instance = new ConfigManager());

		public ConfigFile Config { get; private set; }

		public void Initialize(ConfigFile config)
		{
			Config = config;
			configVerboseLogging = config.Bind<bool>("General", "VerboseLogging", false, "Emit additional debug logs for the Discord/log-relay subsystems. Off by default — most operators only want this on for troubleshooting.");
		}
	}
	public class ConfigSync
	{
		[CompilerGenerated]
		private sealed class <>c__DisplayClass59_0
		{
			public long target;

			internal bool <SendZPackage>b__0(ZNetPeer p)
			{
				return target == ZRoutedRpc.Everybody || p.m_uid == target;
			}
		}

		[CompilerGenerated]
		private sealed class <>c__DisplayClass60_0
		{
			public ConfigSync <>4__this;

			public ZPackage package;

			internal IEnumerator <SendZPackage>b__1(ZNetPeer p)
			{
				return <>4__this.distributeConfigToPeers(p, package);
			}
		}

		[CompilerGenerated]
		private sealed class <>c__DisplayClass61_0
		{
			public ZNetPeer peer;

			public ConfigSync <>4__this;
		}

		[CompilerGenerated]
		private sealed class <SendZPackage>d__59 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			public long target;

			public ZPackage package;

			public ConfigSync <>4__this;

			private <>c__DisplayClass59_0 <>8__1;

			private List<ZNetPeer> <peers>5__2;

			private IEnumerator <enumerator>5__3;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <SendZPackage>d__59(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>8__1 = null;
				<peers>5__2 = null;
				<enumerator>5__3 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>8__1 = new <>c__DisplayClass59_0();
					<>8__1.target = target;
					if (!Object.op_Implicit((Object)(object)ZNet.instance))
					{
						return false;
					}
					<peers>5__2 = ((List<ZNetPeer>)AccessTools.Field(typeof(ZRoutedRpc), "m_peers").GetValue(ZRoutedRpc.instance)).Where((ZNetPeer p) => <>8__1.target == ZRoutedRpc.Everybody || p.m_uid == <>8__1.target).ToList();
					<enumerator>5__3 = <>4__this.SendZPackage(<peers>5__2, package);
					break;
				case 1:
					<>1__state = -1;
					break;
				}
				if (<enumerator>5__3.MoveNext())
				{
					<>2__current = <enumerator>5__3.Current;
					<>1__state = 1;
					return true;
				}
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <SendZPackage>d__60 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			public List<ZNetPeer> peers;

			public ZPackage package;

			public ConfigSync <>4__this;

			private <>c__DisplayClass60_0 <>8__1;

			private byte[] <data>5__2;

			private List<IEnumerator> <writers>5__3;

			private ZPackage <compressed>5__4;

			private MemoryStream <output>5__5;

			private DeflateStream <deflate>5__6;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <SendZPackage>d__60(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>8__1 = null;
				<data>5__2 = null;
				<writers>5__3 = null;
				<compressed>5__4 = null;
				<output>5__5 = null;
				<deflate>5__6 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0091: Unknown result type (might be due to invalid IL or missing references)
				//IL_009b: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>8__1 = new <>c__DisplayClass60_0();
					<>8__1.<>4__this = <>4__this;
					<>8__1.package = package;
					if (!Object.op_Implicit((Object)(object)ZNet.instance))
					{
						return false;
					}
					<data>5__2 = <>8__1.package.GetArray();
					if (<data>5__2.Length > 10000)
					{
						<compressed>5__4 = new ZPackage();
						<compressed>5__4.Write((byte)4);
						<output>5__5 = new MemoryStream();
						try
						{
							<deflate>5__6 = new DeflateStream(<output>5__5, CompressionLevel.Optimal);
							try
							{
								<deflate>5__6.Write(<data>5__2, 0, <data>5__2.Length);
							}
							finally
							{
								if (<deflate>5__6 != null)
								{
									((IDisposable)<deflate>5__6).Dispose();
								}
							}
							<deflate>5__6 = null;
							<compressed>5__4.Write(<output>5__5.ToArray());
							<>8__1.package = <compressed>5__4;
						}
						finally
						{
							if (<output>5__5 != null)
							{
								((IDisposable)<output>5__5).Dispose();
							}
						}
						<compressed>5__4 = null;
						<output>5__5 = null;
					}
					<writers>5__3 = (from p in peers
						where p.IsReady()
						select <>8__1.<>4__this.distributeConfigToPeers(p, <>8__1.package)).ToList();
					<writers>5__3.RemoveAll((IEnumerator w) => !w.MoveNext());
					break;
				case 1:
					<>1__state = -1;
					<writers>5__3.RemoveAll((IEnumerator w) => !w.MoveNext());
					break;
				}
				if (<writers>5__3.Any())
				{
					<>2__current = null;
					<>1__state = 1;
					return true;
				}
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <distributeConfigToPeers>d__61 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			public ZNetPeer peer;

			public ZPackage package;

			public ConfigSync <>4__this;

			private <>c__DisplayClass61_0 <>8__1;

			private byte[] <data>5__2;

			private int <fragments>5__3;

			private long <packageId>5__4;

			private int <i>5__5;

			private ZPackage <pkg>5__6;

			private IEnumerator<bool> <>s__7;

			private bool <wait>5__8;

			private IEnumerator<bool> <>s__9;

			private bool <wait>5__10;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <distributeConfigToPeers>d__61(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				switch (<>1__state)
				{
				case -3:
				case 1:
					try
					{
					}
					finally
					{
						<>m__Finally1();
					}
					break;
				case -4:
				case 3:
					try
					{
					}
					finally
					{
						<>m__Finally2();
					}
					break;
				}
				<>8__1 = null;
				<data>5__2 = null;
				<pkg>5__6 = null;
				<>s__7 = null;
				<>s__9 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0165: Unknown result type (might be due to invalid IL or missing references)
				//IL_016f: Expected O, but got Unknown
				try
				{
					switch (<>1__state)
					{
					default:
						return false;
					case 0:
						<>1__state = -1;
						<>8__1 = new <>c__DisplayClass61_0();
						<>8__1.peer = peer;
						<>8__1.<>4__this = <>4__this;
						<data>5__2 = package.GetArray();
						if (<data>5__2.Length > 250000)
						{
							<fragments>5__3 = (<data>5__2.Length + 249999) / 250000;
							<packageId>5__4 = ++packageCounter;
							<i>5__5 = 0;
							goto IL_0247;
						}
						<>s__9 = waitForQueue().GetEnumerator();
						<>1__state = -4;
						goto IL_02bb;
					case 1:
						<>1__state = -3;
						goto IL_0128;
					case 2:
						<>1__state = -1;
						goto IL_022d;
					case 3:
						{
							<>1__state = -4;
							goto IL_02bb;
						}
						IL_02bb:
						if (<>s__9.MoveNext())
						{
							<wait>5__10 = <>s__9.Current;
							<>2__current = <wait>5__10;
							<>1__state = 3;
							return true;
						}
						<>m__Finally2();
						<>s__9 = null;
						SendPackage(package);
						break;
						IL_0247:
						if (<i>5__5 < <fragments>5__3)
						{
							<>s__7 = waitForQueue().GetEnumerator();
							<>1__state = -3;
							goto IL_0128;
						}
						break;
						IL_022d:
						<pkg>5__6 = null;
						<i>5__5++;
						goto IL_0247;
						IL_0128:
						if (<>s__7.MoveNext())
						{
							<wait>5__8 = <>s__7.Current;
							<>2__current = <wait>5__8;
							<>1__state = 1;
							return true;
						}
						<>m__Finally1();
						<>s__7 = null;
						if (!<>8__1.peer.m_socket.IsConnected())
						{
							break;
						}
						<pkg>5__6 = new ZPackage();
						<pkg>5__6.Write((byte)2);
						<pkg>5__6.Write(<packageId>5__4);
						<pkg>5__6.Write(<i>5__5);
						<pkg>5__6.Write(<fragments>5__3);
						<pkg>5__6.Write(<data>5__2.Skip(250000 * <i>5__5).Take(250000).ToArray());
						SendPackage(<pkg>5__6);
						if (<i>5__5 < <fragments>5__3 - 1)
						{
							<>2__current = true;
							<>1__state = 2;
							return true;
						}
						goto IL_022d;
					}
					return false;
				}
				catch
				{
					//try-fault
					((IDisposable)this).Dispose();
					throw;
				}
				void SendPackage(ZPackage pkg)
				{
					if (isServer)
					{
						((<>c__DisplayClass61_0)this).peer.m_rpc.Invoke(((<>c__DisplayClass61_0)this).<>4__this.Name + " ConfigSync", new object[1] { pkg });
					}
					else
					{
						ZRoutedRpc.instance.InvokeRoutedRPC(((<>c__DisplayClass61_0)this).peer.m_server ? 0 : ((<>c__DisplayClass61_0)this).peer.m_uid, ((<>c__DisplayClass61_0)this).<>4__this.Name + " ConfigSync", new object[1] { pkg });
					}
				}
				[IteratorStateMachine(typeof(<>c__DisplayClass61_0.<<distributeConfigToPeers>g__waitForQueue|0>d))]
				IEnumerable<bool> waitForQueue()
				{
					//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
					return new <>c__DisplayClass61_0.<<distributeConfigToPeers>g__waitForQueue|0>d(-2)
					{
						<>4__this = this
					};
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			private void <>m__Finally1()
			{
				<>1__state = -1;
				if (<>s__7 != null)
				{
					<>s__7.Dispose();
				}
			}

			private void <>m__Finally2()
			{
				<>1__state = -1;
				if (<>s__9 != null)
				{
					<>s__9.Dispose();
				}
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		public static bool ProcessingServerUpdate = false;

		public readonly string Name;

		public string DisplayName;

		public string CurrentVersion;

		public string MinimumRequiredVersion;

		public bool ModRequired;

		private bool? forceConfigLocking;

		private bool isSourceOfTruth = true;

		public static HashSet<ConfigSync> configSyncs = new HashSet<ConfigSync>();

		public HashSet<OwnConfigEntryBase> allConfigs = new HashSet<OwnConfigEntryBase>();

		public HashSet<CustomSyncedValueBase> allCustomValues = new HashSet<CustomSyncedValueBase>();

		public static bool isServer;

		public static bool lockExempt = false;

		private OwnConfigEntryBase lockedConfig;

		private const byte PARTIAL_CONFIGS = 1;

		private const byte FRAGMENTED_CONFIG = 2;

		private const byte COMPRESSED_CONFIG = 4;

		private readonly Dictionary<string, SortedDictionary<int, byte[]>> configValueCache = new Dictionary<string, SortedDictionary<int, byte[]>>();

		private readonly List<KeyValuePair<long, string>> cacheExpirations = new List<KeyValuePair<long, string>>();

		private static long packageCounter = 0L;

		private DateTime lastConfigLogTime = DateTime.MinValue;

		private bool initialSyncDone;

		public bool IsLocked
		{
			get
			{
				bool? flag = forceConfigLocking;
				bool num;
				if (!flag.HasValue)
				{
					OwnConfigEntryBase ownConfigEntryBase = lockedConfig;
					if (!(((ownConfigEntryBase != null) ? ownConfigEntryBase.BaseConfig.BoxedValue : null) is IConvertible convertible))
					{
						goto IL_0056;
					}
					num = convertible.ToInt32(CultureInfo.InvariantCulture) != 0;
				}
				else
				{
					num = flag.GetValueOrDefault();
				}
				if (!num)
				{
					goto IL_0056;
				}
				int result = ((!lockExempt) ? 1 : 0);
				goto IL_0057;
				IL_0056:
				result = 0;
				goto IL_0057;
				IL_0057:
				return (byte)result != 0;
			}
			set
			{
				forceConfigLocking = value;
			}
		}

		public bool IsAdmin => lockExempt || isSourceOfTruth;

		public bool IsSourceOfTruth
		{
			get
			{
				return isSourceOfTruth;
			}
			internal set
			{
				if (value != isSourceOfTruth)
				{
					isSourceOfTruth = value;
					this.SourceOfTruthChanged?.Invoke(value);
				}
			}
		}

		public bool InitialSyncDone
		{
			get
			{
				return initialSyncDone;
			}
			internal set
			{
				initialSyncDone = value;
			}
		}

		public event Action<bool> SourceOfTruthChanged;

		public event Action lockedConfigChanged;

		public ConfigSync(string name)
		{
			Name = name;
			DisplayName = name;
			configSyncs.Add(this);
		}

		public void RequestSync()
		{
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Expected O, but got Unknown
			if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer())
			{
				List<ZNetPeer> source = (List<ZNetPeer>)AccessTools.Field(typeof(ZRoutedRpc), "m_peers").GetValue(ZRoutedRpc.instance);
				ZNetPeer val = ((IEnumerable<ZNetPeer>)source).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => p.m_server));
				if (val != null)
				{
					ZRoutedRpc.instance.InvokeRoutedRPC(val.m_uid, Name + " ConfigSync", new object[1] { (object)new ZPackage() });
				}
			}
		}

		public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigEntry<T> configEntry)
		{
			SyncedConfigEntry<T> syncedEntry = (configData((ConfigEntryBase)(object)configEntry) as SyncedConfigEntry<T>) ?? new SyncedConfigEntry<T>(configEntry);
			object[] first = ((ConfigEntryBase)configEntry).Description.Tags?.ToArray() ?? new object[1]
			{
				new ConfigurationManagerAttributes()
			};
			first = first.Concat(new object[1] { syncedEntry }).ToArray();
			AccessTools.Field(typeof(ConfigDescription), "<Tags>k__BackingField").SetValue(((ConfigEntryBase)configEntry).Description, first);
			configEntry.SettingChanged += delegate
			{
				if (!ProcessingServerUpdate && syncedEntry.SynchronizedConfig)
				{
					Broadcast(ZRoutedRpc.Everybody, (ConfigEntryBase)(object)configEntry);
				}
			};
			allConfigs.Add(syncedEntry);
			return syncedEntry;
		}

		public SyncedConfigEntry<T> AddLockingConfigEntry<T>(ConfigEntry<T> lockingConfig) where T : IConvertible
		{
			if (lockedConfig != null)
			{
				throw new Exception("Cannot initialize locking ConfigEntry twice");
			}
			lockedConfig = AddConfigEntry<T>(lockingConfig);
			lockingConfig.SettingChanged += delegate
			{
				this.lockedConfigChanged?.Invoke();
			};
			return (SyncedConfigEntry<T>)lockedConfig;
		}

		internal void AddCustomValue(CustomSyncedValueBase customValue)
		{
			if (allCustomValues.Any((CustomSyncedValueBase v) => v.Identifier == customValue.Identifier) || customValue.Identifier == "serverversion")
			{
				throw new Exception("Cannot have multiple settings with the same name or with a reserved name (serverversion)");
			}
			allCustomValues.Add(customValue);
			allCustomValues = new HashSet<CustomSyncedValueBase>(allCustomValues.OrderByDescending((CustomSyncedValueBase v) => v.Priority));
			customValue.ValueChanged += delegate
			{
				if (!ProcessingServerUpdate)
				{
					Broadcast(ZRoutedRpc.Everybody, customValue);
				}
			};
		}

		public void Broadcast(long target, ConfigEntryBase config)
		{
			if (!IsLocked || IsAdmin)
			{
				ZPackage package = ConfigsToPackage((IEnumerable<ConfigEntryBase>)(object)new ConfigEntryBase[1] { config });
				ZNet instance = ZNet.instance;
				if (instance != null)
				{
					((MonoBehaviour)instance).StartCoroutine(SendZPackage(target, package));
				}
			}
		}

		public void Broadcast(long target, CustomSyncedValueBase customValue)
		{
			if (!IsLocked || IsAdmin)
			{
				ZPackage package = ConfigsToPackage(null, new CustomSyncedValueBase[1] { customValue });
				ZNet instance = ZNet.instance;
				if (instance != null)
				{
					((MonoBehaviour)instance).StartCoroutine(SendZPackage(target, package));
				}
			}
		}

		internal void RPC_FromServerConfigSync(ZRpc rpc, ZPackage package)
		{
			lockedConfigChanged += serverLockedSettingChanged;
			IsSourceOfTruth = false;
			if (HandleConfigSyncRPC(0L, package, clientUpdate: false))
			{
				InitialSyncDone = true;
			}
		}

		internal void RPC_FromOtherClientConfigSync(long sender, ZPackage package)
		{
			HandleConfigSyncRPC(sender, package, clientUpdate: true);
		}

		private bool HandleConfigSyncRPC(long sender, ZPackage package, bool clientUpdate)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			//IL_019b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a2: Expected O, but got Unknown
			//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0202: Expected O, but got Unknown
			try
			{
				if (isServer && IsLocked)
				{
					ZRpc currentRpc = SnatchCurrentlyHandlingRPC.currentRpc;
					object obj;
					if (currentRpc == null)
					{
						obj = null;
					}
					else
					{
						ISocket socket = currentRpc.GetSocket();
						obj = ((socket != null) ? socket.GetHostName() : null);
					}
					string text = (string)obj;
					SyncedList val = (SyncedList)AccessTools.Field(typeof(ZNet), "m_adminList").GetValue(ZNet.instance);
					if (text != null && !val.Contains(text))
					{
						return false;
					}
				}
				cacheExpirations.RemoveAll((KeyValuePair<long, string> kv) => kv.Key < DateTimeOffset.Now.Ticks && configValueCache.Remove(kv.Value));
				byte b = package.ReadByte();
				if ((b & 2u) != 0)
				{
					long num = package.ReadLong();
					string text2 = sender.ToString() + num;
					if (!configValueCache.TryGetValue(text2, out var value))
					{
						value = new SortedDictionary<int, byte[]>();
						configValueCache[text2] = value;
						cacheExpirations.Add(new KeyValuePair<long, string>(DateTimeOffset.Now.Ticks + 600000000, text2));
					}
					int key = package.ReadInt();
					int num2 = package.ReadInt();
					value[key] = package.ReadByteArray();
					if (value.Count < num2)
					{
						return false;
					}
					configValueCache.Remove(text2);
					package = new ZPackage(value.Values.SelectMany((byte[] a) => a).ToArray());
					b = package.ReadByte();
				}
				ProcessingServerUpdate = true;
				if ((b & 4u) != 0)
				{
					using MemoryStream stream = new MemoryStream(package.ReadByteArray());
					using MemoryStream memoryStream = new MemoryStream();
					using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress))
					{
						deflateStream.CopyTo(memoryStream);
					}
					package = new ZPackage(memoryStream.ToArray());
					b = package.ReadByte();
				}
				if ((b & 1) == 0)
				{
					resetConfigsFromServer();
				}
				ParsedConfigs parsedConfigs = ReadConfigsFromPackage(package);
				ConfigFile val2 = null;
				bool saveOnConfigSet = false;
				foreach (KeyValuePair<OwnConfigEntryBase, object> configValue in parsedConfigs.configValues)
				{
					if (!isServer && configValue.Key.LocalBaseValue == null)
					{
						configValue.Key.LocalBaseValue = configValue.Key.BaseConfig.BoxedValue;
					}
					if (val2 == null)
					{
						val2 = configValue.Key.BaseConfig.ConfigFile;
						saveOnConfigSet = val2.SaveOnConfigSet;
						val2.SaveOnConfigSet = false;
					}
					configValue.Key.BaseConfig.BoxedValue = configValue.Value;
				}
				if (val2 != null)
				{
					val2.SaveOnConfigSet = saveOnConfigSet;
					val2.Save();
				}
				foreach (KeyValuePair<CustomSyncedValueBase, object> customValue in parsedConfigs.customValues)
				{
					if (!isServer && customValue.Key.LocalBaseValue == null)
					{
						customValue.Key.LocalBaseValue = customValue.Key.BoxedValue;
					}
					customValue.Key.BoxedValue = customValue.Value;
				}
				if ((DateTime.Now - lastConfigLogTime).TotalSeconds > 12.0)
				{
					Debug.Log((object)string.Format("[{0}] Received {1} configs and {2} custom values from {3} for mod {4}", "FiresDiscordIntegration", parsedConfigs.configValues.Count, parsedConfigs.customValues.Count, (isServer || clientUpdate) ? $"client {sender}" : "server", DisplayName ?? Name));
					lastConfigLogTime = DateTime.Now;
				}
				if (!isServer)
				{
					serverLockedSettingChanged();
				}
				return true;
			}
			finally
			{
				ProcessingServerUpdate = false;
			}
		}

		private void serverLockedSettingChanged()
		{
			foreach (OwnConfigEntryBase allConfig in allConfigs)
			{
				ConfigurationManagerAttributes configurationManagerAttributes = allConfig.BaseConfig.Description.Tags?.OfType<ConfigurationManagerAttributes>().FirstOrDefault();
				if (configurationManagerAttributes != null)
				{
					configurationManagerAttributes.ReadOnly = !isWritableConfig(allConfig);
				}
			}
		}

		internal static bool isWritableConfig(OwnConfigEntryBase config)
		{
			ConfigSync configSync = configSyncs.FirstOrDefault((ConfigSync cs) => cs.allConfigs.Contains(config));
			if (configSync == null || configSync.IsSourceOfTruth || !config.SynchronizedConfig || config.LocalBaseValue == null)
			{
				return true;
			}
			return !configSync.IsLocked || config != configSync.lockedConfig || lockExempt;
		}

		internal void resetConfigsFromServer()
		{
			ConfigFile val = null;
			bool saveOnConfigSet = false;
			foreach (OwnConfigEntryBase item in allConfigs.Where((OwnConfigEntryBase c) => c.LocalBaseValue != null))
			{
				if (val == null)
				{
					val = item.BaseConfig.ConfigFile;
					saveOnConfigSet = val.SaveOnConfigSet;
					val.SaveOnConfigSet = false;
				}
				item.BaseConfig.BoxedValue = item.LocalBaseValue;
				item.LocalBaseValue = null;
			}
			if (val != null)
			{
				val.SaveOnConfigSet = saveOnConfigSet;
				val.Save();
			}
			foreach (CustomSyncedValueBase item2 in allCustomValues.Where((CustomSyncedValueBase c) => c.LocalBaseValue != null))
			{
				item2.BoxedValue = item2.LocalBaseValue;
				item2.LocalBaseValue = null;
			}
			lockedConfigChanged -= serverLockedSettingChanged;
			serverLockedSettingChanged();
		}

		private ParsedConfigs ReadConfigsFromPackage(ZPackage package)
		{
			ParsedConfigs parsedConfigs = new ParsedConfigs();
			Dictionary<string, OwnConfigEntryBase> dictionary = allConfigs.ToDictionary((OwnConfigEntryBase c) => c.BaseConfig.Definition.Section + "*" + c.BaseConfig.Definition.Key, (OwnConfigEntryBase c) => c);
			int num = package.ReadInt();
			for (int i = 0; i < num; i++)
			{
				string text = package.ReadString();
				string key = package.ReadString();
				string text2 = package.ReadString();
				Type type = Type.GetType(text2);
				if (text2 == "" || type != null)
				{
					object obj;
					try
					{
						obj = ((text2 == "") ? null : ReadValueWithTypeFromZPackage(package, type));
					}
					catch (InvalidDeserializationTypeException ex)
					{
						Debug.LogWarning((object)("[FiresDiscordIntegration] Got unexpected struct internal type " + ex.received + " for field " + ex.field + " struct " + text2 + " for " + key + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + ex.expected));
						continue;
					}
					OwnConfigEntryBase value;
					if (text == "Internal")
					{
						if (key == "lockexempt" && obj is bool flag)
						{
							lockExempt = flag;
							continue;
						}
						CustomSyncedValueBase customSyncedValueBase = allCustomValues.FirstOrDefault((CustomSyncedValueBase v) => v.Identifier == key);
						if (customSyncedValueBase != null)
						{
							if ((text2 == "" && (!customSyncedValueBase.Type.IsValueType || Nullable.GetUnderlyingType(customSyncedValueBase.Type) != null)) || GetZPackageTypeString(customSyncedValueBase.Type) == text2)
							{
								parsedConfigs.customValues[customSyncedValueBase] = obj;
								continue;
							}
							Debug.LogWarning((object)("[FiresDiscordIntegration] Got unexpected type " + text2 + " for internal value " + key + " for mod " + (DisplayName ?? Name) + ", expecting " + customSyncedValueBase.Type.AssemblyQualifiedName));
						}
					}
					else if (dictionary.TryGetValue(text + "*" + key, out value))
					{
						Type type2 = configType(value.BaseConfig);
						if ((text2 == "" && (!type2.IsValueType || Nullable.GetUnderlyingType(type2) != null)) || GetZPackageTypeString(type2) == text2)
						{
							parsedConfigs.configValues[value] = obj;
							continue;
						}
						Debug.LogWarning((object)("[FiresDiscordIntegration] Got unexpected type " + text2 + " for " + key + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + type2.AssemblyQualifiedName));
					}
					else
					{
						Debug.LogWarning((object)("[FiresDiscordIntegration] Received unknown config entry " + key + " in section " + text + " for mod " + (DisplayName ?? Name) + "."));
					}
					continue;
				}
				Debug.LogWarning((object)("[FiresDiscordIntegration] Got invalid type " + text2 + ", abort reading of received configs"));
				return new ParsedConfigs();
			}
			return parsedConfigs;
		}

		private static string GetZPackageTypeString(Type type)
		{
			return type.AssemblyQualifiedName;
		}

		private static void AddValueToZPackage(ZPackage package, object value)
		{
			Type type = value?.GetType();
			if (value is Enum)
			{
				value = ((IConvertible)value).ToType(Enum.GetUnderlyingType(type), CultureInfo.InvariantCulture);
			}
			else
			{
				if (value is ICollection collection)
				{
					package.Write(collection.Count);
					{
						IEnumerator enumerator = collection.GetEnumerator();
						try
						{
							while (enumerator.MoveNext())
							{
								AddValueToZPackage(package, enumerator.Current);
							}
							return;
						}
						finally
						{
							IDisposable disposable = enumerator as IDisposable;
							if (disposable != null)
							{
								disposable.Dispose();
							}
						}
					}
				}
				if (type != null && type.IsValueType && !type.IsPrimitive)
				{
					FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					package.Write(fields.Length);
					FieldInfo[] array = fields;
					foreach (FieldInfo fieldInfo in array)
					{
						package.Write(GetZPackageTypeString(fieldInfo.FieldType));
						AddValueToZPackage(package, fieldInfo.GetValue(value));
					}
					return;
				}
			}
			ZRpc.Serialize(new object[1] { value }, ref package);
		}

		private static object ReadValueWithTypeFromZPackage(ZPackage package, Type type)
		{
			if (type != null && type.IsValueType && !type.IsPrimitive && !type.IsEnum)
			{
				FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				int num = package.ReadInt();
				if (num != fields.Length)
				{
					throw new InvalidDeserializationTypeException
					{
						received = $"(field count: {num})",
						expected = $"(field count: {fields.Length})"
					};
				}
				object uninitializedObject = FormatterServices.GetUninitializedObject(type);
				FieldInfo[] array = fields;
				foreach (FieldInfo fieldInfo in array)
				{
					string text = package.ReadString();
					if (text != GetZPackageTypeString(fieldInfo.FieldType))
					{
						throw new InvalidDeserializationTypeException
						{
							received = text,
							expected = GetZPackageTypeString(fieldInfo.FieldType),
							field = fieldInfo.Name
						};
					}
					fieldInfo.SetValue(uninitializedObject, ReadValueWithTypeFromZPackage(package, fieldInfo.FieldType));
				}
				return uninitializedObject;
			}
			if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<, >))
			{
				int num2 = package.ReadInt();
				Type type2 = typeof(KeyValuePair<, >).MakeGenericType(type.GenericTypeArguments);
				IDictionary dictionary = (IDictionary)Activator.CreateInstance(type);
				FieldInfo field = type2.GetField("key", BindingFlags.Instance | BindingFlags.NonPublic);
				FieldInfo field2 = type2.GetField("value", BindingFlags.Instance | BindingFlags.NonPublic);
				for (int j = 0; j < num2; j++)
				{
					object obj = ReadValueWithTypeFromZPackage(package, type2);
					dictionary.Add(field.GetValue(obj), field2.GetValue(obj));
				}
				return dictionary;
			}
			if (type.IsGenericType)
			{
				Type type3 = typeof(ICollection<>).MakeGenericType(type.GenericTypeArguments[0]);
				if (type3.IsAssignableFrom(type))
				{
					int num3 = package.ReadInt();
					object obj2 = Activator.CreateInstance(type);
					MethodInfo method = type3.GetMethod("Add");
					for (int k = 0; k < num3; k++)
					{
						method.Invoke(obj2, new object[1] { ReadValueWithTypeFromZPackage(package, type.GenericTypeArguments[0]) });
					}
					return obj2;
				}
			}
			ParameterInfo parameterInfo = (ParameterInfo)FormatterServices.GetUninitializedObject(typeof(ParameterInfo));
			AccessTools.Field(typeof(ParameterInfo), "ClassImpl").SetValue(parameterInfo, type);
			List<object> source = new List<object>();
			ZRpc.Deserialize(new ParameterInfo[2] { null, parameterInfo }, package, ref source);
			return source.First();
		}

		internal ZPackage ConfigsToPackage(IEnumerable<ConfigEntryBase> configs = null, IEnumerable<CustomSyncedValueBase> customValues = null, IEnumerable<PackageEntry> packageEntries = null, bool partial = true)
		{
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Expected O, but got Unknown
			List<ConfigEntryBase> list = configs?.Where((ConfigEntryBase c) => configData(c).SynchronizedConfig).ToList() ?? new List<ConfigEntryBase>();
			List<CustomSyncedValueBase> list2 = customValues?.ToList() ?? new List<CustomSyncedValueBase>();
			List<PackageEntry> list3 = packageEntries?.ToList() ?? new List<PackageEntry>();
			ZPackage val = new ZPackage();
			val.Write((byte)(partial ? 1u : 0u));
			val.Write(list.Count + list2.Count + list3.Count);
			foreach (PackageEntry item in list3)
			{
				val.Write(item.section);
				val.Write(item.key);
				val.Write((item.value == null) ? "" : GetZPackageTypeString(item.type));
				AddValueToZPackage(val, item.value);
			}
			foreach (CustomSyncedValueBase item2 in list2)
			{
				val.Write("Internal");
				val.Write(item2.Identifier);
				val.Write(GetZPackageTypeString(item2.Type));
				AddValueToZPackage(val, item2.BoxedValue);
			}
			foreach (ConfigEntryBase item3 in list)
			{
				val.Write(item3.Definition.Section);
				val.Write(item3.Definition.Key);
				val.Write(GetZPackageTypeString(configType(item3)));
				AddValueToZPackage(val, item3.BoxedValue);
			}
			return val;
		}

		private static Type configType(ConfigEntryBase config)
		{
			return configType(config.SettingType);
		}

		private static Type configType(Type type)
		{
			return type.IsEnum ? Enum.GetUnderlyingType(type) : type;
		}

		[IteratorStateMachine(typeof(<SendZPackage>d__59))]
		public IEnumerator SendZPackage(long target, ZPackage package)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <SendZPackage>d__59(0)
			{
				<>4__this = this,
				target = target,
				package = package
			};
		}

		[IteratorStateMachine(typeof(<SendZPackage>d__60))]
		public IEnumerator SendZPackage(List<ZNetPeer> peers, ZPackage package)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <SendZPackage>d__60(0)
			{
				<>4__this = this,
				peers = peers,
				package = package
			};
		}

		[IteratorStateMachine(typeof(<distributeConfigToPeers>d__61))]
		private IEnumerator distributeConfigToPeers(ZNetPeer peer, ZPackage package)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <distributeConfigToPeers>d__61(0)
			{
				<>4__this = this,
				peer = peer,
				package = package
			};
		}

		internal static OwnConfigEntryBase configData(ConfigEntryBase config)
		{
			return config.Description.Tags?.OfType<OwnConfigEntryBase>().SingleOrDefault();
		}

		private static T configAttribute<T>(ConfigEntryBase config) where T : class
		{
			object[] tags = config.Description.Tags;
			return (tags != null) ? tags.OfType<T>().FirstOrDefault() : null;
		}
	}
	public abstract class OwnConfigEntryBase
	{
		public readonly ConfigEntryBase BaseConfig;

		public object LocalBaseValue;

		public bool SynchronizedConfig = true;

		protected OwnConfigEntryBase(ConfigEntryBase config)
		{
			BaseConfig = config;
		}
	}
	public class SyncedConfigEntry<T> : OwnConfigEntryBase
	{
		public T Value
		{
			get
			{
				if (!(BaseConfig is ConfigEntry<T> val))
				{
					throw new InvalidOperationException("Cannot cast BaseConfig to ConfigEntry<" + typeof(T).Name + ">");
				}
				return val.Value;
			}
			set
			{
				((ConfigEntry<T>)(object)BaseConfig).Value = value;
			}
		}

		public T DefaultValue => (T)BaseConfig.BoxedValue;

		public SyncedConfigEntry(ConfigEntry<T> config)
			: base((ConfigEntryBase)(object)config)
		{
		}
	}
	public class CustomSyncedValueBase
	{
		public string Identifier { get; }

		public Type Type { get; }

		public object BoxedValue { get; set; }

		public object LocalBaseValue { get; set; }

		public int Priority { get; }

		public event Action ValueChanged;

		public CustomSyncedValueBase(string identifier, Type type, int priority = 0)
		{
			Identifier = identifier;
			Type = type;
			Priority = priority;
		}
	}
	public class ConfigurationManagerAttributes
	{
		public bool? ReadOnly;
	}
	public class ParsedConfigs
	{
		public readonly Dictionary<OwnConfigEntryBase, object> configValues = new Dictionary<OwnConfigEntryBase, object>();

		public readonly Dictionary<CustomSyncedValueBase, object> customValues = new Dictionary<CustomSyncedValueBase, object>();
	}
	public class PackageEntry
	{
		public string section;

		public string key;

		public Type type;

		public object value;
	}
	public class InvalidDeserializationTypeException : Exception
	{
		public string expected;

		public string received;

		public string field = "";
	}
	[HarmonyPatch(typeof(ZRpc), "HandlePackage")]
	public class SnatchCurrentlyHandlingRPC
	{
		public static ZRpc currentRpc;

		[HarmonyPrefix]
		private static void Prefix(ZRpc __instance)
		{
			currentRpc = __instance;
		}
	}
	[HarmonyPatch(typeof(ZNet), "Awake")]
	public class RegisterRPCPatch
	{
		[HarmonyPostfix]
		private static void Postfix(ZNet __instance)
		{
			ConfigSync.isServer = __instance.IsServer();
			foreach (ConfigSync configSync in ConfigSync.configSyncs)
			{
				ZRoutedRpc.instance.Register<ZPackage>(configSync.Name + " ConfigSync", (Action<long, ZPackage>)configSync.RPC_FromOtherClientConfigSync);
				if (ConfigSync.isServer)
				{
					Debug.Log((object)("[FiresDiscordIntegration] Registered '" + configSync.Name + " ConfigSync' RPC"));
				}
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
	public class RegisterClientRPCPatch
	{
		[HarmonyPostfix]
		private static void Postfix(ZNet __instance, ZNetPeer peer)
		{
			if (__instance.IsServer())
			{
				return;
			}
			foreach (ConfigSync configSync in ConfigSync.configSyncs)
			{
				peer.m_rpc.Register<ZPackage>(configSync.Name + " ConfigSync", (Action<ZRpc, ZPackage>)configSync.RPC_FromServerConfigSync);
			}
		}
	}
	[HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")]
	public class PreventSavingServerInfo
	{
		[HarmonyPrefix]
		private static bool Prefix(ConfigEntryBase __instance, ref string __result)
		{
			OwnConfigEntryBase ownConfigEntryBase = ConfigSync.configData(__instance);
			if (ownConfigEntryBase == null || ConfigSync.isWritableConfig(ownConfigEntryBase))
			{
				return true;
			}
			__result = TomlTypeConverter.ConvertToString(ownConfigEntryBase.LocalBaseValue, __instance.SettingType);
			return false;
		}
	}
	[HarmonyPatch(typeof(ConfigEntryBase), "SetSerializedValue")]
	public class PreventConfigRereadChangingValues
	{
		[HarmonyPrefix]
		private static bool Prefix(ConfigEntryBase __instance, string value)
		{
			OwnConfigEntryBase ownConfigEntryBase = ConfigSync.configData(__instance);
			if (ownConfigEntryBase?.LocalBaseValue != null)
			{
				try
				{
					ownConfigEntryBase.LocalBaseValue = TomlTypeConverter.ConvertToValue(value, __instance.SettingType);
				}
				catch (Exception ex)
				{
					Debug.LogWarning((object)string.Format("[{0}] Config value of setting \"{1}\" could not be parsed and will be ignored. Reason: {2}; Value: {3}", "FiresDiscordIntegration", __instance.Definition, ex.Message, value));
				}
				return false;
			}
			return true;
		}
	}
}
namespace FiresDiscordIntegration.ClientLogRelay
{
	public sealed class ClientLogArtifacts
	{
		public string PlatformId { get; }

		public string PlayerName { get; }

		public byte[] LogBytes { get; }

		public IReadOnlyDictionary<string, string> ModList { get; }

		public IReadOnlyDictionary<string, string> ServerMods { get; }

		public string BrandLabel { get; }

		public DateTime CapturedUtc { get; }

		public string ErrorsWarningsReport { get; internal set; }

		public int ErrorCount { get; internal set; }

		public int WarningCount { get; internal set; }

		public int BenignSkipped { get; internal set; }

		public int DuplicatesCollapsed { get; internal set; }

		public IReadOnlyList<LogErrorWarningExtractor.SourceIssueCount> SourceBreakdown { get; internal set; }

		public ModListDiff.Result ModDiff { get; internal set; }

		public string SafePlatformId => (PlatformId ?? "unknown").Replace(":", "_").Replace("/", "_").Replace("\\", "_");

		public string SafePlayerName
		{
			get
			{
				if (string.IsNullOrEmpty(PlayerName))
				{
					return "unknown";
				}
				char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
				return string.Concat(PlayerName.Split(invalidFileNameChars));
			}
		}

		public string DefaultFolderName => SafePlayerName + "_" + SafePlatformId;

		public ClientLogArtifacts(string platformId, string playerName, byte[] logBytes, IReadOnlyDictionary<string, string> modList, IReadOnlyDictionary<string, string> serverMods = null, string brandLabel = null)
		{
			PlatformId = platformId ?? string.Empty;
			PlayerName = (string.IsNullOrEmpty(playerName) ? "unknown" : playerName);
			LogBytes = logBytes ?? Array.Empty<byte>();
			ModList = modList ?? new Dictionary<string, string>(0);
			ServerMods = serverMods;
			BrandLabel = brandLabel;
			CapturedUtc = DateTime.UtcNow;
		}
	}
	public static class ClientLogArtifactWriter
	{
		public static string Write(string targetDir, ClientLogArtifacts artifacts)
		{
			if (artifacts == null)
			{
				return null;
			}
			if (string.IsNullOrEmpty(targetDir))
			{
				return null;
			}
			try
			{
				string text = Path.Combine(targetDir, artifacts.DefaultFolderName);
				if (!Directory.Exists(text))
				{
					Directory.CreateDirectory(text);
				}
				if (artifacts.LogBytes != null && artifacts.LogBytes.Length != 0)
				{
					File.WriteAllBytes(Path.Combine(text, "LogOutput.log"), artifacts.LogBytes);
				}
				if (artifacts.ModList != null && artifacts.ModList.Count > 0)
				{
					StringBuilder stringBuilder = new StringBuilder();
					stringBuilder.AppendLine("# Client BepInEx Mod List");
					stringBuilder.AppendLine("# Player:    " + artifacts.PlayerName);
					stringBuilder.AppendLine("# SteamID:   " + artifacts.PlatformId);
					stringBuilder.AppendLine($"# Captured:  {artifacts.CapturedUtc:yyyy-MM-dd HH:mm:ss} UTC");
					stringBuilder.AppendLine($"# Mod Count: {artifacts.ModList.Count}");
					stringBuilder.AppendLine("# Format:    GUID=Version");
					stringBuilder.AppendLine();
					foreach (KeyValuePair<string, string> item in artifacts.ModList.OrderBy<KeyValuePair<string, string>, string>((KeyValuePair<string, string> kv) => kv.Key, StringComparer.OrdinalIgnoreCase))
					{
						stringBuilder.Append(item.Key).Append('=').AppendLine(item.Value);
					}
					File.WriteAllText(Path.Combine(text, "modlist.txt"), stringBuilder.ToString());
				}
				if (!string.IsNullOrEmpty(artifacts.ErrorsWarningsReport))
				{
					File.WriteAllText(Path.Combine(text, "errors_warnings.txt"), artifacts.ErrorsWarningsReport);
				}
				if (artifacts.ModDiff != null && !string.IsNullOrEmpty(artifacts.ModDiff.Report))
				{
					File.WriteAllText(Path.Combine(text, "mod_diff.txt"), artifacts.ModDiff.Report);
				}
				return text;
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[ClientLogRelay] Failed to write artifacts for " + artifacts.PlatformId + ": " + ex.Message));
				return null;
			}
		}
	}
	public static class ClientLogRelay
	{
		private static readonly object _lock = new object();

		private static readonly List<IClientLogConsumer> _consumers = new List<IClientLogConsumer>();

		private const string LoginOwnerKey = "FiresMods.ClientLogRelay.LoginSnapshotOwner";

		private const string LoginOwnerPriorityKey = "FiresMods.ClientLogRelay.LoginSnapshotOwnerPriority";

		private static ILogRequestHandler _logRequestHandler;

		public static bool HasConsumers
		{
			get
			{
				lock (_lock)
				{
					return _consumers.Count > 0;
				}
			}
		}

		public static bool HasLogRequestHandler
		{
			get
			{
				lock (_lock)
				{
					return _logRequestHandler != null;
				}
			}
		}

		public static bool RegisterConsumer(IClientLogConsumer consumer)
		{
			if (consumer == null || string.IsNullOrEmpty(consumer.ConsumerId))
			{
				return false;
			}
			lock (_lock)
			{
				for (int i = 0; i < _consumers.Count; i++)
				{
					if (string.Equals(_consumers[i].ConsumerId, consumer.ConsumerId, StringComparison.Ordinal))
					{
						return false;
					}
				}
				_consumers.Add(consumer);
				Debug.Log((object)$"[ClientLogRelay] Registered consumer: {consumer.ConsumerId} (total: {_consumers.Count})");
				return true;
			}
		}

		public static bool UnregisterConsumer(string consumerId)
		{
			if (string.IsNullOrEmpty(consumerId))
			{
				return false;
			}
			lock (_lock)
			{
				for (int i = 0; i < _consumers.Count; i++)
				{
					if (string.Equals(_consumers[i].ConsumerId, consumerId, StringComparison.Ordinal))
					{
						_consumers.RemoveAt(i);
						Debug.Log((object)$"[ClientLogRelay] Unregistered consumer: {consumerId} (total: {_consumers.Count})");
						return true;
					}
				}
				return false;
			}
		}

		public static void ReportArtifacts(ClientLogArtifacts artifacts)
		{
			if (artifacts == null)
			{
				return;
			}
			try
			{
				LogErrorWarningExtractor.Result result = LogErrorWarningExtractor.Extract(artifacts.LogBytes, artifacts.PlayerName, artifacts.PlatformId);
				artifacts.ErrorsWarningsReport = result.Report;
				artifacts.ErrorCount = result.ErrorCount;
				artifacts.WarningCount = result.WarningCount;
				artifacts.BenignSkipped = result.BenignSkipped;
				artifacts.DuplicatesCollapsed = result.DuplicatesCollapsed;
				artifacts.SourceBreakdown = result.SourceBreakdown;
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[ClientLogRelay] Extraction failed for " + artifacts.PlatformId + ": " + ex.Message));
				artifacts.ErrorsWarningsReport = "# Extraction error: " + ex.Message;
			}
			if (artifacts.ServerMods != null && artifacts.ServerMods.Count > 0)
			{
				try
				{
					artifacts.ModDiff = ModListDiff.Compute(artifacts.ModList, artifacts.ServerMods, artifacts.PlayerName, artifacts.PlatformId, artifacts.BrandLabel, artifacts.CapturedUtc);
				}
				catch (Exception ex2)
				{
					Debug.LogWarning((object)("[ClientLogRelay] ModListDiff failed for " + artifacts.PlatformId + ": " + ex2.Message));
				}
			}
			IClientLogConsumer[] array;
			lock (_lock)
			{
				if (_consumers.Count == 0)
				{
					Debug.Log((object)("[ClientLogRelay] No consumers; dropping artifacts for " + artifacts.PlatformId));
					return;
				}
				array = _consumers.ToArray();
			}
			foreach (IClientLogConsumer clientLogConsumer in array)
			{
				try
				{
					clientLogConsumer.OnClientArtifacts(artifacts);
				}
				catch (Exception ex3)
				{
					Debug.LogWarning((object)("[ClientLogRelay] Consumer '" + clientLogConsumer.ConsumerId + "' threw: " + ex3.Message));
				}
			}
		}

		public static bool TryClaimLoginSnapshotOwnership(string ownerId, int priority = 0)
		{
			if (string.IsNullOrEmpty(ownerId))
			{
				return false;
			}
			lock (_lock)
			{
				string text = AppDomain.CurrentDomain.GetData("FiresMods.ClientLogRelay.LoginSnapshotOwner") as string;
				int num2 = ((AppDomain.CurrentDomain.GetData("FiresMods.ClientLogRelay.LoginSnapshotOwnerPriority") is int num) ? num : int.MinValue);
				if (string.IsNullOrEmpty(text))
				{
					AppDomain.CurrentDomain.SetData("FiresMods.ClientLogRelay.LoginSnapshotOwner", ownerId);
					AppDomain.CurrentDomain.SetData("FiresMods.ClientLogRelay.LoginSnapshotOwnerPriority", priority);
					Debug.Log((object)$"[ClientLogRelay] Login snapshot owner: '{ownerId}' (priority {priority})");
					return true;
				}
				if (string.Equals(text, ownerId, StringComparison.Ordinal))
				{
					if (priority > num2)
					{
						AppDomain.CurrentDomain.SetData("FiresMods.ClientLogRelay.LoginSnapshotOwnerPriority", priority);
					}
					return true;
				}
				if (priority > num2)
				{
					Debug.Log((object)("[ClientLogRelay] Login snapshot owner changed: " + $"'{text}' (priority {num2}) ? '{ownerId}' (priority {priority})"));
					AppDomain.CurrentDomain.SetData("FiresMods.ClientLogRelay.LoginSnapshotOwner", ownerId);
					AppDomain.CurrentDomain.SetData("FiresMods.ClientLogRelay.LoginSnapshotOwnerPriority", priority);
					return true;
				}
				Debug.Log((object)("[ClientLogRelay] Login snapshot claim declined for '" + ownerId + "' " + $"(priority {priority}); current owner '{text}' (priority {num2})"));
				return false;
			}
		}

		public static bool IsLoginSnapshotOwner(string ownerId)
		{
			if (string.IsNullOrEmpty(ownerId))
			{
				return false;
			}
			string text = AppDomain.CurrentDomain.GetData("FiresMods.ClientLogRelay.LoginSnapshotOwner") as string;
			if (string.IsNullOrEmpty(text))
			{
				return true;
			}
			return string.Equals(text, ownerId, StringComparison.Ordinal);
		}

		public static string GetLoginSnapshotOwner()
		{
			return AppDomain.CurrentDomain.GetData("FiresMods.ClientLogRelay.LoginSnapshotOwner") as string;
		}

		public static void RegisterLogRequestHandler(ILogRequestHandler handler)
		{
			lock (_lock)
			{
				_logRequestHandler = handler;
				Debug.Log((object)((handler != null) ? ("[ClientLogRelay] LogRequestHandler installed: " + handler.GetType().FullName) : "[ClientLogRelay] LogRequestHandler cleared"));
			}
		}

		public static bool TryDispatchLogRequest(string messageId, string discordUserId, string emoji)
		{
			ILogRequestHandler logRequestHandler;
			lock (_lock)
			{
				logRequestHandler = _logRequestHandler;
			}
			if (logRequestHandler == null)
			{
				return false;
			}
			if (!LogRequestRegistry.TryGet(messageId, out var ctx))
			{
				return false;
			}
			bool flag;
			try
			{
				flag = logRequestHandler.IsAuthorized(discordUserId);
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[ClientLogRelay] ILogRequestHandler.IsAuthorized threw: " + ex.Message));
				return false;
			}
			if (!flag)
			{
				Debug.Log((object)("[ClientLogRelay] Log request from unauthorised Discord user '" + discordUserId + "' on message " + messageId + "; ignoring"));
				return false;
			}
			try
			{
				logRequestHandler.HandleRequest(ctx, discordUserId, emoji);
				return true;
			}
			catch (Exception ex2)
			{
				Debug.LogWarning((object)("[ClientLogRelay] ILogRequestHandler.HandleRequest threw: " + ex2.Message));
				return false;
			}
		}
	}
	[HarmonyPatch]
	public static class ClientLogRelayIntegration
	{
		[CompilerGenerated]
		private sealed class <>c__DisplayClass41_0
		{
			public bool playerReady;

			internal void <DelayedClientPush>b__0(Player _)
			{
				playerReady = true;
			}
		}

		[CompilerGenerated]
		private sealed class <DelayedClientPush>d__41 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			private <>c__DisplayClass41_0 <>8__1;

			private float <timeoutAt>5__2;

			private long <serverId>5__3;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <DelayedClientPush>d__41(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>8__1 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
				//IL_00fb: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>8__1 = new <>c__DisplayClass41_0();
					Debug.Log((object)"[ClientLogRelay][DIAG] DelayedClientPush ENTER (queueing PlayerSpawnGate.RunWhenLocalReady)");
					<>8__1.playerReady = false;
					PlayerSpawnGate.RunWhenLocalReady(60f, delegate
					{
						<>8__1.playerReady = true;
					});
					<timeoutAt>5__2 = Time.realtimeSinceStartup + 65f;
					goto IL_009f;
				case 1:
					<>1__state = -1;
					goto IL_009f;
				case 2:
					<>1__state = -1;
					Debug.Log((object)"[ClientLogRelay][DIAG] DelayedClientPush 2s grace elapsed, about to call PushLogToServerCoroutine");
					if ((Object)(object)ZNet.instance == (Object)null)
					{
						Debug.Log((object)"[ClientLogRelay] ZNet.instance gone before push — cancelling.");
						return false;
					}
					if (ZRoutedRpc.instance == null)
					{
						Debug.LogWarning((object)"[ClientLogRelay] ZRoutedRpc.instance null at push time — cancelling.");
						return false;
					}
					<serverId>5__3 = ZRoutedRpc.instance.GetServerPeerID();
					if (<serverId>5__3 == 0)
					{
						Debug.LogWarning((object)"[ClientLogRelay] ServerPeerID is 0 at push time — connection no longer valid; cancelling.");
						return false;
					}
					<>2__current = ((MonoBehaviour)ZNet.instance).StartCoroutine(PushLogToServerCoroutine(<serverId>5__3));
					<>1__state = 3;
					return true;
				case 3:
					{
						<>1__state = -1;
						return false;
					}
					IL_009f:
					if (!<>8__1.playerReady && Time.realtimeSinceStartup < <timeoutAt>5__2)
					{
						<>2__current = null;
						<>1__state = 1;
						return true;
					}
					if (!<>8__1.playerReady)
					{
						Debug.LogWarning((object)"[ClientLogRelay] Player not ready within 60s — cancelling log push for this session.");
						return false;
					}
					Debug.Log((object)"[ClientLogRelay][DIAG] DelayedClientPush playerReady=true, entering 2s post-ready grace");
					<>2__current = (object)new WaitForSeconds(2f);
					<>1__state = 2;
					return true;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <PeriodicClientRepush>d__43 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			private long <serverId>5__1;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <PeriodicClientRepush>d__43(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_003a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0044: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					break;
				case 1:
					<>1__state = -1;
					if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null)
					{
						Debug.Log((object)"[ClientLogRelay] Repush skipped: ZNet or ZRoutedRpc is null (will retry next interval)");
						break;
					}
					<serverId>5__1 = ZRoutedRpc.instance.GetServerPeerID();
					if (<serverId>5__1 == 0)
					{
						Debug.Log((object)"[ClientLogRelay] Repush skipped: not connected to server (will retry next interval)");
						break;
					}
					<>2__current = ((MonoBehaviour)ZNet.instance).StartCoroutine(RepushLogToServerCoroutine(<serverId>5__1));
					<>1__state = 2;
					return true;
				case 2:
					<>1__state = -1;
					break;
				}
				<>2__current = (object)new WaitForSeconds(900f);
				<>1__state = 1;
				return true;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <PeriodicTransferCleanup>d__44 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <PeriodicTransferCleanup>d__44(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0029: Unknown result type (might be due to invalid IL or missing references)
				//IL_0033: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					break;
				case 1:
					<>1__state = -1;
					ClientLogChunkedTransfer.CleanupTimedOutTransfers();
					break;
				}
				<>2__current = (object)new WaitForSeconds(30f);
				<>1__state = 1;
				return true;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <PostDisconnectLogAsync>d__52 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			public string platformId;

			public string playerName;

			public string requestedByName;

			private Exception <ex>5__1;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <PostDisconnectLogAsync>d__52(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<ex>5__1 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0026: Unknown result type (might be due to invalid IL or missing references)
				//IL_0030: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = (object)new WaitForSeconds(1f);
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					try
					{
						PostDisconnectLog(platformId, playerName, requestedByName);
					}
					catch (Exception ex)
					{
						<ex>5__1 = ex;
						Debug.LogWarning((object)("[ClientLogRelay] PostDisconnectLogAsync threw: " + <ex>5__1.Message));
					}
					return false;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <PushLogToServerCoroutine>d__42 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			public long serverId;

			private byte[] <logBytes>5__1;

			private Dictionary<string, string> <modList>5__2;

			private string <steamId>5__3;

			private int <logSize>5__4;

			private int <totalChunks>5__5;

			private Exception <ex>5__6;

			private Exception <sidEx>5__7;

			private ZPackage <metaPayload>5__8;

			private byte[] <metaData>5__9;

			private Dictionary<string, string>.Enumerator <>s__10;

			private KeyValuePair<string, string> <kv>5__11;

			private int <chunkIndex>5__12;

			private ZPackage <payload>5__13;

			private int <offset>5__14;

			private int <size>5__15;

			private byte[] <chunk>5__16;

			private byte[] <packageData>5__17;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <PushLogToServerCoroutine>d__42(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<logBytes>5__1 = null;
				<modList>5__2 = null;
				<steamId>5__3 = null;
				<ex>5__6 = null;
				<sidEx>5__7 = null;
				<metaPayload>5__8 = null;
				<metaData>5__9 = null;
				<>s__10 = default(Dictionary<string, string>.Enumerator);
				<kv>5__11 = default(KeyValuePair<string, string>);
				<payload>5__13 = null;
				<chunk>5__16 = null;
				<packageData>5__17 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0207: Unknown result type (might be due to invalid IL or missing references)
				//IL_0211: Expected O, but got Unknown
				//IL_0433: Unknown result type (might be due to invalid IL or missing references)
				//IL_043d: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					Debug.Log((object)$"[ClientLogRelay] PushLogToServer entered (serverId={serverId}) - reading local LogOutput.log...");
					<logBytes>5__1 = null;
					<modList>5__2 = null;
					try
					{
						ClientLogCollector.TryCollect(out <logBytes>5__1, out <modList>5__2);
						if (<modList>5__2 == null)
						{
							<modList>5__2 = new Dictionary<string, string>(0, StringComparer.OrdinalIgnoreCase);
						}
						byte[] array2 = <logBytes>5__1;
						Debug.Log((object)$"[ClientLogRelay] PushLogToServer read complete - logBytes={((array2 != null) ? array2.Length : 0)}B, mods={<modList>5__2.Count}.");
					}
					catch (Exception ex)
					{
						<ex>5__6 = ex;
						Debug.LogWarning((object)("[ClientLogRelay] TryCollect failed: " + <ex>5__6.Message));
						return false;
					}
					<>2__current = null;
					<>1__state = 1;
					return true;
				case 1:
				{
					<>1__state = -1;
					<steamId>5__3 = string.Empty;
					try
					{
						<steamId>5__3 = GetOwnSteamId() ?? string.Empty;
					}
					catch (Exception ex)
					{
						<sidEx>5__7 = ex;
						Debug.Log((object)("[ClientLogRelay] SteamID unavailable (" + <sidEx>5__7.GetType().Name + ": " + <sidEx>5__7.Message + "); pushing without it."));
					}
					<logBytes>5__1 = <logBytes>5__1 ?? new byte[0];
					<logSize>5__4 = <logBytes>5__1.Length;
					<totalChunks>5__5 = ((<logSize>5__4 == 0) ? 1 : ((<logSize>5__4 + 409600 - 1) / 409600));
					Debug.Log((object)$"[ClientLogRelay] Pushing log to server: {<logSize>5__4}B in {<totalChunks>5__5} chunk(s) + metadata, mods={<modList>5__2.Count}");
					<metaPayload>5__8 = new ZPackage();
					<metaPayload>5__8.Write(-1);
					<metaPayload>5__8.Write(<totalChunks>5__5);
					<metaPayload>5__8.Write(<modList>5__2.Count);
					<>s__10 = <modList>5__2.GetEnumerator();
					try
					{
						while (<>s__10.MoveNext())
						{
							<kv>5__11 = <>s__10.Current;
							<metaPayload>5__8.Write(<kv>5__11.Key ?? string.Empty);
							<metaPayload>5__8.Write(<kv>5__11.Value ?? string.Empty);
							<kv>5__11 = default(KeyValuePair<string, string>);
						}
					}
					finally
					{
						((IDisposable)<>s__10).Dispose();
					}
					<>s__10 = default(Dictionary<string, string>.Enumerator);
					<metaPayload>5__8.Write(<steamId>5__3 ?? string.Empty);
					<metaData>5__9 = <metaPayload>5__8.GetArray();
					if (<metaData>5__9 != null && <metaData>5__9.Length > 420000)
					{
						Debug.LogWarning((object)$"[ClientLogRelay] Metadata ZPackage is {<metaData>5__9.Length}B - very large mod list!");
					}
					if (ZRoutedRpc.instance == null)
					{
						Debug.LogWarning((object)"[ClientLogRelay] ZRoutedRpc gone before metadata send - aborting push.");
						return false;
					}
					ZRoutedRpc.instance.InvokeRoutedRPC(serverId, "VAG_SubmitClientLog", new object[1] { <metaPayload>5__8 });
					byte[] array = <metaData>5__9;
					Debug.Log((object)$"[ClientLogRelay]  -> Sent metadata ({((array != null) ? array.Length : 0)}B total, {<modList>5__2.Count} mods)");
					<metaPayload>5__8 = null;
					<metaData>5__9 = null;
					<>2__current = null;
					<>1__state = 2;
					return true;
				}
				case 2:
					<>1__state = -1;
					<chunkIndex>5__12 = 0;
					break;
				case 3:
					<>1__state = -1;
					<payload>5__13 = null;
					<chunk>5__16 = null;
					<packageData>5__17 = null;
					<chunkIndex>5__12++;
					break;
				}
				if (<chunkIndex>5__12 < <totalChunks>5__5)
				{
					if (ZRoutedRpc.instance == null)
					{
						Debug.LogWarning((object)$"[ClientLogRelay] ZRoutedRpc gone at chunk {<chunkIndex>5__12 + 1}/{<totalChunks>5__5} - aborting push.");
						return false;
					}
					<payload>5__13 = new ZPackage();
					<payload>5__13.Write(<chunkIndex>5__12);
					<payload>5__13.Write(<totalChunks>5__5);
					<offset>5__14 = <chunkIndex>5__12 * 409600;
					<size>5__15 = Math.Min(409600, <logSize>5__4 - <offset>5__14);
					<chunk>5__16 = new byte[<size>5__15];
					if (<size>5__15 > 0)
					{
						Array.Copy(<logBytes>5__1, <offset>5__14, <chunk>5__16, 0, <size>5__15);
					}
					<payload>5__13.Write(<chunk>5__16);
					<packageData>5__17 = <payload>5__13.GetArray();
					if (<packageData>5__17 != null && <packageData>5__17.Length > 420000)
					{
						Debug.LogWarning((object)$"[ClientLogRelay] CRITICAL: Chunk {<chunkIndex>5__12 + 1} ZPackage is {<packageData>5__17.Length}B!");
					}
					ZRoutedRpc.instance.InvokeRoutedRPC(serverId, "VAG_SubmitClientLog", new object[1] { <payload>5__13 });
					if (<totalChunks>5__5 > 1)
					{
						object[] obj = new object[4]
						{
							<chunkIndex>5__12 + 1,
							<totalChunks>5__5,
							<size>5__15,
							null
						};
						byte[] array3 = <packageData>5__17;
						obj[3] = ((array3 != null) ? array3.Length : 0);
						Debug.Log((object)string.Format("[ClientLogRelay]  -> Sent chunk {0}/{1} ({2}B data, {3}B total)", obj));
					}
					<>2__current = null;
					<>1__state = 3;
					return true;
				}
				Debug.Log((object)$"[ClientLogRelay] Push complete: {<logSize>5__4}B in {<totalChunks>5__5} chunk(s)");
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <RepushLogToServerCoroutine>d__45 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			public long serverId;

			private byte[] <logBytes>5__1;

			private string <steamId>5__2;

			private int <logSize>5__3;

			private int <totalChunks>5__4;

			private Dictionary<string, string> <modList>5__5;

			private Exception <ex>5__6;

			private ZPackage <payload>5__7;

			private int <chunkIndex>5__8;

			private ZPackage <payload>5__9;

			private int <offset>5__10;

			private int <size>5__11;

			private byte[] <chunk>5__12;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <RepushLogToServerCoroutine>d__45(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<logBytes>5__1 = null;
				<steamId>5__2 = null;
				<modList>5__5 = null;
				<ex>5__6 = null;
				<payload>5__7 = null;
				<payload>5__9 = null;
				<chunk>5__12 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0109: Unknown result type (might be due to invalid IL or missing references)
				//IL_0113: Expected O, but got Unknown
				//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
				//IL_01e6: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<logBytes>5__1 = null;
					try
					{
						ClientLogCollector.TryCollect(out <logBytes>5__1, out <modList>5__5);
						<modList>5__5 = null;
					}
					catch (Exception ex)
					{
						<ex>5__6 = ex;
						Debug.LogWarning((object)("[ClientLogRelay] Repush TryCollect failed: " + <ex>5__6.Message));
						return false;
					}
					<>2__current = null;
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					<steamId>5__2 = string.Empty;
					try
					{
						<steamId>5__2 = GetOwnSteamId() ?? string.Empty;
					}
					catch
					{
					}
					<logBytes>5__1 = <logBytes>5__1 ?? new byte[0];
					<logSize>5__3 = <logBytes>5__1.Length;
					if (<logSize>5__3 <= 409600)
					{
						if (ZRoutedRpc.instance == null)
						{
							return false;
						}
						<payload>5__7 = new ZPackage();
						<payload>5__7.Write(<logBytes>5__1);
						<payload>5__7.Write(<steamId>5__2);
						ZRoutedRpc.instance.InvokeRoutedRPC(serverId, "VAG_UpdateClientLog", new object[1] { <payload>5__7 });
						Debug.Log((object)string.Format("[ClientLogRelay] Re-pushed '{0}' to server: {1}B (single message)", "VAG_UpdateClientLog", <logSize>5__3));
						return false;
					}
					<totalChunks>5__4 = (<logSize>5__3 + 409600 - 1) / 409600;
					Debug.Log((object)$"[ClientLogRelay] Re-pushing large log to server: {<logSize>5__3}B in {<totalChunks>5__4} chunk(s)");
					<chunkIndex>5__8 = 0;
					break;
				case 2:
					<>1__state = -1;
					<payload>5__9 = null;
					<chunk>5__12 = null;
					<chunkIndex>5__8++;
					break;
				}
				if (<chunkIndex>5__8 < <totalChunks>5__4)
				{
					if (ZRoutedRpc.instance == null)
					{
						return false;
					}
					<payload>5__9 = new ZPackage();
					<payload>5__9.Write(<chunkIndex>5__8);
					<payload>5__9.Write(<totalChunks>5__4);
					<offset>5__10 = <chunkIndex>5__8 * 409600;
					<size>5__11 = Math.Min(409600, <logSize>5__3 - <offset>5__10);
					<chunk>5__12 = new byte[<size>5__11];
					if (<size>5__11 > 0)
					{
						Array.Copy(<logBytes>5__1, <offset>5__10, <chunk>5__12, 0, <size>5__11);
					}
					<payload>5__9.Write(<chunk>5__12);
					<payload>5__9.Write(<steamId>5__2);
					ZRoutedRpc.instance.InvokeRoutedRPC(serverId, "VAG_UpdateClientLog", new object[1] { <payload>5__9 });
					if (<totalChunks>5__4 > 1)
					{
						Debug.Log((object)$"[ClientLogRelay]  -> Re-pushed chunk {<chunkIndex>5__8 + 1}/{<totalChunks>5__4} ({<size>5__11}B)");
					}
					<>2__current = null;
					<>1__state = 2;
					return true;
				}
				Debug.Log((object)$"[ClientLogRelay] Re-push complete: {<logSize>5__3}B in {<totalChunks>5__4} chunk(s)");
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private const string MOD_ID = "FiresDiscordIntegration";

		private const string PLUGIN_BRAND = "FiresDiscordIntegration";

		private const string FallbackWebhookDisplayName = "Server Relay";

		private const string RPC_SUBMIT = "VAG_SubmitClientLog";

		private const string RPC_UPDATE = "VAG_UpdateClientLog";

		private const float CLIENT_PUSH_DELAY_SECONDS = 15f;

		private const float CLIENT_REPUSH_INTERVAL_SECONDS = 900f;

		private const float GLOBAL_MIN_POST_INTERVAL_SECONDS = 10f;

		private static readonly HashSet<long> _postedPeers = new HashSet<long>();

		private static readonly object _debounceLock = new object();

		private static DateTime _lastPostUtc = DateTime.MinValue;

		private static bool _clientPushedThisSession;

		private static bool _initialized;

		private static bool _isServerSide;

		private static readonly Dictionary<long, string> _peerToPlatformId = new Dictionary<long, string>();

		private static readonly Dictionary<long, string> _peerToPlayerName = new Dictionary<long, string>();

		private static bool MasterEnabled => DiscordIntegrationConfig.NotifyClientLoginArtifacts?.Value ?? true;

		private static string PrimaryWebhookUrl => DiscordIntegrationConfig.ResolveWebhookUrl(WebhookCategory.ClientLog) ?? string.Empty;

		private static string SecondaryWebhookUrl => string.Empty;

		private static bool AutoSendFullLog => DiscordIntegrationConfig.AttachFullClientLogOnLogin?.Value ?? false;

		private static string BotTokenValue => BotTokenFile.Token;

		private static string ServerManagerWebhookUrl => DiscordIntegrationConfig.ServerManagerWebhookUrl?.Value ?? string.Empty;

		private static string ServerManagerChannelIdValue
		{
			get
			{
				string text = DiscordIntegrationConfig.ServerManagerChannelId?.Value;
				if (!string.IsNullOrEmpty(text))
				{
					return text;
				}
				return DiscordIntegrationConfig.StatusChannelId?.Value ?? string.Empty;
			}
		}

		public static string ClientLogsRoot => ClientLogRelayPaths.GetDefaultClientLogsDir("FiresDiscordIntegration");

		private static string ResolveWebhookName()
		{
			string text = DiscordIntegrationConfig.WebhookDisplayName?.Value;
			string fallback = ((!string.IsNullOrEmpty(text) && text != "Valheim Server") ? text : "Server Relay");
			return DiscordBotIdentity.ResolveUsername(fallback);
		}

		private static string ResolveAvatarUrl()
		{
			string text = DiscordIntegrationConfig.WebhookAvatarUrl?.Value;
			if (!string.IsNullOrEmpty(text))
			{
				return text;
			}
			return DiscordBotIdentity.ResolveAvatarUrl(null);
		}

		public static void InitServer()
		{
			if (!_initialized)
			{
				_initialized = true;
				_isServerSide = true;
				if (!MasterEnabled)
				{
					Debug.Log((object)"[ClientLogRelay] Disabled by config (Discord.ClientLogs.NotifyClientLoginArtifacts=false) - skipping initialisation.");
					return;
				}
				TryRun("RegisterDiskConsumer", RegisterDiskConsumer);
				TryRun("RegisterDiscordConsumer", RegisterDiscordConsumer);
				TryRun("StartReactionPoller", StartReactionPoller);
				TryRun("StartServerStatusPoster", StartServerStatusPoster);
				Debug.Log((object)"[ClientLogRelay] Server-side integration initialised.");
			}
		}

		public static void InitClient()
		{
			if (!_initialized)
			{
				_initialized = true;
				_isServerSide = false;
				Debug.Log((object)"[ClientLogRelay] Client-side integration initialised (will push log on connect).");
			}
		}

		private static void TryRun(string label, Action action)
		{
			try
			{
				action();
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[ClientLogRelay] " + label + " failed: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static void RegisterDiskConsumer()
		{
			DiskConsumer consumer = new DiskConsumer("FiresDiscordIntegration.ClientLogRelay.Disk", () => ClientLogRelayPaths.GetDefaultClientLogsDir("FiresDiscordIntegration"), () => true);
			ClientLogRelay.RegisterConsumer(consumer);
			Debug.Log((object)("[ClientLogRelay] Disk consumer will persist artifacts under: " + ClientLogsRoot));
		}

		private static void RegisterDiscordConsumer()
		{
			DiscordWebhookConsumer discordWebhookConsumer = new DiscordWebhookConsumer("FiresDiscordIntegration.ClientLogRelay.Discord.Primary");
			discordWebhookConsumer.EnabledGate = () => MasterEnabled && !string.IsNullOrEmpty(PrimaryWebhookUrl);
			discordWebhookConsumer.WebhookUrl = () => PrimaryWebhookUrl;
			discordWebhookConsumer.WebhookName = ResolveWebhookName;
			discordWebhookConsumer.AvatarUrl = ResolveAvatarUrl;
			discordWebhookConsumer.BrandLabel = () => "FiresDiscordIntegration";
			discordWebhookConsumer.AttachFullLog = () => AutoSendFullLog;
			discordWebhookConsumer.AttachModList = () => true;
			discordWebhookConsumer.AttachErrorsWarnings = () => true;
			discordWebhookConsumer.OnlyIfErrorsOrWarnings = () => false;
			discordWebhookConsumer.EnableLogRequestReaction = ReactionEnabled;
			discordWebhookConsumer.BotToken = () => BotTokenValue;
			discordWebhookConsumer.ServerName = () => ((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetWorldName() : "Unknown";
			DiscordWebhookConsumer consumer = discordWebhookConsumer;
			ClientLogRelay.RegisterConsumer(consumer);
			discordWebhookConsumer = new DiscordWebhookConsumer("FiresDiscordIntegration.ClientLogRelay.Discord.Secondary");
			discordWebhookConsumer.EnabledGate = () => MasterEnabled && !string.IsNullOrEmpty(SecondaryWebhookUrl);
			discordWebhookConsumer.WebhookUrl = () => SecondaryWebhookUrl;
			discordWebhookConsumer.WebhookName = ResolveWebhookName;
			discordWebhookConsumer.AvatarUrl = ResolveAvatarUrl;
			discordWebhookConsumer.BrandLabel = () => "FiresDiscordIntegration";
			discordWebhookConsumer.AttachFullLog = () => AutoSendFullLog;
			discordWebhookConsumer.AttachModList = () => true;
			discordWebhookConsumer.AttachErrorsWarnings = () => true;
			discordWebhookConsumer.OnlyIfErrorsOrWarnings = () => false;
			discordWebhookConsumer.EnableLogRequestReaction = () => false;
			discordWebhookConsumer.BotToken = () => null;
			discordWebhookConsumer.ServerName = () => ((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetWorldName() : "Unknown";
			DiscordWebhookConsumer consumer2 = discordWebhookConsumer;
			ClientLogRelay.RegisterConsumer(consumer2);
			static bool ReactionEnabled()
			{
				return !AutoSendFullLog;
			}
		}

		private static void StartReactionPoller()
		{
			string botTokenValue = BotTokenValue;
			if (string.IsNullOrEmpty(botTokenValue))
			{
				Debug.Log((object)"[ClientLogRelay] No bot token configured \ufffd reaction poller disabled.");
				return;
			}
			if (string.IsNullOrEmpty(PrimaryWebhookUrl))
			{
				Debug.Log((object)"[ClientLogRelay] No primary webhook URL configured — reaction poller disabled.");
				return;
			}
			ReactionPoller.Create(() => BotTokenValue, () => PrimaryWebhookUrl, () => ClientLogsRoot, ResolveWebhookName);
			Debug.Log((object)"[ClientLogRelay] Reaction poller started — polling every 15s for log requests.");
		}

		private static void StartServerStatusPoster()
		{
			string botTokenValue = BotTokenValue;
			string serverManagerChannelIdValue = ServerManagerChannelIdValue;
			if (string.IsNullOrEmpty(botTokenValue))
			{
				Debug.Log((object)"[ClientLogRelay] No bot token configured (Discord.BotListener.BotToken) - Server Manager panel disabled.");
			}
			else if (string.IsNullOrEmpty(serverManagerChannelIdValue))
			{
				Debug.Log((object)"[ClientLogRelay] No channel ID configured (Discord.ServerManager.ServerManagerChannelId or Discord.BotListener.StatusChannelId) - Server Manager panel disabled.");
			}
			else
			{
				Debug.Log((object)"[ClientLogRelay] Server Manager panel configured - will start when ZNet is ready.");
			}
		}

		[HarmonyPatch(typeof(ZNet), "Awake")]
		[HarmonyPostfix]
		public static void ZNet_Awake_Postfix(ZNet __instance)
		{
			try
			{
				if (!_initialized)
				{
					if ((Object)(object)__instance != (Object)null && __instance.IsServer())
					{
						InitServer();
					}
					else
					{
						InitClient();
					}
				}
				if (ZRoutedRpc.instance == null)
				{
					Debug.LogWarning((object)"[ClientLogRelay] ZNet.Awake postfix fired but ZRoutedRpc.instance is null - RPC registration skipped.");
				}
				else if (_isServerSide)
				{
					ZRoutedRpc.instance.Register<ZPackage>("VAG_SubmitClientLog", (Action<long, ZPackage>)OnServerReceiveLog);
					ZRoutedRpc.instance.Register<ZPackage>("VAG_UpdateClientLog", (Action<long, ZPackage>)OnServerReceiveLogUpdate);
					Debug.Log((object)"[ClientLogRelay] Registered server-side handlers for 'VAG_SubmitClientLog' + 'VAG_UpdateClientLog'.");
					((MonoBehaviour)ZNet.instance).StartCoroutine(PeriodicTransferCleanup());
					string botTokenValue = BotTokenValue;
					string serverManagerWebhookUrl = ServerManagerWebhookUrl;
					string serverManagerChannelIdValue = ServerManagerChannelIdValue;
					if (!string.IsNullOrEmpty(botTokenValue) && !string.IsNullOrEmpty(serverManagerChannelIdValue))
					{
						ServerHeartbeat.OnServerStart(botTokenValue, serverManagerWebhookUrl, serverManagerChannelIdValue, "FiresDiscordIntegration Relay", "FiresDiscordIntegration");
						Debug.Log((object)"[ClientLogRelay] Server heartbeat + control panel started.");
					}
				}
				else
				{
					_clientPushedThisSession = false;
					Debug.Log((object)"[ClientLogRelay] Client-side ZNet awake — push-on-connect armed.");
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[ClientLogRelay] ZNet.Awake postfix threw: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
		[HarmonyPostfix]
		public static void ZNet_RPC_PeerInfo_Postfix(ZNet __instance, ZRpc rpc)
		{
			try
			{
				if (!_isServerSide && !((Object)(object)__instance == (Object)null) && rpc != null)
				{
					if (_clientPushedThisSession)
					{
						Debug.Log((object)"[ClientLogRelay] PeerInfo postfix fired but client already pushed this session — skipping.");
						return;
					}
					_clientPushedThisSession = true;
					Debug.Log((object)$"[ClientLogRelay] PeerInfo handshake complete — scheduling log push in {15f:F0}s.");
					((MonoBehaviour)__instance).StartCoroutine(DelayedClientPush());
					((MonoBehaviour)__instance).StartCoroutine(PeriodicClientRepush());
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[ClientLogRelay] ZNet.RPC_PeerInfo postfix threw: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		[IteratorStateMachine(typeof(<DelayedClientPush>d__41))]
		private static IEnumerator DelayedClientPush()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <DelayedClientPush>d__41(0);
		}

		[IteratorStateMachine(typeof(<PushLogToServerCoroutine>d__42))]
		private static IEnumerator PushLogToServerCoroutine(long serverId)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <PushLogToServerCoroutine>d__42(0)
			{
				serverId = serverId
			};
		}

		[IteratorStateMachine(typeof(<PeriodicClientRepush>d__43))]
		private static IEnumerator PeriodicClientRepush()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <PeriodicClientRepush>d__43(0);
		}

		[IteratorStateMachine(typeof(<PeriodicTransferCleanup>d__44))]
		private static IEnumerator PeriodicTransferCleanup()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <PeriodicTransferCleanup>d__44(0);
		}

		[IteratorStateMachine(typeof(<RepushLogToServerCoroutine>d__45))]
		private static IEnumerator RepushLogToServerCoroutine(long serverId)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <RepushLogToServerCoroutine>d__45(0)
			{
				serverId = serverId
			};
		}

		public static void ResetDebounce()
		{
			lock (_debounceLock)
			{
				int count = _postedPeers.Count;
				_postedPeers.Clear();
				_lastPostUtc = DateTime.MinValue;
				Debug.Log((object)$"[ClientLogRelay] Debounce reset (cleared {count} posted peers).");
			}
		}

		private static void OnServerReceiveLog(long sender, ZPackage pkg)
		{
			try
			{
				if (pkg == null)
				{
					Debug.LogWarning((object)$"[ClientLogRelay] Submit from peer {sender} had a null payload.");
					return;
				}
				int num = pkg.ReadInt();
				int num2 = pkg.ReadInt();
				if (num == -1)
				{
					int num3 = pkg.ReadInt();
					if (num3 < 0)
					{
						num3 = 0;
					}
					Dictionary<string, string> dictionary = new Dictionary<string, string>(num3, StringComparer.OrdinalIgnoreCase);
					for (int i = 0; i < num3; i++)
					{
						string text = pkg.ReadString();
						string text2 = pkg.ReadString();
						if (!string.IsNullOrEmpty(text))
						{
							dictionary[text] = text2 ?? string.Empty;
						}
					}
					string steamId = null;
					try
					{
						steamId = pkg.ReadString();
					}
					catch
					{
					}
					ClientLogChunkedTransfer.StoreMetadata(sender, num2, dictionary, steamId);
					Debug.Log((object)$"[ClientLogRelay] ? Received metadata from peer {sender}: {dictionary.Count} mods, expecting {num2} chunk(s)");
					return;
				}
				byte[] array = pkg.ReadByteArray();
				Debug.Log((object)$"[ClientLogRelay] ? Received chunk {num + 1}/{num2} from peer {sender} ({array.Length}B)");
				ClientLogChunkedTransfer.TransferResult transferResult = ClientLogChunkedTransfer.ReceiveChunk(sender, num, num2, array);
				if (transferResult == null)
				{
					return;
				}
				byte[] logBytes = transferResult.LogBytes;
				Dictionary<string, string> dictionary2 = transferResult.ModList;
				string steamId2 = transferResult.SteamId;
				if (dictionary2 == null)
				{
					dictionary2 = new Dictionary<string, string>(0, StringComparer.OrdinalIgnoreCase);
				}
				Debug.Log((object)$"[ClientLogRelay] ? Complete log received from peer {sender}: {logBytes.Length}B, {dictionary2.Count} mods");
				string peerPlayerName = GetPeerPlayerName(sender);
				string text3 = ((!string.IsNullOrEmpty(steamId2)) ? steamId2 : sender.ToString());
				lock (_debounceLock)
				{
					if (!_postedPeers.Add(sender))
					{
						Debug.Log((object)("[ClientLogRelay] Dropping duplicate submission from '" + peerPlayerName + "' (" + text3 + ") — already posted this session."));
						return;
					}
					TimeSpan timeSpan = DateTime.UtcNow - _lastPostUtc;
					if (timeSpan.TotalSeconds < 10.0)
					{
						_postedPeers.Remove(sender);
						Debug.Log((object)$"[ClientLogRelay] Rate-limiting post from '{peerPlayerName}' ({text3}); {timeSpan.TotalSeconds:F1}s since last post (floor {10f:F0}s).");
						return;
					}
					_lastPostUtc = DateTime.UtcNow;
				}
				Dictionary<string, string> dictionary3 = null;
				try
				{
					dictionary3 = ClientLogCollector.BuildLocalModList();
				}
				catch (Exception ex)
				{
					Debug.Log((object)("[ClientLogRelay] Could not enumerate server-side mod list: " + ex.GetType().Name + ": " + ex.Message));
				}
				ClientLogArtifacts artifacts = new ClientLogArtifacts(text3, peerPlayerName, logBytes, dictionary2, dictionary3, "FiresDiscordIntegration");
				ClientLogRelay.ReportArtifacts(artifacts);
				lock (_debounceLock)
				{
					_peerToPlatformId[sender] = text3;
					_peerToPlayerName[sender] = peerPlayerName;
				}
				Debug.Log((object)$"[ClientLogRelay] Processed log from '{peerPlayerName}' ({text3}): {logBytes.Length} bytes, {dictionary2.Count} client mods, {dictionary3?.Count ?? 0} server mods.");
			}
			catch (Exception ex2)
			{
				Debug.LogWarning((object)("[ClientLogRelay] Failed to process submitted log: " + ex2.Message));
			}
		}

		private static void OnServerReceiveLogUpdate(long sender, ZPackage pkg)
		{
			try
			{
				if (pkg == null)
				{
					return;
				}
				pkg.SetPos(0);
				int num = pkg.ReadInt();
				pkg.SetPos(0);
				string text = null;
				byte[] array2;
				if (num >= 0 && num < 1000)
				{
					int num2 = pkg.ReadInt();
					int num3 = pkg.ReadInt();
					byte[] array = pkg.ReadByteArray();
					try
					{
						text = pkg.ReadString();
					}
					catch
					{
					}
					Debug.Log((object)$"[ClientLogRelay] ? Received update chunk {num2 + 1}/{num3} from peer {sender} ({array.Length}B)");
					ClientLogChunkedTransfer.TransferResult transferResult = ClientLogChunkedTransfer.ReceiveUpdateChunk(sender, num2, num3, array, text);
					if (transferResult == null)
					{
						return;
					}
					array2 = transferResult.LogBytes;
					text = transferResult.SteamId;
					Debug.Log((object)$"[ClientLogRelay] ? Complete update received from peer {sender}: {array2.Length}B");
				}
				else
				{
					array2 = pkg.ReadByteArray();
					try
					{
						text = pkg.ReadString();
					}
					catch
					{
					}
				}
				string peerPlayerName = GetPeerPlayerName(sender);
				string text2 = ((!string.IsNullOrEmpty(text)) ? text : sender.ToString());
				if (array2 == null || array2.Length == 0)
				{
					Debug.Log((object)("[ClientLogRelay] Update from '" + peerPlayerName + "' (" + text2 + ") had empty log \ufffd skipping."));
					return;
				}
				string clientLogsRoot = ClientLogsRoot;
				if (!string.IsNullOrEmpty(clientLogsRoot))
				{
					Dictionary<string, string> dictionary = new Dictionary<string, string>(0, StringComparer.OrdinalIgnoreCase);
					string text3 = peerPlayerName ?? "unknown";
					char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
					text3 = string.Concat(text3.Split(invalidFileNameChars));
					string text4 = (text2 ?? "unknown").Replace(":", "_").Replace("/", "_").Replace("\\", "_");
					string path = Path.Combine(clientLogsRoot, text3 + "_" + text4, "modlist.txt");
					if (File.Exists(path))
					{
						try
						{
							string[] array3 = File.ReadAllLines(path);
							foreach (string text5 in array3)
							{
								if (!string.IsNullOrEmpty(text5) && !text5.StartsWith("#"))
								{
									int num4 = text5.IndexOf('=');
									if (num4 > 0)
									{
										dictionary[text5.Substring(0, num4)] = text5.Substring(num4 + 1);
									}
								}
							}
						}
						catch
						{
						}
					}
					Dictionary<string, string> dictionary2 = null;
					try
					{
						dictionary2 = ClientLogCollector.BuildLocalModList();
					}
					catch
					{
					}
					ClientLogArtifacts clientLogArtifacts = new ClientLogArtifacts(text2, peerPlayerName, array2, dictionary, dictionary2, "FiresDiscordIntegration");
					try
					{
						LogErrorWarningExtractor.Result result = LogErrorWarningExtractor.Extract(array2, peerPlayerName, text2);
						clientLogArtifacts.ErrorsWarningsReport = result.Report;
						clientLogArtifacts.ErrorCount = result.ErrorCount;
						clientLogArtifacts.WarningCount = result.WarningCount;
						clientLogArtifacts.SourceBreakdown = result.SourceBreakdown;
					}
					catch
					{
					}
					if (dictionary2 != null && dictionary2.Count > 0 && dictionary.Count > 0)
					{
						try
						{
							clientLogArtifacts.ModDiff = ModListDiff.Compute(dictionary, dictionary2, peerPlayerName, text2, "FiresDiscordIntegration", clientLogArtifacts.CapturedUtc);
						}
						catch
						{
						}
					}
					ClientLogArtifactWriter.Write(clientLogsRoot, clientLogArtifacts);
				}
				lock (_debounceLock)
				{
					_peerToPlatformId[sender] = text2;
					_peerToPlayerName[sender] = peerPlayerName;
				}
				Debug.Log((object)$"[ClientLogRelay] ← Updated all cached artifacts for '{peerPlayerName}' ({text2}): {array2.Length} bytes.");
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[ClientLogRelay] Failed to process log update: " + ex.Message));
			}
		}

		[HarmonyPatch(typeof(ZNet), "Disconnect")]
		[HarmonyPrefix]
		public static void ZNet_Disconnect_Prefix(ZNetPeer peer)
		{
			try
			{
				if (!_isServerSide || peer == null)
				{
					return;
				}
				ClientLogChunkedTransfer.CancelTransfer(peer.m_uid);
				string value;
				string value2;
				lock (_debounceLock)
				{
					_peerToPlatformId.TryGetValue(peer.m_uid, out value);
					_peerToPlayerName.TryGetValue(peer.m_uid, out value2);
					_peerToPlatformId.Remove(peer.m_uid);
					_peerToPlayerName.Remove(peer.m_uid);
					_postedPeers.Remove(peer.m_uid);
				}
				if (string.IsNullOrEmpty(value))
				{
					value = peer.m_uid.ToString();
				}
				if (string.IsNullOrEmpty(value2))
				{
					value2 = (string.IsNullOrEmpty(peer.m_playerName) ? "unknown" : peer.m_playerName);
				}
				DisconnectLogRegistry.Entry entry = DisconnectLogRegistry.TakeIfRegistered(value);
				if (entry != null)
				{
					Debug.Log((object)("[ClientLogRelay] Player '" + value2 + "' (" + value + ") disconnected \ufffd posting session log (requested by " + entry.RequestedByName + ")."));
					if ((Object)(object)ZNet.instance != (Object)null)
					{
						((MonoBehaviour)ZNet.instance).StartCoroutine(PostDisconnectLogAsync(value, value2, entry.RequestedByName));
					}
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[ClientLogRelay] Disconnect hook threw: " + ex.Message));
			}
		}

		[IteratorStateMachine(typeof(<PostDisconnectLogAsync>d__52))]
		private static IEnumerator PostDisconnectLogAsync(string platformId, string playerName, string requestedByName)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <PostDisconnectLogAsync>d__52(0)
			{
				platformId = platformId,
				playerName = playerName,
				requestedByName = requestedByName
			};
		}

		private static void PostDisconnectLog(string platformId, string playerName, string requestedByName)
		{
			try
			{
				string clientLogsRoot = ClientLogsRoot;
				if (string.IsNullOrEmpty(clientLogsRoot))
				{
					return;
				}
				string text = playerName ?? "unknown";
				char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
				text = string.Concat(text.Split(invalidFileNameChars));
				string text2 = (platformId ?? "unknown").Replace(":", "_").Replace("/", "_").Replace("\\", "_");
				string text3 = Path.Combine(clientLogsRoot, text + "_" + text2, "LogOutput.log");
				if (!File.Exists(text3))
				{
					Debug.LogWarning((object)("[ClientLogRelay] No cached log at '" + text3 + "' for disconnect post."));
					return;
				}
				byte[] array = File.ReadAllBytes(text3);
				if (array.Length == 0)
				{
					return;
				}
				List<string> list = new List<string>(2);
				string[] array2 = new string[2] { PrimaryWebhookUrl, SecondaryWebhookUrl };
				foreach (string text4 in array2)
				{
					if (!string.IsNullOrEmpty(text4) && MinimalWebhookPoster.IsValidWebhookUrl(text4))
					{
						list.Add(text4);
					}
				}
				if (list.Count == 0)
				{
					return;
				}
				string text5 = DateTime.UtcNow.ToString("yyyyMMdd_HHmmss");
				MinimalWebhookPoster.Embed embed = new MinimalWebhookPoster.Embed().SetTitle("♻\ufe0f Session Log — Player Disconnected").SetColor(3066993).AddField("\ud83d\udc64 Player", playerName, inline: true)
					.AddField("\ud83d\udd94 Steam ID", platformId ?? "?", inline: true)
					.AddField("\ud83d\udcc4 Log Size", FormatBytes(array.Length), inline: true)
					.SetFooter("Requested by " + requestedByName);
				if (array.Length <= 8323072)
				{
					List<MinimalWebhookPoster.Attachment> list2 = new List<MinimalWebhookPoster.Attachment>();
					list2.Add(new MinimalWebhookPoster.Attachment("session_log_" + text2 + "_" + text5 + ".log", array));
					List<MinimalWebhookPoster.Attachment> files = list2;
					foreach (string item in list)
					{
						MinimalWebhookPoster.Post(item, embed, files, ResolveWebhookName());
					}
				}
				else
				{
					int num = 0;
					int num2 = 0;
					while (num2 < array.Length)
					{
						num++;
						int num3 = Math.Min(8323072, array.Length - num2);
						byte[] array3 = new byte[num3];
						Buffer.BlockCopy(array, num2, array3, 0, num3);
						num2 += num3;
						int num4 = (array.Length + 8323072 - 1) / 8323072;
						MinimalWebhookPoster.Embed embed2 = new MinimalWebhookPoster.Embed().SetTitle($"♻\ufe0f Session Log — Part {num}/{num4}").SetColor(3066993).AddField("\ud83d\udc64 Player", playerName, inline: true)
							.AddField("\ud83d\udcc4 Size", FormatBytes(array3.Length), inline: true)
							.SetFooter("Requested by " + requestedByName);
						List<MinimalWebhookPoster.Attachment> files2 = new List<MinimalWebhookPoster.Attachment>
						{
							new MinimalWebhookPoster.Attachment($"session_log_{text2}_{text5}_part{num}.log", array3)
						};
						foreach (string item2 in list)
						{
							MinimalWebhookPoster.Post(item2, embed2, files2, ResolveWebhookName());
						}
					}
				}
				Debug.Log((object)$"[ClientLogRelay] Posted disconnect session log for '{playerName}' ({platformId}), {array.Length} bytes.");
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[ClientLogRelay] Failed to post disconnect log: " + ex.Message));
			}
		}

		private static string FormatBytes(long bytes)
		{
			if (bytes <= 0)
			{
				return "0 B";
			}
			string[] array = new string[4] { "B", "KB", "MB", "GB" };
			double num = bytes;
			int num2 = 0;
			while (num >= 1024.0 && num2 < array.Length - 1)
			{
				num /= 1024.0;
				num2++;
			}
			return $"{num:0.##} {array[num2]}";
		}

		private static string GetPeerPlayerName(long uid)
		{
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				return "unknown";
			}
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				if (peer != null && peer.m_uid == uid)
				{
					return string.IsNullOrEmpty(peer.m_playerName) ? "unknown" : peer.m_playerName;
				}
			}
			return "unknown";
		}

		private static string GetOwnSteamId()
		{
			try
			{
				Type type = Type.GetType("Steamworks.SteamAPI, Steamworks.NET") ?? AccessTools.TypeByName("Steamworks.SteamAPI");
				Type type2 = Type.GetType("Steamworks.SteamUser, Steamworks.NET") ?? AccessTools.TypeByName("Steamworks.SteamUser");
				if (type == null || type2 == null)
				{
					return string.Empty;
				}
				MethodInfo method = type.GetMethod("IsSteamRunning", BindingFlags.Static | BindingFlags.Public);
				if (method == null)
				{
					return string.Empty;
				}
				if (!(method.Invoke(null, null) is bool flag) || !flag)
				{
					return string.Empty;
				}
				MethodInfo method2 = type2.GetMethod("GetSteamID", BindingFlags.Static | BindingFlags.Public);
				if (method2 == null)
				{
					return string.Empty;
				}
				object obj = method2.Invoke(null, null);
				if (obj == null)
				{
					return string.Empty;
				}
				FieldInfo field = obj.GetType().GetField("m_SteamID", BindingFlags.Instance | BindingFlags.Public);
				if (field != null)
				{
					object value = field.GetValue(obj);
					if (value != null)
					{
						return value.ToString();
					}
				}
				return obj.ToString();
			}
			catch
			{
			}
			return string.Empty;
		}
	}
	public static class ClientLogRelayPaths
	{
		public const string DefaultFolderName = "ClientLogs";

		public static string GetDefaultClientLogsDir(string modId)
		{
			if (string.IsNullOrEmpty(modId))
			{
				throw new ArgumentException("modId must be provided", "modId");
			}
			string text = Path.Combine(Paths.ConfigPath, modId, "ClientLogs");
			TryEnsureDirectory(text);
			return text;
		}

		public static string GetDefaultClientLogsDir(string modId, string subfolder)
		{
			string defaultClientLogsDir = GetDefaultClientLogsDir(modId);
			if (string.IsNullOrEmpty(subfolder))
			{
				return defaultClientLogsDir;
			}
			string text = Path.Combine(defaultClientLogsDir, subfolder);
			TryEnsureDirectory(text);
			return text;
		}

		private static void TryEnsureDirectory(string dir)
		{
			try
			{
				if (!Directory.Exists(dir))
				{
					Directory.CreateDirectory(dir);
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[ClientLogRelay] Failed to create directory '" + dir + "': " + ex.Message));
			}
		}
	}
	public interface IClientLogConsumer
	{
		string ConsumerId { get; }

		void OnClientArtifacts(ClientLogArtifacts artifacts);
	}
	public static class LogErrorWarningExtractor
	{
		public struct SourceIssueCount
		{
			public string SourceTag;

			public int ErrorCount;

			public int WarningCount;
		}

		public struct Result
		{
			public string Report;

			public int ErrorCount;

			public int WarningCount;

			public int BenignSkipped;

			public int DuplicatesCollapsed;

			public List<SourceIssueCount> SourceBreakdown;
		}

		private sealed class SourceGroup
		{
			public string Source;

			public int Errors;

			public int Warnings;

			public List<string> ErrorLines = new List<string>();

			public List<string> WarningLines = new List<string>();
		}

		public static readonly List<string> BenignPatterns = new List<string> { "Failed to find expected binary shader data", "The texture is not suitable to be used as a single mip level texture", "The AssetBundle", "audio clip could not be loaded" };

		public static Result Extract(byte[] logBytes, string pla