Decompiled source of NetKit v0.2.10

plugins/NetKit.Core.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("NetKit.Core")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.2.10.0")]
[assembly: AssemblyInformationalVersion("0.2.10+de8ede7c1cc4e525b56eb73ca1c0bdd9555d1cc3")]
[assembly: AssemblyProduct("NetKit.Core")]
[assembly: AssemblyTitle("NetKit.Core")]
[assembly: AssemblyMetadata("BuildStamp", "de8ede7c 2026-09-05")]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class IsReadOnlyAttribute : Attribute
	{
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace NetKit.Core
{
	public struct AnnounceEntry
	{
		public string WireKey;

		public string Payload;

		public string Extra;
	}
	public sealed class AnnounceBook
	{
		private sealed class Entry
		{
			public MirrorLatch Latch;

			public string WireKey = "";

			public string LastPayload = "";

			public string LastExtra = "";
		}

		private const char KeySep = '\u001f';

		private readonly double _refreshSeconds;

		private readonly Dictionary<string, Entry> _entries = new Dictionary<string, Entry>(StringComparer.Ordinal);

		public int Count => _entries.Count;

		public AnnounceBook(double refreshSeconds)
		{
			_refreshSeconds = refreshSeconds;
		}

		public MirrorDecision Decide(string? key, string? payload, string? extra, double now)
		{
			string text = RecordKey.Canonical(key);
			if (text.Length == 0)
			{
				return MirrorDecision.None;
			}
			return EntryOf(text, key).Latch.Decide((payload == null) ? null : ComposeKey(payload, extra), now);
		}

		public void OnLanded(string? key, string payload, string? extra, double now)
		{
			string text = RecordKey.Canonical(key);
			if (text.Length != 0)
			{
				Entry entry = EntryOf(text, key);
				entry.Latch.OnSent(ComposeKey(payload, extra), now);
				entry.LastPayload = payload ?? "";
				entry.LastExtra = extra ?? "";
			}
		}

		public bool Forget(string? key)
		{
			return _entries.Remove(RecordKey.Canonical(key));
		}

		public void Invalidate(string? key)
		{
			if (_entries.TryGetValue(RecordKey.Canonical(key), out Entry value))
			{
				value.Latch.Invalidate();
			}
		}

		public void InvalidateAll()
		{
			foreach (KeyValuePair<string, Entry> entry in _entries)
			{
				entry.Value.Latch.Invalidate();
			}
		}

		public void Reset()
		{
			_entries.Clear();
		}

		public List<AnnounceEntry> FlushSnapshot()
		{
			List<AnnounceEntry> list = new List<AnnounceEntry>();
			foreach (KeyValuePair<string, Entry> entry in _entries)
			{
				if (entry.Value.Latch.HasSent)
				{
					list.Add(new AnnounceEntry
					{
						WireKey = entry.Value.WireKey,
						Payload = entry.Value.LastPayload,
						Extra = entry.Value.LastExtra
					});
				}
			}
			return list;
		}

		public bool HasLanded(string? key)
		{
			if (_entries.TryGetValue(RecordKey.Canonical(key), out Entry value))
			{
				return value.Latch.HasSent;
			}
			return false;
		}

		private Entry EntryOf(string canonical, string rawKey)
		{
			if (!_entries.TryGetValue(canonical, out Entry value))
			{
				RecordKey.TryParse(rawKey, out string ownerUid, out int slot);
				Dictionary<string, Entry> entries = _entries;
				Entry obj = new Entry
				{
					Latch = new MirrorLatch(_refreshSeconds),
					WireKey = RecordKey.Wire(ownerUid, slot)
				};
				value = obj;
				entries[canonical] = obj;
			}
			return value;
		}

		private static string ComposeKey(string payload, string? extra)
		{
			return payload + "\u001f" + extra;
		}
	}
	public static class BuildStampCompat
	{
		public const string Unknown = "unknown";

		public static bool IsKnown(string stamp)
		{
			if (!string.IsNullOrEmpty(stamp))
			{
				return !string.Equals(stamp, "unknown", StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		public static bool ShouldWarn(string ours, string theirs)
		{
			if (IsKnown(ours) && IsKnown(theirs))
			{
				return !string.Equals(ours, theirs, StringComparison.Ordinal);
			}
			return false;
		}
	}
	public static class ChannelCompat
	{
		public enum Kind
		{
			Compatible,
			ProtoMismatch,
			VersionMismatch,
			PeerMissingChannel
		}

		public static bool ProtoCompatible(int localProto, int remoteProto)
		{
			return localProto == remoteProto;
		}

		public static Kind Evaluate(int localProto, int remoteProto, string localVersion, string remoteVersion)
		{
			if (localProto != remoteProto)
			{
				return Kind.ProtoMismatch;
			}
			if (remoteVersion == null)
			{
				return Kind.PeerMissingChannel;
			}
			if (!string.Equals(localVersion, remoteVersion, StringComparison.Ordinal))
			{
				return Kind.VersionMismatch;
			}
			return Kind.Compatible;
		}

		public static bool IsReady(Kind kind)
		{
			return kind == Kind.Compatible;
		}
	}
	public static class EventFanout
	{
		public static void Raise<T>(Action<T> ev, T arg, Action<Delegate, Exception> onError)
		{
			if (ev == null)
			{
				return;
			}
			Delegate[] invocationList = ev.GetInvocationList();
			foreach (Delegate obj in invocationList)
			{
				try
				{
					((Action<T>)obj)(arg);
				}
				catch (Exception arg2)
				{
					onError?.Invoke(obj, arg2);
				}
			}
		}

		public static void ForEach<T>(IEnumerable<T> items, Action<T> body, Action<T, Exception> onError)
		{
			if (items == null || body == null)
			{
				return;
			}
			foreach (T item in items)
			{
				try
				{
					body(item);
				}
				catch (Exception arg)
				{
					onError?.Invoke(item, arg);
				}
			}
		}
	}
	public sealed class PendingOnce<T> where T : class
	{
		private readonly List<T> _items = new List<T>();

		public int Count => _items.Count;

		public bool Add(T item)
		{
			if (item == null || _items.Contains(item))
			{
				return false;
			}
			_items.Add(item);
			return true;
		}

		public List<T> Drain()
		{
			if (_items.Count == 0)
			{
				return null;
			}
			List<T> result = new List<T>(_items);
			_items.Clear();
			return result;
		}
	}
	public static class ExtensionChange
	{
		public static bool ShouldRaiseChanged(bool wasReady, bool hadPrevious, string previousExtension, string newExtension)
		{
			if (!wasReady)
			{
				return false;
			}
			if (!hadPrevious)
			{
				return false;
			}
			return !string.Equals(previousExtension ?? "", newExtension ?? "", StringComparison.Ordinal);
		}
	}
	public enum FireAndForgetAction
	{
		Skip,
		Drop,
		Apply,
		ApplyAndRelay
	}
	public readonly struct FireAndForgetDecision
	{
		public FireAndForgetAction Action { get; }

		public string? Reason { get; }

		public static FireAndForgetDecision Skip => new FireAndForgetDecision(FireAndForgetAction.Skip, null);

		public static FireAndForgetDecision Apply => new FireAndForgetDecision(FireAndForgetAction.Apply, null);

		public static FireAndForgetDecision ApplyAndRelay => new FireAndForgetDecision(FireAndForgetAction.ApplyAndRelay, null);

		private FireAndForgetDecision(FireAndForgetAction action, string? reason)
		{
			Action = action;
			Reason = reason;
		}

		public static FireAndForgetDecision Drop(string reason)
		{
			return new FireAndForgetDecision(FireAndForgetAction.Drop, reason);
		}

		public override string ToString()
		{
			if (Reason != null)
			{
				return Action.ToString() + "(" + Reason + ")";
			}
			return Action.ToString();
		}
	}
	public static class FireAndForgetLadder
	{
		public const string EmptyIdentity = "empty-identity";

		public const string DisabledOnMaster = "disabled-on-master";

		public static FireAndForgetDecision OnFire(bool enabled, bool uidEmpty, bool inRoom)
		{
			if (!enabled || uidEmpty)
			{
				return FireAndForgetDecision.Skip;
			}
			if (!inRoom)
			{
				return FireAndForgetDecision.Apply;
			}
			return FireAndForgetDecision.ApplyAndRelay;
		}

		public static FireAndForgetDecision OnCastReceived(bool enabled, bool senderIsMaster, bool senderIsSelf, bool uidEmpty, bool isLocalOwner)
		{
			return OnCastReceived(enabled, senderIsMaster, senderIsSelf, uidEmpty, isLocalOwner, masterOriginated: false);
		}

		public static FireAndForgetDecision OnCastReceived(bool enabled, bool senderIsMaster, bool senderIsSelf, bool uidEmpty, bool isLocalOwner, bool masterOriginated)
		{
			string text = RoleGuard.Evaluate(HandlerRole.SenderMustBeMaster | HandlerRole.SenderMustNotBeSelf, localIsMaster: false, senderIsMaster, senderIsSelf);
			if (text != null)
			{
				return FireAndForgetDecision.Drop(text);
			}
			if (!enabled)
			{
				return FireAndForgetDecision.Skip;
			}
			if (uidEmpty)
			{
				return FireAndForgetDecision.Drop("empty-identity");
			}
			if (isLocalOwner && !masterOriginated)
			{
				return FireAndForgetDecision.Skip;
			}
			return FireAndForgetDecision.Apply;
		}

		public static FireAndForgetDecision OnProxyReceived(bool enabled, bool localIsMaster, bool uidEmpty, string? authorizeReason)
		{
			string text = RoleGuard.Evaluate(HandlerRole.RunOnMasterOnly, localIsMaster, senderIsMaster: false, senderIsSelf: false);
			if (text != null)
			{
				return FireAndForgetDecision.Drop(text);
			}
			if (!enabled)
			{
				return FireAndForgetDecision.Drop("disabled-on-master");
			}
			if (uidEmpty)
			{
				return FireAndForgetDecision.Drop("empty-identity");
			}
			if (authorizeReason != null)
			{
				return FireAndForgetDecision.Drop(authorizeReason);
			}
			return FireAndForgetDecision.ApplyAndRelay;
		}
	}
	public static class Heartbeat
	{
		public static string Line(string tag, bool isMaster, int peers, int ready, int tx, int rx, int drops, string fragment, double photonTime)
		{
			string text = string.Format("[{0}] hb role={1} peers={2} ready={3}/{4}", tag, isMaster ? "M" : "G", peers, ready, peers) + $" pt={photonTime:F1} tx={tx} rx={rx} drops={drops}";
			if (!string.IsNullOrEmpty(fragment))
			{
				text = text + " " + fragment;
			}
			return text;
		}
	}
	public static class HelloCodec
	{
		public struct HelloMsg
		{
			public int Proto;

			public List<ChannelHello> Channels;

			public string Stamp;

			public bool SceneReady;
		}

		public struct ChannelHello
		{
			public string Channel;

			public string Version;

			public string Extension;
		}

		public const int Version = 1;

		public static string Encode(HelloMsg m)
		{
			List<ChannelHello> list = m.Channels ?? new List<ChannelHello>();
			List<string> list2 = new List<string>(2 + list.Count * 3)
			{
				NetWire.I(m.Proto),
				NetWire.I(list.Count)
			};
			foreach (ChannelHello item in list)
			{
				list2.Add(NetWire.Esc(item.Channel));
				list2.Add(NetWire.Esc(item.Version));
				list2.Add(NetWire.Esc(item.Extension));
			}
			list2.Add(NetWire.Esc(m.Stamp ?? ""));
			list2.Add(m.SceneReady ? "1" : "0");
			return NetWire.Join(list2.ToArray());
		}

		public static bool TryDecode(string payload, out HelloMsg m)
		{
			m = default(HelloMsg);
			m.Channels = new List<ChannelHello>();
			m.Stamp = "";
			List<string> list = NetWire.Split(payload);
			if (list == null || list.Count < 2)
			{
				return false;
			}
			if (!NetWire.TryI(list[0], out m.Proto) || m.Proto <= 0)
			{
				return false;
			}
			if (!NetWire.TryI(list[1], out var v) || v < 0)
			{
				return false;
			}
			if (list.Count < 2 + v * 3)
			{
				return false;
			}
			for (int i = 0; i < v; i++)
			{
				int num = 2 + i * 3;
				string text = list[num];
				if (string.IsNullOrEmpty(text))
				{
					return false;
				}
				m.Channels.Add(new ChannelHello
				{
					Channel = text,
					Version = list[num + 1],
					Extension = list[num + 2]
				});
			}
			int num2 = 2 + v * 3;
			if (list.Count > num2)
			{
				m.Stamp = list[num2];
			}
			m.SceneReady = true;
			if (list.Count > num2 + 1)
			{
				m.SceneReady = list[num2 + 1] != "0";
			}
			return true;
		}

		public static string? VersionOf(in HelloMsg m, string channel)
		{
			if (m.Channels == null)
			{
				return null;
			}
			foreach (ChannelHello channel2 in m.Channels)
			{
				if (string.Equals(channel2.Channel, channel, StringComparison.Ordinal))
				{
					return channel2.Version;
				}
			}
			return null;
		}

		public static string? ExtensionOf(in HelloMsg m, string channel)
		{
			if (m.Channels == null)
			{
				return null;
			}
			foreach (ChannelHello channel2 in m.Channels)
			{
				if (string.Equals(channel2.Channel, channel, StringComparison.Ordinal))
				{
					return channel2.Extension;
				}
			}
			return null;
		}
	}
	public sealed class HelloResend
	{
		private readonly double _intervalSeconds;

		private string? _broadcast;

		private readonly Dictionary<int, string> _targeted = new Dictionary<int, string>();

		private double _nextCheck;

		public bool HasBaseline
		{
			get
			{
				if (_broadcast == null)
				{
					return _targeted.Count > 0;
				}
				return true;
			}
		}

		public int TrackedActorCount => _targeted.Count;

		public HelloResend(double intervalSeconds)
		{
			_intervalSeconds = intervalSeconds;
		}

		public void RecordSent(string payload)
		{
			_broadcast = payload ?? "";
			_targeted.Clear();
		}

		public void RecordSentTo(int actor, string payload)
		{
			_targeted[actor] = payload ?? "";
		}

		public void ForgetActor(int actor)
		{
			_targeted.Remove(actor);
		}

		public bool DueForCheck(double now)
		{
			if (_intervalSeconds <= 0.0)
			{
				return false;
			}
			if (!HasBaseline)
			{
				return false;
			}
			if (now < _nextCheck)
			{
				return false;
			}
			_nextCheck = now + _intervalSeconds;
			return true;
		}

		public bool PayloadChanged(string currentPayload)
		{
			string b = currentPayload ?? "";
			if (_broadcast != null && !string.Equals(_broadcast, b, StringComparison.Ordinal))
			{
				return true;
			}
			foreach (KeyValuePair<int, string> item in _targeted)
			{
				if (!string.Equals(item.Value, b, StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		public bool ShouldResend(double now, string currentPayload)
		{
			if (DueForCheck(now))
			{
				return PayloadChanged(currentPayload);
			}
			return false;
		}

		public void Reset()
		{
			_broadcast = null;
			_targeted.Clear();
			_nextCheck = 0.0;
		}
	}
	public enum MirrorDecision
	{
		None,
		SendChanged,
		SendPeriodic,
		Clear
	}
	public sealed class MirrorLatch
	{
		private readonly double _resendSeconds;

		private readonly bool _clearOnNull;

		private string _lastKey;

		private bool _invalidated;

		private double _resendAt;

		public bool HasSent => _lastKey != null;

		public string LastKey => _lastKey;

		public MirrorLatch(double resendSeconds = 0.0, bool clearOnNull = false)
		{
			_resendSeconds = resendSeconds;
			_clearOnNull = clearOnNull;
		}

		public MirrorDecision Decide(string key, double now)
		{
			if (key == null)
			{
				if (!_clearOnNull || !HasSent)
				{
					return MirrorDecision.None;
				}
				return MirrorDecision.Clear;
			}
			if (_invalidated || _lastKey == null || !string.Equals(key, _lastKey, StringComparison.Ordinal))
			{
				return MirrorDecision.SendChanged;
			}
			if (_resendSeconds > 0.0 && now >= _resendAt)
			{
				return MirrorDecision.SendPeriodic;
			}
			return MirrorDecision.None;
		}

		public void OnSent(string key, double now)
		{
			_lastKey = key;
			_invalidated = false;
			_resendAt = now + _resendSeconds;
		}

		public void OnCleared()
		{
			_lastKey = null;
			_invalidated = false;
		}

		public void Invalidate()
		{
			_invalidated = true;
		}

		public void Reset()
		{
			_lastKey = null;
			_invalidated = false;
		}
	}
	public sealed class NetCounters
	{
		private sealed class VerbTrace
		{
			public int Sends;

			public int Recvs;

			public int Drops;

			public double LastSendAt = -1.0;

			public double LastRecvAt = -1.0;

			public double LastDropAt = -1.0;

			public readonly Dictionary<string, int> DropReasons = new Dictionary<string, int>();
		}

		private readonly Dictionary<string, VerbTrace> _trace = new Dictionary<string, VerbTrace>();

		private readonly int _verbCap;

		private readonly int _reasonCap;

		private int _overflowEvents;

		public const int DefaultVerbCap = 64;

		public const int DefaultReasonCap = 32;

		public bool Empty
		{
			get
			{
				if (_trace.Count == 0)
				{
					return _overflowEvents == 0;
				}
				return false;
			}
		}

		public int OverflowEvents => _overflowEvents;

		public int TotalSends
		{
			get
			{
				int num = 0;
				foreach (VerbTrace value in _trace.Values)
				{
					num += value.Sends;
				}
				return num;
			}
		}

		public int TotalRecvs
		{
			get
			{
				int num = 0;
				foreach (VerbTrace value in _trace.Values)
				{
					num += value.Recvs;
				}
				return num;
			}
		}

		public int TotalDrops
		{
			get
			{
				int num = 0;
				foreach (VerbTrace value in _trace.Values)
				{
					num += value.Drops;
				}
				return num;
			}
		}

		public NetCounters()
			: this(64)
		{
		}

		public NetCounters(int verbCap, int reasonCap = 32)
		{
			_verbCap = ((verbCap < 1) ? 1 : verbCap);
			_reasonCap = ((reasonCap < 1) ? 1 : reasonCap);
		}

		private VerbTrace For(string verb)
		{
			verb = verb ?? "?";
			if (_trace.TryGetValue(verb, out VerbTrace value))
			{
				return value;
			}
			if (_trace.Count >= _verbCap)
			{
				_overflowEvents++;
				return null;
			}
			return _trace[verb] = new VerbTrace();
		}

		public void RecordSend(string verb, double now)
		{
			VerbTrace verbTrace = For(verb);
			if (verbTrace != null)
			{
				verbTrace.Sends++;
				verbTrace.LastSendAt = now;
			}
		}

		public void RecordRecv(string verb, double now)
		{
			VerbTrace verbTrace = For(verb);
			if (verbTrace != null)
			{
				verbTrace.Recvs++;
				verbTrace.LastRecvAt = now;
			}
		}

		public void RecordDrop(string verb, string reason, double now)
		{
			VerbTrace verbTrace = For(verb);
			if (verbTrace != null)
			{
				verbTrace.Drops++;
				verbTrace.LastDropAt = now;
				reason = reason ?? "?";
				if (verbTrace.DropReasons.TryGetValue(reason, out var value))
				{
					verbTrace.DropReasons[reason] = value + 1;
				}
				else if (verbTrace.DropReasons.Count >= _reasonCap)
				{
					_overflowEvents++;
				}
				else
				{
					verbTrace.DropReasons[reason] = 1;
				}
			}
		}

		public string Summary(string tag, double now)
		{
			if (_trace.Count == 0)
			{
				if (_overflowEvents != 0)
				{
					return $"[{tag}] no traffic recorded ({_overflowEvents} event(s) refused by the counter caps).";
				}
				return "[" + tag + "] no traffic recorded.";
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append($"[{tag}] traffic (t={now:F0}s):");
			List<string> list = new List<string>(_trace.Keys);
			list.Sort(StringComparer.Ordinal);
			foreach (string item in list)
			{
				VerbTrace verbTrace = _trace[item];
				string arg = "";
				if (verbTrace.Drops > 0)
				{
					List<string> list2 = new List<string>();
					foreach (KeyValuePair<string, int> dropReason in verbTrace.DropReasons)
					{
						list2.Add($"{dropReason.Key}×{dropReason.Value}");
					}
					list2.Sort(StringComparer.Ordinal);
					arg = string.Format(" drop={0} ({1}) lastDrop={2:F0}s", verbTrace.Drops, string.Join(", ", list2.ToArray()), verbTrace.LastDropAt);
				}
				stringBuilder.Append(string.Format("\n[{0}]   {1}: send={2}{3}", tag, item, verbTrace.Sends, (verbTrace.Sends > 0) ? $" lastSend={verbTrace.LastSendAt:F0}s" : "") + string.Format(" recv={0}{1}{2}", verbTrace.Recvs, (verbTrace.Recvs > 0) ? $" lastRecv={verbTrace.LastRecvAt:F0}s" : "", arg));
			}
			if (_overflowEvents > 0)
			{
				stringBuilder.Append($"\n[{tag}]   (+{_overflowEvents} event(s) refused by the counter caps: " + $"verbCap={_verbCap} reasonCap={_reasonCap} — a channel legitimately has a handful " + "of constant verbs, so hitting this means something is inventing them.)");
			}
			return stringBuilder.ToString();
		}
	}
	internal static class NetWire
	{
		public static string I(int v)
		{
			return v.ToString(CultureInfo.InvariantCulture);
		}

		public static string F(float v)
		{
			return v.ToString("R", CultureInfo.InvariantCulture);
		}

		public static bool TryI(string s, out int v)
		{
			return int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out v);
		}

		public static bool TryF(string s, out float v)
		{
			return float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out v);
		}

		public static string Join(params string[] fields)
		{
			return string.Join(";", fields);
		}

		public static string Esc(string s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder(s.Length + 4);
			foreach (char c in s)
			{
				switch (c)
				{
				case '\\':
					stringBuilder.Append("\\\\");
					break;
				case ';':
					stringBuilder.Append("\\s");
					break;
				case '\n':
					stringBuilder.Append("\\n");
					break;
				default:
					stringBuilder.Append(c);
					break;
				}
			}
			return stringBuilder.ToString();
		}

		public static List<string>? Split(string payload)
		{
			if (payload == null)
			{
				return null;
			}
			List<string> list = new List<string>();
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < payload.Length; i++)
			{
				char c = payload[i];
				switch (c)
				{
				case '\\':
					if (i + 1 >= payload.Length)
					{
						return null;
					}
					switch (payload[++i])
					{
					case '\\':
						stringBuilder.Append('\\');
						break;
					case 's':
						stringBuilder.Append(';');
						break;
					case 'n':
						stringBuilder.Append('\n');
						break;
					default:
						return null;
					}
					break;
				case ';':
					list.Add(stringBuilder.ToString());
					stringBuilder.Length = 0;
					break;
				default:
					stringBuilder.Append(c);
					break;
				}
			}
			list.Add(stringBuilder.ToString());
			return list;
		}
	}
	public sealed class NoHandlerDrops
	{
		public readonly struct Entry
		{
			public string Channel { get; }

			public string Verb { get; }

			public int Count { get; }

			public Entry(string channel, string verb, int count)
			{
				Channel = channel;
				Verb = verb;
				Count = count;
			}
		}

		private readonly Dictionary<string, Dictionary<string, int>> _byChannel = new Dictionary<string, Dictionary<string, int>>(StringComparer.Ordinal);

		public bool Empty
		{
			get
			{
				foreach (Dictionary<string, int> value in _byChannel.Values)
				{
					if (value.Count > 0)
					{
						return false;
					}
				}
				return true;
			}
		}

		public bool Record(string channel, string verb)
		{
			channel = channel ?? "?";
			verb = verb ?? "?";
			if (!_byChannel.TryGetValue(channel, out Dictionary<string, int> value))
			{
				value = (_byChannel[channel] = new Dictionary<string, int>(StringComparer.Ordinal));
			}
			bool result = !value.ContainsKey(verb);
			value.TryGetValue(verb, out var value2);
			value[verb] = value2 + 1;
			return result;
		}

		public int Count(string channel, string verb)
		{
			if (channel != null && verb != null && _byChannel.TryGetValue(channel, out Dictionary<string, int> value) && value.TryGetValue(verb, out var value2))
			{
				return value2;
			}
			return 0;
		}

		public IEnumerable<Entry> NonZero()
		{
			List<string> list = new List<string>(_byChannel.Keys);
			list.Sort(StringComparer.Ordinal);
			foreach (string c in list)
			{
				List<string> list2 = new List<string>(_byChannel[c].Keys);
				list2.Sort(StringComparer.Ordinal);
				foreach (string item in list2)
				{
					yield return new Entry(c, item, _byChannel[c][item]);
				}
			}
		}

		public string Summary(string tag)
		{
			List<string> list = new List<string>();
			foreach (Entry item in NonZero())
			{
				list.Add($"{item.Channel}.{item.Verb}×{item.Count}");
			}
			if (list.Count != 0)
			{
				return "[" + tag + "] droppedVerbs: " + string.Join(", ", list.ToArray());
			}
			return "[" + tag + "] droppedVerbs: none";
		}
	}
	public static class PayloadSizeGuard
	{
		public const int PunPracticalLimitBytes = 32717;

		public const int WarnThresholdBytes = 26173;

		public static int Utf8ByteSize(string payload)
		{
			if (!string.IsNullOrEmpty(payload))
			{
				return Encoding.UTF8.GetByteCount(payload);
			}
			return 0;
		}

		public static bool ShouldWarn(int utf8ByteSize)
		{
			return utf8ByteSize >= 26173;
		}
	}
	public sealed class PayloadSizeWarnLedger
	{
		private readonly HashSet<string> _warned = new HashSet<string>(StringComparer.Ordinal);

		public static string Key(string channelId, string verb)
		{
			return channelId + "|" + verb;
		}

		public bool ShouldWarn(string channelId, string verb)
		{
			return _warned.Add(Key(channelId, verb));
		}

		public void Clear()
		{
			_warned.Clear();
		}
	}
	public sealed class PeerHelloStore
	{
		public struct Entry
		{
			public int Actor;

			public HelloCodec.HelloMsg Hello;

			public bool IsMaster;
		}

		public struct ChannelReadiness
		{
			public int Actor;

			public bool Ready;

			public ChannelCompat.Kind Kind;

			public string Version;

			public string Extension;

			public bool IsMaster;
		}

		private readonly Dictionary<int, Entry> _entries = new Dictionary<int, Entry>();

		public int Count => _entries.Count;

		public IEnumerable<Entry> Entries => _entries.Values;

		public void Record(int actor, HelloCodec.HelloMsg hello, bool isMaster)
		{
			_entries[actor] = new Entry
			{
				Actor = actor,
				Hello = hello,
				IsMaster = isMaster
			};
		}

		public bool TryGet(int actor, out Entry entry)
		{
			return _entries.TryGetValue(actor, out entry);
		}

		public void Remove(int actor)
		{
			_entries.Remove(actor);
		}

		public void Clear()
		{
			_entries.Clear();
		}

		public List<ChannelReadiness> EvaluateChannel(int localProto, string channelId, string localVersion)
		{
			List<ChannelReadiness> list = new List<ChannelReadiness>(_entries.Count);
			foreach (Entry value in _entries.Values)
			{
				Entry current = value;
				string text = HelloCodec.VersionOf(in current.Hello, channelId);
				ChannelCompat.Kind kind = ChannelCompat.Evaluate(localProto, current.Hello.Proto, localVersion, text);
				list.Add(new ChannelReadiness
				{
					Actor = current.Actor,
					Ready = ChannelCompat.IsReady(kind),
					Kind = kind,
					Version = (text ?? ""),
					Extension = (HelloCodec.ExtensionOf(in current.Hello, channelId) ?? ""),
					IsMaster = current.IsMaster
				});
			}
			return list;
		}
	}
	public sealed class PeerLedger
	{
		private readonly Dictionary<int, double> _expectedSince = new Dictionary<int, double>();

		private readonly HashSet<int> _helloed = new HashSet<int>();

		public int PendingCount => _expectedSince.Count;

		public int HelloedCount => _helloed.Count;

		public IReadOnlyDictionary<int, double> Pending => _expectedSince;

		public bool HasHello(int actor)
		{
			return _helloed.Contains(actor);
		}

		public bool Expect(int actor, double now)
		{
			if (_helloed.Contains(actor))
			{
				return false;
			}
			if (_expectedSince.ContainsKey(actor))
			{
				return false;
			}
			_expectedSince[actor] = now;
			return true;
		}

		public void MarkHello(int actor)
		{
			_expectedSince.Remove(actor);
			_helloed.Add(actor);
		}

		public List<int>? DueForWarning(double now, double warnSeconds)
		{
			List<int> list = null;
			if (_expectedSince.Count == 0)
			{
				return null;
			}
			foreach (KeyValuePair<int, double> item in _expectedSince)
			{
				if (now - item.Value > warnSeconds)
				{
					(list ?? (list = new List<int>())).Add(item.Key);
				}
			}
			if (list != null)
			{
				foreach (int item2 in list)
				{
					_expectedSince.Remove(item2);
				}
			}
			return list;
		}

		public double GraceElapsed(int actor, double now)
		{
			if (!_expectedSince.TryGetValue(actor, out var value))
			{
				return -1.0;
			}
			return now - value;
		}

		public void Reset()
		{
			_expectedSince.Clear();
			_helloed.Clear();
		}
	}
	public static class PunLog
	{
		public static readonly string[] Signatures = new string[3] { "Received OnSerialization for view ID", "has no method", "PhotonView ID duplicate found" };

		public static int Match(string condition)
		{
			if (string.IsNullOrEmpty(condition))
			{
				return -1;
			}
			for (int i = 0; i < Signatures.Length; i++)
			{
				if (condition.IndexOf(Signatures[i], StringComparison.Ordinal) >= 0)
				{
					return i;
				}
			}
			return -1;
		}

		public static bool TryParseViewId(string condition, out int id)
		{
			id = 0;
			if (string.IsNullOrEmpty(condition))
			{
				return false;
			}
			int num = condition.IndexOf(Signatures[0], StringComparison.Ordinal);
			if (num < 0)
			{
				return false;
			}
			int i;
			for (i = num + Signatures[0].Length; i < condition.Length && !char.IsDigit(condition[i]); i++)
			{
			}
			int j;
			for (j = i; j < condition.Length && char.IsDigit(condition[j]); j++)
			{
			}
			if (j <= i)
			{
				return false;
			}
			return int.TryParse(condition.Substring(i, j - i), out id);
		}
	}
	public sealed class PunSignatureCounters
	{
		private readonly int[] _counts = new int[PunLog.Signatures.Length];

		private readonly double[] _firstAt;

		private readonly double[] _lastAt;

		public int UnknownViewCount => _counts[0];

		public PunSignatureCounters()
		{
			_firstAt = new double[PunLog.Signatures.Length];
			_lastAt = new double[PunLog.Signatures.Length];
			for (int i = 0; i < _firstAt.Length; i++)
			{
				_firstAt[i] = -1.0;
				_lastAt[i] = -1.0;
			}
		}

		public void Record(int signatureIndex, double now)
		{
			if (signatureIndex >= 0 && signatureIndex < _counts.Length)
			{
				_counts[signatureIndex]++;
				if (_firstAt[signatureIndex] < 0.0)
				{
					_firstAt[signatureIndex] = now;
				}
				_lastAt[signatureIndex] = now;
			}
		}

		public int Count(int signatureIndex)
		{
			if (signatureIndex < 0 || signatureIndex >= _counts.Length)
			{
				return 0;
			}
			return _counts[signatureIndex];
		}

		public string Summary(string tag)
		{
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < PunLog.Signatures.Length; i++)
			{
				if (_counts[i] > 0)
				{
					stringBuilder.Append($"\n[{tag}] PUN-signature '{PunLog.Signatures[i]}': {_counts[i]}× " + $"(first {_firstAt[i]:F0}s, last {_lastAt[i]:F0}s)");
				}
			}
			if (stringBuilder.Length != 0)
			{
				return stringBuilder.ToString(1, stringBuilder.Length - 1);
			}
			return "";
		}
	}
	public sealed class UnknownViewTable
	{
		private sealed class Row
		{
			public int Count;

			public double FirstAt;

			public double LastAt;

			public bool RunawayAnnounced;
		}

		private readonly Dictionary<int, Row> _rows = new Dictionary<int, Row>();

		private readonly int _cap;

		private readonly int _runawayThreshold;

		public const int DefaultRunawayThreshold = 500;

		public int IdCount => _rows.Count;

		public UnknownViewTable(int cap = 64, int runawayThreshold = 500)
		{
			_cap = ((cap < 1) ? 1 : cap);
			_runawayThreshold = ((runawayThreshold < 1) ? 1 : runawayThreshold);
		}

		public bool Record(int id, double now)
		{
			bool runaway;
			return Record(id, now, out runaway);
		}

		public bool Record(int id, double now, out bool runaway)
		{
			runaway = false;
			if (_rows.TryGetValue(id, out Row value))
			{
				value.Count++;
				value.LastAt = now;
				if (!value.RunawayAnnounced && value.Count >= _runawayThreshold)
				{
					value.RunawayAnnounced = true;
					runaway = true;
				}
				return false;
			}
			if (_rows.Count >= _cap)
			{
				return false;
			}
			_rows[id] = new Row
			{
				Count = 1,
				FirstAt = now,
				LastAt = now
			};
			return true;
		}

		public int CountOf(int id)
		{
			if (!_rows.TryGetValue(id, out Row value))
			{
				return 0;
			}
			return value.Count;
		}

		public string TopSummary(int max = 4)
		{
			if (_rows.Count == 0 || max < 1)
			{
				return "";
			}
			List<KeyValuePair<int, Row>> list = new List<KeyValuePair<int, Row>>(_rows);
			list.Sort((KeyValuePair<int, Row> a, KeyValuePair<int, Row> b) => b.Value.Count.CompareTo(a.Value.Count));
			StringBuilder stringBuilder = new StringBuilder();
			int num = ((list.Count < max) ? list.Count : max);
			for (int num2 = 0; num2 < num; num2++)
			{
				if (num2 > 0)
				{
					stringBuilder.Append(' ');
				}
				stringBuilder.Append(list[num2].Key).Append('×').Append(list[num2].Value.Count);
			}
			return stringBuilder.ToString();
		}

		public string Format(string tag)
		{
			if (_rows.Count == 0)
			{
				return "[" + tag + "] unknown-view table: empty.";
			}
			StringBuilder stringBuilder = new StringBuilder($"[{tag}] unknown-view table ({_rows.Count} id(s)):");
			foreach (KeyValuePair<int, Row> row in _rows)
			{
				stringBuilder.Append($"\n[{tag}]   id={row.Key}: {row.Value.Count}× (first {row.Value.FirstAt:F0}s, last {row.Value.LastAt:F0}s)");
			}
			return stringBuilder.ToString();
		}
	}
	public sealed class QuietVerbSet
	{
		public static readonly QuietVerbSet Empty = new QuietVerbSet(null);

		private readonly HashSet<string> _verbs;

		public int Count
		{
			get
			{
				if (_verbs == null)
				{
					return 0;
				}
				return _verbs.Count;
			}
		}

		public QuietVerbSet(IEnumerable<string> verbs)
		{
			if (verbs == null)
			{
				return;
			}
			foreach (string verb in verbs)
			{
				if (!string.IsNullOrEmpty(verb))
				{
					(_verbs ?? (_verbs = new HashSet<string>(StringComparer.Ordinal))).Add(verb);
				}
			}
		}

		public bool IsQuiet(string verb)
		{
			if (_verbs != null && verb != null)
			{
				return _verbs.Contains(verb);
			}
			return false;
		}
	}
	public static class RecordKey
	{
		public const char Separator = ':';

		private const char ExtraSep = '\u001f';

		public static bool TryParse(string? wireKey, out string ownerUid, out int slot)
		{
			ownerUid = wireKey ?? "";
			slot = 0;
			if (string.IsNullOrEmpty(wireKey))
			{
				return false;
			}
			int num = wireKey.LastIndexOf(':');
			if (num > 0 && num < wireKey.Length - 1 && int.TryParse(wireKey.Substring(num + 1), NumberStyles.None, CultureInfo.InvariantCulture, out var result))
			{
				ownerUid = wireKey.Substring(0, num);
				slot = result;
			}
			return ownerUid.Length > 0;
		}

		public static string Canonical(string? wireKey)
		{
			if (!TryParse(wireKey, out string ownerUid, out int slot))
			{
				return "";
			}
			return Compose(ownerUid, slot);
		}

		public static string Compose(string ownerUid, int slot)
		{
			return ownerUid + ":" + ((slot >= 0) ? slot : 0).ToString(CultureInfo.InvariantCulture);
		}

		public static string Wire(string ownerUid, int slot)
		{
			if (slot > 0)
			{
				return ownerUid + ":" + slot.ToString(CultureInfo.InvariantCulture);
			}
			return ownerUid;
		}

		public static string EncodeExtraSlot(string wireKey, string? extra)
		{
			if (!string.IsNullOrEmpty(extra))
			{
				return wireKey + "\u001f" + extra;
			}
			return wireKey;
		}

		public static void DecodeExtraSlot(string? slotValue, out string wireKey, out string extra)
		{
			string text = slotValue ?? "";
			int num = text.IndexOf('\u001f');
			if (num < 0)
			{
				wireKey = text;
				extra = "";
			}
			else
			{
				wireKey = text.Substring(0, num);
				extra = text.Substring(num + 1);
			}
		}
	}
	public enum OwnerResolution
	{
		Unknown,
		OwnedBySender,
		NotOwnedBySender
	}
	public enum RebindRule
	{
		OnlyIfPrevActorGone
	}
	public enum RecordApply
	{
		RefusedEmptyKey,
		DeferredOwnerUnknown,
		RefusedNotOwner,
		RefusedRebindLive,
		Created,
		Updated,
		Refreshed,
		Rebound
	}
	public static class StoreReleaseWire
	{
		public const string OwnerReleasePrefix = "owner release: ";

		public const string DefaultReason = "released by owner";
	}
	public enum RecordRelease
	{
		NoRow,
		RefusedWrongSender,
		Cleared
	}
	public enum RecordTickStep
	{
		Keep,
		ClearOwnerMissing
	}
	public sealed class RecordRow
	{
		public string Key { get; internal set; } = "";

		public string OwnerUid { get; internal set; } = "";

		public int Slot { get; internal set; }

		public int ActorId { get; internal set; }

		public string Payload { get; internal set; } = "";

		public string Extra { get; internal set; } = "";

		public double LastSetAt { get; internal set; }

		public double OwnerMissingSince { get; internal set; }
	}
	public sealed class RecordTable
	{
		private readonly Dictionary<string, RecordRow> _rows = new Dictionary<string, RecordRow>(StringComparer.Ordinal);

		public int Count => _rows.Count;

		public bool TryGet(string? wireKey, out RecordRow row)
		{
			return _rows.TryGetValue(RecordKey.Canonical(wireKey), out row);
		}

		public List<RecordRow> RowsSnapshot()
		{
			List<RecordRow> list = new List<RecordRow>(_rows.Count);
			foreach (KeyValuePair<string, RecordRow> row in _rows)
			{
				list.Add(row.Value);
			}
			return list;
		}

		public RecordApply Apply(string? wireKey, string? payload, string? extra, int senderActor, double now, Func<int, bool>? actorPresent, Func<string, int, int, OwnerResolution>? resolveOwner, out RecordRow? row)
		{
			row = null;
			if (!RecordKey.TryParse(wireKey, out string ownerUid, out int slot))
			{
				return RecordApply.RefusedEmptyKey;
			}
			string key = RecordKey.Compose(ownerUid, slot);
			payload = payload ?? "";
			extra = extra ?? "";
			if (!_rows.TryGetValue(key, out RecordRow value))
			{
				switch (Resolve(resolveOwner, ownerUid, slot, senderActor))
				{
				case OwnerResolution.Unknown:
					return RecordApply.DeferredOwnerUnknown;
				case OwnerResolution.NotOwnedBySender:
					return RecordApply.RefusedNotOwner;
				default:
					row = new RecordRow
					{
						Key = key,
						OwnerUid = ownerUid,
						Slot = slot,
						ActorId = senderActor
					};
					Stamp(row, payload, extra, now);
					_rows[key] = row;
					return RecordApply.Created;
				}
			}
			if (value.ActorId != senderActor)
			{
				if (actorPresent == null || actorPresent(value.ActorId))
				{
					return RecordApply.RefusedRebindLive;
				}
				switch (Resolve(resolveOwner, ownerUid, slot, senderActor))
				{
				case OwnerResolution.Unknown:
					return RecordApply.DeferredOwnerUnknown;
				case OwnerResolution.NotOwnedBySender:
					return RecordApply.RefusedNotOwner;
				default:
					value.ActorId = senderActor;
					value.OwnerMissingSince = 0.0;
					Stamp(value, payload, extra, now);
					row = value;
					return RecordApply.Rebound;
				}
			}
			bool num = !string.Equals(value.Payload, payload, StringComparison.Ordinal) || !string.Equals(value.Extra, extra, StringComparison.Ordinal);
			Stamp(value, payload, extra, now);
			row = value;
			if (!num)
			{
				return RecordApply.Refreshed;
			}
			return RecordApply.Updated;
		}

		private static OwnerResolution Resolve(Func<string, int, int, OwnerResolution>? resolveOwner, string uid, int slot, int senderActor)
		{
			return resolveOwner?.Invoke(uid, slot, senderActor) ?? OwnerResolution.OwnedBySender;
		}

		private static void Stamp(RecordRow row, string payload, string extra, double now)
		{
			row.Payload = payload;
			row.Extra = extra;
			row.LastSetAt = now;
		}

		public RecordRelease Release(string? wireKey, int senderActor, out RecordRow? row)
		{
			row = null;
			string key = RecordKey.Canonical(wireKey);
			if (!_rows.TryGetValue(key, out RecordRow value))
			{
				return RecordRelease.NoRow;
			}
			if (value.ActorId != senderActor)
			{
				return RecordRelease.RefusedWrongSender;
			}
			_rows.Remove(key);
			row = value;
			return RecordRelease.Cleared;
		}

		public bool Clear(string? wireKey, out RecordRow? row)
		{
			string key = RecordKey.Canonical(wireKey);
			if (!_rows.TryGetValue(key, out RecordRow value))
			{
				row = null;
				return false;
			}
			_rows.Remove(key);
			row = value;
			return true;
		}

		public List<RecordRow> ClearActor(int actorId)
		{
			List<RecordRow> list = new List<RecordRow>();
			foreach (KeyValuePair<string, RecordRow> row in _rows)
			{
				if (row.Value.ActorId == actorId)
				{
					list.Add(row.Value);
				}
			}
			foreach (RecordRow item in list)
			{
				_rows.Remove(item.Key);
			}
			return list;
		}

		public List<RecordRow> ClearAll()
		{
			List<RecordRow> result = RowsSnapshot();
			_rows.Clear();
			return result;
		}

		public RecordTickStep TickRow(RecordRow row, bool ownerResolvable, double now, double ownerMissingSeconds)
		{
			if (row == null)
			{
				return RecordTickStep.Keep;
			}
			if (ownerResolvable)
			{
				row.OwnerMissingSince = 0.0;
				return RecordTickStep.Keep;
			}
			if (row.OwnerMissingSince <= 0.0)
			{
				row.OwnerMissingSince = now;
				return RecordTickStep.Keep;
			}
			if (!(now - row.OwnerMissingSince > ownerMissingSeconds))
			{
				return RecordTickStep.Keep;
			}
			return RecordTickStep.ClearOwnerMissing;
		}
	}
	public sealed class RequestBook
	{
		public struct Expired
		{
			public string Verb;

			public string Token;
		}

		private sealed class Row
		{
			public string Verb;

			public double Deadline;
		}

		public const string RefusedInFlight = "in-flight";

		private readonly Dictionary<string, Row> _byToken = new Dictionary<string, Row>(StringComparer.Ordinal);

		private int _counter;

		public int PendingCount => _byToken.Count;

		public string Begin(string verb, int actor, double now, double timeoutSeconds, bool singleFlight, out string refusal)
		{
			refusal = null;
			if (singleFlight && HasPending(verb))
			{
				refusal = "in-flight";
				return null;
			}
			string text = actor.ToString(CultureInfo.InvariantCulture) + "-" + (++_counter).ToString(CultureInfo.InvariantCulture);
			_byToken[text] = new Row
			{
				Verb = verb,
				Deadline = now + timeoutSeconds
			};
			return text;
		}

		public void Abort(string token)
		{
			if (token != null)
			{
				_byToken.Remove(token);
			}
		}

		public bool TryResolve(string token, out string verb)
		{
			verb = null;
			if (token == null || !_byToken.TryGetValue(token, out Row value))
			{
				return false;
			}
			verb = value.Verb;
			_byToken.Remove(token);
			return true;
		}

		public List<Expired> TakeExpired(double now)
		{
			List<Expired> list = null;
			List<string> list2 = null;
			foreach (KeyValuePair<string, Row> item in _byToken)
			{
				if (!(item.Value.Deadline > now))
				{
					(list ?? (list = new List<Expired>())).Add(new Expired
					{
						Verb = item.Value.Verb,
						Token = item.Key
					});
					(list2 ?? (list2 = new List<string>())).Add(item.Key);
				}
			}
			if (list2 != null)
			{
				foreach (string item2 in list2)
				{
					_byToken.Remove(item2);
				}
			}
			return list;
		}

		public List<Expired> TakeAll()
		{
			if (_byToken.Count == 0)
			{
				return null;
			}
			List<Expired> list = new List<Expired>(_byToken.Count);
			foreach (KeyValuePair<string, Row> item in _byToken)
			{
				list.Add(new Expired
				{
					Verb = item.Value.Verb,
					Token = item.Key
				});
			}
			_byToken.Clear();
			return list;
		}

		public bool HasPending(string verb)
		{
			foreach (KeyValuePair<string, Row> item in _byToken)
			{
				if (string.Equals(item.Value.Verb, verb, StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		public bool TryGetPending(string verb, out string token, out double deadline)
		{
			foreach (KeyValuePair<string, Row> item in _byToken)
			{
				if (string.Equals(item.Value.Verb, verb, StringComparison.Ordinal))
				{
					token = item.Key;
					deadline = item.Value.Deadline;
					return true;
				}
			}
			token = null;
			deadline = 0.0;
			return false;
		}
	}
	public static class RequestCodec
	{
		public static string Sanitize(string s)
		{
			if (!string.IsNullOrEmpty(s))
			{
				return s.Replace('\t', ' ');
			}
			return "";
		}

		public static string EncodeRequest(string token, string payload)
		{
			if (!string.IsNullOrEmpty(payload))
			{
				return Sanitize(token) + "\t" + payload;
			}
			return Sanitize(token);
		}

		public static bool TryParseRequest(string wire, out string token, out string payload)
		{
			token = "";
			payload = "";
			if (string.IsNullOrEmpty(wire))
			{
				return false;
			}
			int num = wire.IndexOf('\t');
			if (num < 0)
			{
				token = wire;
				return true;
			}
			if (num == 0)
			{
				return false;
			}
			token = wire.Substring(0, num);
			payload = wire.Substring(num + 1);
			return true;
		}

		public static string EncodeAck(string token, string result)
		{
			return Sanitize(token) + "\t" + Sanitize(result);
		}

		public static bool TryParseAck(string wire, out string token, out string result)
		{
			token = "";
			result = "";
			if (string.IsNullOrEmpty(wire))
			{
				return false;
			}
			int num = wire.IndexOf('\t');
			if (num <= 0 || num == wire.Length - 1)
			{
				return false;
			}
			string text = wire.Substring(num + 1);
			if (text.IndexOf('\t') >= 0)
			{
				return false;
			}
			token = wire.Substring(0, num);
			result = text;
			return true;
		}
	}
	[Flags]
	public enum HandlerRole
	{
		Any = 0,
		RunOnMasterOnly = 1,
		RunOnGuestOnly = 2,
		SenderMustBeMaster = 4,
		SenderMustNotBeSelf = 8
	}
	public static class RoleGuard
	{
		public const string NotMasterMachine = "role:not-master-machine";

		public const string NotGuestMachine = "role:not-guest-machine";

		public const string SenderNotMaster = "role:sender-not-master";

		public const string SenderIsSelf = "role:sender-is-self";

		public static string? Evaluate(HandlerRole role, bool localIsMaster, bool senderIsMaster, bool senderIsSelf)
		{
			if ((role & HandlerRole.RunOnMasterOnly) != HandlerRole.Any && !localIsMaster)
			{
				return "role:not-master-machine";
			}
			if ((role & HandlerRole.RunOnGuestOnly) != 0 && localIsMaster)
			{
				return "role:not-guest-machine";
			}
			if ((role & HandlerRole.SenderMustBeMaster) != HandlerRole.Any && !senderIsMaster)
			{
				return "role:sender-not-master";
			}
			if ((role & HandlerRole.SenderMustNotBeSelf) != 0 && senderIsSelf)
			{
				return "role:sender-is-self";
			}
			return null;
		}
	}
	public struct RoomChange
	{
		public string? OldRoomName;

		public string? NewRoomName;
	}
	public sealed class RoomWatch
	{
		private bool _inRoom;

		private string? _room;

		public bool Update(bool inRoom, string? roomName, out RoomChange change)
		{
			string text = (inRoom ? (roomName ?? "") : null);
			string text2 = (_inRoom ? (_room ?? "") : null);
			if (string.Equals(text, text2, StringComparison.Ordinal))
			{
				change = default(RoomChange);
				return false;
			}
			change = new RoomChange
			{
				OldRoomName = text2,
				NewRoomName = text
			};
			_inRoom = inRoom;
			_room = roomName;
			return true;
		}
	}
	public sealed class SceneReadyGate
	{
		private string? _firedScene;

		public bool Fire(bool inRoom, bool ready, string? scene)
		{
			if (!inRoom)
			{
				_firedScene = null;
				return false;
			}
			if (!ready || string.IsNullOrEmpty(scene))
			{
				return false;
			}
			if (string.Equals(scene, _firedScene, StringComparison.Ordinal))
			{
				return false;
			}
			_firedScene = scene;
			return true;
		}

		public void Reset()
		{
			_firedScene = null;
		}
	}
	public static class OutOfRoomReset
	{
		public static bool ShouldReset(int pendingCount, int helloedCount, int peerCount, int channelReadyCount)
		{
			if (pendingCount <= 0 && helloedCount <= 0 && peerCount <= 0)
			{
				return channelReadyCount > 0;
			}
			return true;
		}
	}
	public struct SimEnvelope
	{
		public string Channel;

		public string Verb;

		public int Seq;

		public string Extra;

		public string Payload;
	}
	public sealed class SimLink
	{
		private struct Entry
		{
			public SimEnvelope Env;

			public double Due;

			public long Order;
		}

		private readonly List<Entry> _queue = new List<Entry>();

		private readonly Random _rng;

		private readonly int _delayMs;

		private readonly int _jitterMs;

		private readonly int _dropPct;

		private readonly bool _ordered;

		private double _lastDue;

		private long _order;

		public int Enqueued { get; private set; }

		public int Dropped { get; private set; }

		public int Delivered { get; private set; }

		public int PendingCount => _queue.Count;

		public SimLink(int delayMs, int jitterMs, int dropPct, bool ordered, int seed)
		{
			_delayMs = ((delayMs >= 0) ? delayMs : 0);
			_jitterMs = ((jitterMs >= 0) ? jitterMs : 0);
			_dropPct = ((dropPct >= 0) ? ((dropPct > 100) ? 100 : dropPct) : 0);
			_ordered = ordered;
			_rng = new Random(seed);
		}

		public bool Enqueue(SimEnvelope env, double now)
		{
			if (_dropPct > 0 && _rng.Next(100) < _dropPct)
			{
				Dropped++;
				return false;
			}
			double num = now + (double)(_delayMs + ((_jitterMs > 0) ? _rng.Next(_jitterMs + 1) : 0)) / 1000.0;
			if (_ordered && num < _lastDue)
			{
				num = _lastDue;
			}
			_lastDue = (_ordered ? num : Math.Max(_lastDue, num));
			_queue.Add(new Entry
			{
				Env = env,
				Due = num,
				Order = _order++
			});
			Enqueued++;
			return true;
		}

		public List<SimEnvelope> DrainDue(double now)
		{
			List<Entry> list = null;
			for (int num = _queue.Count - 1; num >= 0; num--)
			{
				if (!(_queue[num].Due > now))
				{
					List<Entry> obj = list ?? new List<Entry>();
					list = obj;
					obj.Add(_queue[num]);
					_queue.RemoveAt(num);
				}
			}
			if (list == null)
			{
				return null;
			}
			list.Sort((Entry a, Entry b) => (a.Due == b.Due) ? a.Order.CompareTo(b.Order) : a.Due.CompareTo(b.Due));
			List<SimEnvelope> list2 = new List<SimEnvelope>(list.Count);
			foreach (Entry item in list)
			{
				list2.Add(item.Env);
			}
			Delivered += list2.Count;
			return list2;
		}

		public void Clear()
		{
			_queue.Clear();
			_lastDue = 0.0;
		}

		public string Describe()
		{
			return $"delay={_delayMs}ms jitter={_jitterMs}ms drop={_dropPct}% " + string.Format("{0} queued={1} dropped={2} ", _ordered ? "ordered" : "unordered", Enqueued, Dropped) + $"delivered={Delivered} pending={_queue.Count}";
		}
	}
	public enum StoreAuthorityKind
	{
		Master,
		Owner,
		PeerOwned
	}
	public static class StoreRoles
	{
		public static HandlerRole For(StoreAuthorityKind kind)
		{
			return kind switch
			{
				StoreAuthorityKind.Master => HandlerRole.RunOnMasterOnly, 
				StoreAuthorityKind.PeerOwned => HandlerRole.SenderMustNotBeSelf, 
				_ => HandlerRole.SenderMustBeMaster | HandlerRole.SenderMustNotBeSelf, 
			};
		}

		public static bool KeyFromSender(StoreAuthorityKind kind)
		{
			return kind == StoreAuthorityKind.PeerOwned;
		}

		public static bool BroadcastsToOthers(StoreAuthorityKind kind)
		{
			if (kind != StoreAuthorityKind.Owner)
			{
				return kind == StoreAuthorityKind.PeerOwned;
			}
			return true;
		}

		public static bool OwnerBindingApplies(StoreAuthorityKind kind)
		{
			return kind == StoreAuthorityKind.Master;
		}
	}
	public static class TimeoutConfig
	{
		public const int Vanilla = 10000;

		public static int Resolve(int configured, out bool negativeWasInvalid)
		{
			negativeWasInvalid = configured < 0;
			if (configured <= 0)
			{
				return 0;
			}
			return configured;
		}

		public static bool Armed(int configured)
		{
			bool negativeWasInvalid;
			return Resolve(configured, out negativeWasInvalid) > 0;
		}
	}
	public sealed class TraceRing
	{
		public struct Entry
		{
			public double T;

			public string Line;
		}

		private readonly Entry[] _ring;

		private int _next;

		public int Capacity => _ring.Length;

		public TraceRing(int capacity = 32)
		{
			if (capacity < 1)
			{
				capacity = 1;
			}
			_ring = new Entry[capacity];
		}

		public void Add(double now, string line)
		{
			_ring[_next] = new Entry
			{
				T = now,
				Line = line
			};
			_next = (_next + 1) % _ring.Length;
		}

		public IEnumerable<Entry> Ordered()
		{
			for (int i = 0; i < _ring.Length; i++)
			{
				Entry entry = _ring[(_next + i) % _ring.Length];
				if (entry.Line != null)
				{
					yield return entry;
				}
			}
		}
	}
	public struct ViewFacts
	{
		public int Views;

		public int NonZeroIds;

		public int AlreadyAwake;

		public int Deregistered;
	}
	public struct SurvivorFacts
	{
		public int Views;

		public int NetControls;

		public int Disarmed;

		public int Queued;

		public bool PinnedByCharacter;
	}
	public static class ViewHygiene
	{
		public const string RefusalNote = "DestroyImmediate was REFUSED, not attempted-and-failed: Unity silently refuses it inside animation events, physics trigger/contact callbacks, StateMachineBehaviour callbacks and render callbacks — it logs an error, returns, and never throws, so no try/catch can see it.";

		public static string Describe(in ViewFacts f)
		{
			return $"views={f.Views} ids={f.NonZeroIds} awake={f.AlreadyAwake} deregistered={f.Deregistered}";
		}

		public static string DescribeSurvivors(in SurvivorFacts f)
		{
			return $"pv={f.Views} ncc={f.NetControls} disarmed={f.Disarmed} queued={f.Queued}";
		}

		public static bool IsClean(in SurvivorFacts f)
		{
			if (f.Views == 0)
			{
				return f.NetControls == 0;
			}
			return false;
		}
	}
	public enum LeaseVerdict
	{
		Keep,
		ReleaseNow,
		ReleaseAgedOut,
		NotePersist,
		WarnLeak
	}
	public struct MuteResult
	{
		public bool Muted;

		public int ViewId;

		public string Error;
	}
	public static class LeasePolicy
	{
		public const float MaxParkSeconds = 300f;

		public const int MintWarnWatermark = 800;

		public static bool ShouldWarnMintCount(int outstanding)
		{
			return outstanding >= 800;
		}

		public static LeaseVerdict Verdict(bool bodyGone, float ageSeconds, bool viewLostId, bool warned, bool mayPersist)
		{
			if (bodyGone)
			{
				return LeaseVerdict.ReleaseNow;
			}
			if (ageSeconds < 300f)
			{
				return LeaseVerdict.Keep;
			}
			if (viewLostId)
			{
				return LeaseVerdict.ReleaseAgedOut;
			}
			if (warned)
			{
				return LeaseVerdict.Keep;
			}
			if (!mayPersist)
			{
				return LeaseVerdict.WarnLeak;
			}
			return LeaseVerdict.NotePersist;
		}
	}
	public sealed class LeaseEntry<TBody> where TBody : class
	{
		public int ViewId;

		public TBody Body;

		public float QueuedAt;

		public bool Warned;

		public bool MayPersist;
	}
	public sealed class ViewLedger<TBody> where TBody : class
	{
		private readonly List<LeaseEntry<TBody>> _entries = new List<LeaseEntry<TBody>>();

		public int Count => _entries.Count;

		public IReadOnlyList<LeaseEntry<TBody>> Entries => _entries;

		public LeaseEntry<TBody> Park(int viewId, TBody body, float now, bool mayPersist)
		{
			LeaseEntry<TBody> leaseEntry = new LeaseEntry<TBody>
			{
				ViewId = viewId,
				Body = body,
				QueuedAt = now,
				MayPersist = mayPersist
			};
			_entries.Add(leaseEntry);
			return leaseEntry;
		}

		public bool Remove(LeaseEntry<TBody> entry)
		{
			return _entries.Remove(entry);
		}

		public int DropAll()
		{
			int count = _entries.Count;
			_entries.Clear();
			return count;
		}

		public string DescribePending(string tag, float now, Func<TBody, string> bodyName)
		{
			if (_entries.Count == 0)
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder($"{tag} {_entries.Count} viewID release(s) parked:");
			foreach (LeaseEntry<TBody> entry in _entries)
			{
				string text = bodyName?.Invoke(entry.Body);
				stringBuilder.Append(string.Format("\n  viewID={0} body='{1}' ", entry.ViewId, text ?? "<destroyed>") + $"age={now - entry.QueuedAt:0}s" + (entry.Warned ? (entry.MayPersist ? " PARKED(vanilla corpse — reserved until scene unload)" : " AGED-OUT(leak-warned)") : ""));
			}
			return stringBuilder.ToString();
		}
	}
	public sealed class WarnOnceSet
	{
		private readonly HashSet<string> _warned = new HashSet<string>(StringComparer.Ordinal);

		public int Count => _warned.Count;

		public static string Key(int actor, string channelId)
		{
			return actor + "|" + channelId;
		}

		public bool ShouldWarn(int actor, string channelId)
		{
			return _warned.Add(Key(actor, channelId));
		}

		public void Rearm(int actor, string channelId)
		{
			_warned.Remove(Key(actor, channelId));
		}

		public void Clear()
		{
			_warned.Clear();
		}
	}
}

plugins/NetKit.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using ForgeKit;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using NetKit.Core;
using Photon;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("NetKit")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.2.10.0")]
[assembly: AssemblyInformationalVersion("0.2.10+de8ede7c1cc4e525b56eb73ca1c0bdd9555d1cc3")]
[assembly: AssemblyProduct("NetKit")]
[assembly: AssemblyTitle("NetKit")]
[assembly: AssemblyMetadata("BuildStamp", "de8ede7c 2026-09-05")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace NetKit
{
	internal static class Diagnostics
	{
		private static readonly PunSignatureCounters _signatures = new PunSignatureCounters();

		private static readonly UnknownViewTable _unknownViews = new UnknownViewTable(64, 500);

		private static bool _logHooked;

		private static float _nextHeartbeat;

		internal static readonly NoHandlerDrops NoHandler = new NoHandlerDrops();

		internal static int UnknownViewWarnCount => _signatures.UnknownViewCount;

		internal static void Init()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			if (!_logHooked)
			{
				_logHooked = true;
				Application.logMessageReceived += new LogCallback(OnUnityLog);
			}
		}

		private static void OnUnityLog(string condition, string stackTrace, LogType type)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Invalid comparison between Unknown and I4
			if ((int)type != 2 && (int)type != 0 && (int)type != 1)
			{
				return;
			}
			int num = PunLog.Match(condition);
			if (num < 0)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			_signatures.Record(num, (double)unscaledTime);
			int num2 = default(int);
			if (num == 0 && PunLog.TryParseViewId(condition, ref num2))
			{
				bool flag = default(bool);
				if (_unknownViews.Record(num2, (double)unscaledTime, ref flag))
				{
					Plugin.Log.LogMessage((object)($"[NETKIT] unknown-view NEW id={num2} — something is streaming at a view this " + "machine doesn't hold (netdump shows the per-id table)."));
				}
				else if (flag)
				{
					Plugin.Log.LogWarning((object)($"[NETKIT] unknown-view RUNAWAY id={num2}: {_unknownViews.CountOf(num2)} warns and " + "climbing — a peer is continuously streaming at a view this machine doesn't hold (orphaned/evicted view; low ids 1-999 are scene-baked — donor-harvest eviction class). netdump shows the per-id table; a relaunch of the streaming peer clears it."));
				}
			}
		}

		internal static void HeartbeatTick()
		{
			if (!PhotonNetwork.inRoom)
			{
				return;
			}
			float num = ((Plugin.HeartbeatSeconds != null) ? Plugin.HeartbeatSeconds.Value : 0f);
			if (num <= 0f || Time.unscaledTime < _nextHeartbeat)
			{
				return;
			}
			_nextHeartbeat = Time.unscaledTime + num;
			int num2 = 0;
			try
			{
				num2 = ((PhotonNetwork.otherPlayers != null) ? PhotonNetwork.otherPlayers.Length : 0);
			}
			catch
			{
			}
			double time = PhotonNetwork.time;
			bool isMasterClient = PhotonNetwork.isMasterClient;
			foreach (NetChannel channel in Net.Channels)
			{
				if (!(channel.Id == "nk"))
				{
					Plugin.Log.LogMessage((object)Heartbeat.Line(channel.LogTag, isMasterClient, num2, channel.ReadyCount, channel.Counters.TotalSends, channel.Counters.TotalRecvs, channel.Counters.TotalDrops, channel.HeartbeatFragmentValue(), time));
				}
			}
			if (UnknownViewWarnCount > 0)
			{
				string text = _unknownViews.TopSummary(4);
				Plugin.Log.LogMessage((object)($"[NETKIT] hb unknownViewWarns={UnknownViewWarnCount} pt={time:F1}" + ((text.Length > 0) ? (" top=[" + text + "]") : "")));
			}
		}

		internal static string Dump()
		{
			//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0203: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02de: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0342: Unknown result type (might be due to invalid IL or missing references)
			//IL_0344: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0355: Unknown result type (might be due to invalid IL or missing references)
			//IL_0357: Unknown result type (might be due to invalid IL or missing references)
			//IL_036f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0371: Unknown result type (might be due to invalid IL or missing references)
			//IL_0303: Unknown result type (might be due to invalid IL or missing references)
			//IL_0308: Unknown result type (might be due to invalid IL or missing references)
			//IL_030c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0318: Unknown result type (might be due to invalid IL or missing references)
			//IL_039e: Unknown result type (might be due to invalid IL or missing references)
			//IL_03aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c3: Unknown result type (might be due to invalid IL or missing references)
			StringBuilder stringBuilder = new StringBuilder();
			INetTransport transport = Net.Transport;
			stringBuilder.AppendLine($"[NETKIT] transport={transport.Name} attached={transport.Attached} inRoom={PhotonNetwork.inRoom} " + $"isMaster={PhotonNetwork.isMasterClient} isGuest={PhotonNetwork.isNonMasterClientInRoom} " + $"pt={PhotonNetwork.time:F1} seqTx={Net.NextSeqPreview} helloMuted={HelloService.HelloMuted}");
			stringBuilder.AppendLine("[NETKIT] " + transport.AttachInfo());
			stringBuilder.AppendLine("[NETKIT] local build " + Net.LocalBuildStamp);
			stringBuilder.AppendLine(LoaderLine());
			stringBuilder.AppendLine($"[NETKIT] {Net.ChannelCount} channel(s) registered.");
			foreach (NetChannel channel in Net.Channels)
			{
				List<string> list = new List<string>(channel.Verbs);
				list.Sort(StringComparer.Ordinal);
				stringBuilder.AppendLine(string.Format("[{0}] channel '{1}' v{2} — {3} verb(s): {4}", channel.LogTag, channel.Id, channel.Version, list.Count, string.Join(", ", list.ToArray())));
				stringBuilder.AppendLine($"[{channel.LogTag}] peers ready: {channel.ReadyCount}");
				stringBuilder.AppendLine(channel.Counters.Summary(channel.LogTag, (double)Time.unscaledTime));
				bool flag = false;
				foreach (Entry item in channel.Trace.Ordered())
				{
					if (!flag)
					{
						stringBuilder.AppendLine("[" + channel.LogTag + "] last events (newest last):");
						flag = true;
					}
					stringBuilder.AppendLine($"[{channel.LogTag}]   {item.T:F0}s {item.Line}");
				}
				if (channel.Stores == null)
				{
					continue;
				}
				foreach (ReplicatedStore store in channel.Stores)
				{
					stringBuilder.AppendLine(store.Dump());
				}
			}
			stringBuilder.AppendLine(NoHandler.Summary("NETKIT"));
			if (HelloService.Peers.Count == 0)
			{
				stringBuilder.AppendLine("[NETKIT] no peer hellos received.");
			}
			foreach (Entry entry in HelloService.Peers.Entries)
			{
				List<string> list2 = new List<string>();
				if (entry.Hello.Channels != null)
				{
					foreach (ChannelHello channel2 in entry.Hello.Channels)
					{
						list2.Add(channel2.Channel + "=" + channel2.Version);
					}
				}
				string text = (string.IsNullOrEmpty(entry.Hello.Stamp) ? "(none)" : entry.Hello.Stamp);
				string text2 = (BuildStampCompat.ShouldWarn(Net.LocalBuildStamp, entry.Hello.Stamp) ? " ⚠DIFFERS" : "");
				stringBuilder.AppendLine($"[NETKIT] peer actor {entry.Actor}: proto v{entry.Hello.Proto} " + ((PhotonPlayer.Find(entry.Actor) != null) ? "(in room)" : "(GONE)") + " build " + text + text2 + " — " + string.Join(", ", list2.ToArray()));
			}
			foreach (KeyValuePair<int, double> item2 in HelloService.Ledger.Pending)
			{
				stringBuilder.AppendLine($"[NETKIT] awaiting hello from actor {item2.Key} " + $"({HelloService.Ledger.GraceElapsed(item2.Key, (double)Time.unscaledTime):F0}s grace elapsed).");
			}
			string value = _signatures.Summary("NETKIT");
			if (!string.IsNullOrEmpty(value))
			{
				stringBuilder.AppendLine(value);
			}
			if (_unknownViews.IdCount > 0)
			{
				stringBuilder.AppendLine(_unknownViews.Format("NETKIT"));
			}
			return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
		}

		private static string LoaderLine()
		{
			NetworkLevelLoader instance = NetworkLevelLoader.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return "[NETKIT] loader: n/a (NetworkLevelLoader.Instance null — main menu?)";
			}
			try
			{
				return $"[NETKIT] loader: gameplayPaused={instance.IsGameplayPaused} " + $"allPlayersDone={instance.AllPlayerDoneLoading} overallDone={instance.IsOverallLoadingDone} " + $"pausedBy=[{PausedByKeys(instance)}] otherPaused={instance.m_otherPlayerPaused} " + $"msgQueue={PhotonNetwork.isMessageQueueRunning} timeScale={Time.timeScale:F2}";
			}
			catch (Exception ex)
			{
				return "[NETKIT] loader: n/a (read threw: " + ex.GetType().Name + ")";
			}
		}

		private static string PausedByKeys(NetworkLevelLoader nll)
		{
			try
			{
				List<string> list = ((nll.m_gameplayPausedBy != null) ? nll.m_gameplayPausedBy.Keys : null);
				if (list == null || list.Count == 0)
				{
					return "";
				}
				return string.Join(",", list.ToArray());
			}
			catch (Exception ex)
			{
				return "threw:" + ex.GetType().Name;
			}
		}
	}
	internal sealed class EventTransport : INetTransport
	{
		private readonly byte _code;

		private bool _subscribed;

		public string Name => "Event";

		public bool Attached => _subscribed;

		public EventTransport(int eventCode)
		{
			if (eventCode < 0 || eventCode > 199)
			{
				Plugin.Log.LogWarning((object)($"[NETKIT] Event transport code {eventCode} is outside Photon's 0..199 " + "custom-event range — clamped to 177. Fix [Net] EventCode so both boxes agree."));
				eventCode = 177;
			}
			_code = (byte)eventCode;
		}

		public bool TryAttach()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected O, but got Unknown
			if (_subscribed)
			{
				return true;
			}
			try
			{
				PhotonNetwork.OnEventCall += new EventCallback(OnEvent);
				_subscribed = true;
				Plugin.Log.LogMessage((object)$"[NETKIT] Event transport subscribed (code {_code}).");
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[NETKIT] Event subscribe threw: " + ex.Message));
				return false;
			}
		}

		public bool Send(SendTarget target, PhotonPlayer specific, string channel, string verb, int seq, string extra, string payload)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			if (!TryAttach())
			{
				return false;
			}
			try
			{
				object[] array = new object[5] { channel, verb, seq, extra, payload };
				RaiseEventOptions val = new RaiseEventOptions();
				if (target == SendTarget.Player)
				{
					if (specific == null)
					{
						return false;
					}
					val.TargetActors = new int[1] { specific.ID };
				}
				else
				{
					val.Receivers = ToReceiverGroup(target);
				}
				return PhotonNetwork.RaiseEvent(_code, (object)array, true, val);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)$"[NETKIT] Event send '{channel}.{verb}' seq={seq} failed: {ex.Message}");
				return false;
			}
		}

		public bool SendLoopback(string channel, string verb, int seq, string extra, string payload)
		{
			try
			{
				int senderId = ((PhotonNetwork.player != null) ? PhotonNetwork.player.ID : (-1));
				Net.DeliverEvent(channel, verb, seq, extra, payload, senderId);
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[NETKIT] Event loopback '" + channel + "." + verb + "' failed: " + ex.Message));
				return false;
			}
		}

		public string AttachInfo()
		{
			return $"event-code={_code} subscribed={_subscribed}";
		}

		private void OnEvent(byte eventCode, object content, int senderId)
		{
			if (eventCode != _code || !(content is object[] array) || array.Length < 5)
			{
				return;
			}
			try
			{
				string channel = array[0] as string;
				string verb = array[1] as string;
				int seq = ((array[2] is int num) ? num : 0);
				string extra = (array[3] as string) ?? "";
				string payload = (array[4] as string) ?? "";
				Net.DeliverEvent(channel, verb, seq, extra, payload, senderId);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[NETKIT] Event receive threw: " + ex.Message));
			}
		}

		private static ReceiverGroup ToReceiverGroup(SendTarget t)
		{
			return (ReceiverGroup)(t switch
			{
				SendTarget.Master => 2, 
				SendTarget.All => 1, 
				_ => 0, 
			});
		}
	}
	internal static class HelloService
	{
		private static readonly PeerHelloStore _peers = new PeerHelloStore();

		private static readonly PeerLedger _ledger = new PeerLedger();

		private static readonly HashSet<int> _buildWarned = new HashSet<int>();

		private static readonly WarnOnceSet _incompatWarned = new WarnOnceSet();

		private static readonly HashSet<int> _malformedWarned = new HashSet<int>();

		private static readonly RoomWatch _roomWatch = new RoomWatch();

		private static readonly SceneReadyGate _sceneGate = new SceneReadyGate();

		private static HelloResend _resend = new HelloResend(5.0);

		private static float _warnSeconds = 10f;

		internal static bool HelloMuted;

		internal static PeerHelloStore Peers => _peers;

		internal static PeerLedger Ledger => _ledger;

		internal static void Configure(float warnSeconds, float refreshSeconds)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			_warnSeconds = ((warnSeconds > 0f) ? warnSeconds : 10f);
			_resend = new HelloResend((double)refreshSeconds);
		}

		internal static bool LocalSceneReady()
		{
			Character val = default(Character);
			if ((Object)(object)NetworkLevelLoader.Instance != (Object)null && NetworkLevelLoader.Instance.IsOverallLoadingDone)
			{
				return Lifecycle.TryGetFirstLocalCharacter(ref val);
			}
			return false;
		}

		internal static void OnHello(NetMessage msg)
		{
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			_ledger.MarkHello(msg.SenderActor);
			HelloMsg val = default(HelloMsg);
			if (!HelloCodec.TryDecode(msg.Payload, ref val))
			{
				Net.TryGetChannel("nk", out var ch);
				ch?.CountDrop("nk.hello", "unparseable");
				if (_malformedWarned.Add(msg.SenderActor))
				{
					Plugin.Log.LogWarning((object)($"[NETKIT] malformed hello from actor {msg.SenderActor}: '{msg.Payload}'. " + "The peer IS modded but its hello is unreadable — every co-op feature will REFUSE toward it (no channel becomes ready, and the appears-UNMODDED warning stands down because the peer provably messages). Mismatched builds are the usual cause. (warned once per peer per room; further malformed hellos counted silently)"));
				}
			}
			else
			{
				Entry val2 = default(Entry);
				bool hadPrev = _peers.TryGet(msg.SenderActor, ref val2);
				_peers.Record(msg.SenderActor, val, msg.SenderIsMaster);
				WarnOnBuildMismatch(msg.SenderActor, val.Stamp);
				ApplyReadiness(msg.SenderActor, val2.Hello, hadPrev, val, msg.SenderIsMaster);
			}
		}

		private static void WarnOnBuildMismatch(int actor, string theirStamp)
		{
			if (!_buildWarned.Contains(actor))
			{
				string localBuildStamp = Net.LocalBuildStamp;
				if (BuildStampCompat.ShouldWarn(localBuildStamp, theirStamp))
				{
					_buildWarned.Add(actor);
					Plugin.Log.LogWarning((object)($"[NETKIT] peer actor {actor} build differs — ours {localBuildStamp}, " + "theirs " + theirStamp + " (stale install?)"));
				}
			}
		}

		private static void ApplyReadiness(int actor, HelloMsg prev, bool hadPrev, HelloMsg hello, bool isMaster)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			List<string> newlyIncompatible = new List<string>();
			EventFanout.ForEach<NetChannel>((IEnumerable<NetChannel>)Net.Channels, (Action<NetChannel>)delegate(NetChannel ch)
			{
				//IL_0053: Unknown result type (might be due to invalid IL or missing references)
				//IL_0058: Unknown result type (might be due to invalid IL or missing references)
				//IL_0097: Unknown result type (might be due to invalid IL or missing references)
				if (!(ch.Id == "nk"))
				{
					string text = HelloCodec.VersionOf(ref hello, ch.Id);
					string text2 = HelloCodec.ExtensionOf(ref hello, ch.Id) ?? "";
					Kind val = ChannelCompat.Evaluate(1, hello.Proto, ch.Version, text);
					PeerInfo info = new PeerInfo
					{
						Actor = actor,
						ChannelVersion = (text ?? ""),
						Extension = text2,
						IsMaster = isMaster
					};
					if (ChannelCompat.IsReady(val))
					{
						bool flag = ch.SetSceneReady(actor, hello.SceneReady);
						if (ch.IsPeerReady(actor))
						{
							string text3 = (hadPrev ? (HelloCodec.ExtensionOf(ref prev, ch.Id) ?? "") : "");
							if (ExtensionChange.ShouldRaiseChanged(true, hadPrev, text3, text2))
							{
								ch.RaiseExtensionChanged(info);
							}
						}
						else
						{
							ch.MarkReady(actor, info);
						}
						if (flag)
						{
							ch.RaiseSceneReady(info);
						}
						_incompatWarned.Rearm(actor, ch.Id);
					}
					else
					{
						ch.MarkLost(actor, info);
						if (_incompatWarned.ShouldWarn(actor, ch.Id))
						{
							newlyIncompatible.Add(ch.Id);
						}
					}
				}
			}, (Action<NetChannel, Exception>)delegate(NetChannel ch, Exception e)
			{
				Plugin.Log.LogWarning((object)("[NETKIT] readiness evaluation for channel '" + ch.Id + "' threw " + $"(hello from actor {actor}) — later channels still evaluated: {e}"));
			});
			if (newlyIncompatible.Count > 0)
			{
				Plugin.Log.LogWarning((object)($"[NETKIT] hello from actor {actor} (proto v{hello.Proto}) is INCOMPATIBLE on channel(s) " + string.Join(", ", newlyIncompatible.ToArray()) + " — those features stay inert toward this peer (netdump shows per-channel readiness; warned once per peer/channel)."));
			}
		}

		internal static void ReplayForChannel(NetChannel ch)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			if (ch == null || ch.Id == "nk" || _peers.Count == 0)
			{
				return;
			}
			Entry val = default(Entry);
			foreach (ChannelReadiness item in _peers.EvaluateChannel(1, ch.Id, ch.Version))
			{
				PeerInfo info = new PeerInfo
				{
					Actor = item.Actor,
					ChannelVersion = item.Version,
					Extension = item.Extension,
					IsMaster = item.IsMaster
				};
				if (item.Ready)
				{
					Plugin.Log.LogMessage((object)("[NETKIT] late-registered channel '" + ch.Id + "' — replayed stored hello from " + $"actor {item.Actor}: ready (v{item.Version})."));
					bool flag = _peers.TryGet(item.Actor, ref val) && ch.SetSceneReady(item.Actor, val.Hello.SceneReady);
					ch.MarkReady(item.Actor, info);
					if (flag)
					{
						ch.RaiseSceneReady(info);
					}
				}
				else
				{
					Plugin.Log.LogWarning((object)("[NETKIT] late-registered channel '" + ch.Id + "' — replayed stored hello from " + $"actor {item.Actor}: {item.Kind} (feature stays inert toward this peer)."));
				}
			}
		}

		internal static void RecordHelloSent(string payload, int? toActor)
		{
			if (toActor.HasValue)
			{
				_resend.RecordSentTo(toActor.Value, payload);
			}
			else
			{
				_resend.RecordSent(payload);
			}
		}

		internal static void Tick()
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_0227: Unknown result type (might be due to invalid IL or missing references)
			//IL_022c: Unknown result type (might be due to invalid IL or missing references)
			bool inRoom = PhotonNetwork.inRoom;
			string text = null;
			if (inRoom && PhotonNetwork.room != null)
			{
				text = PhotonNetwork.room.Name;
			}
			RoomChange val = default(RoomChange);
			if (_roomWatch.Update(inRoom, text, ref val))
			{
				_sceneGate.Reset();
				Plugin.Log.LogMessage((object)("[NETKIT] room changed: '" + (val.OldRoomName ?? "(none)") + "' -> '" + (val.NewRoomName ?? "(none)") + "'."));
				Net.RaiseRoomChanged(val);
			}
			if (!inRoom)
			{
				int num = 0;
				foreach (NetChannel channel in Net.Channels)
				{
					num += channel.ReadyCount;
				}
				if (OutOfRoomReset.ShouldReset(_ledger.PendingCount, _ledger.HelloedCount, _peers.Count, num))
				{
					_ledger.Reset();
					_peers.Clear();
					_buildWarned.Clear();
					_incompatWarned.Clear();
					_malformedWarned.Clear();
					foreach (NetChannel channel2 in Net.Channels)
					{
						channel2.ResetPeers();
					}
				}
				_resend.Reset();
				return;
			}
			List<int> list = _ledger.DueForWarning((double)Time.unscaledTime, (double)_warnSeconds);
			if (list != null)
			{
				foreach (int item in list)
				{
					if (PhotonPlayer.Find(item) != null)
					{
						Plugin.Log.LogWarning((object)(Net.Attached ? ($"[NETKIT] no hello from actor {item} {_warnSeconds:F0}s after joining — peer appears UNMODDED " + "(no NetKit): every co-op feature is inert toward them.") : ($"[NETKIT] no hello from actor {item} {_warnSeconds:F0}s after joining — but the LOCAL transport " + "never attached (see the [NETKIT] attach lines), so whether the peer is modded is unknowable.")));
					}
				}
			}
			bool flag = LocalSceneReady();
			object obj;
			if (!flag)
			{
				obj = null;
			}
			else
			{
				Scene activeScene = SceneManager.GetActiveScene();
				obj = ((Scene)(ref activeScene)).name;
			}
			string text2 = (string)obj;
			if (_sceneGate.Fire(true, flag, text2))
			{
				Net.RaiseSceneReady(text2);
				if (!PhotonNetwork.isMasterClient)
				{
					Plugin.Log.LogMessage((object)("[NETKIT] scene-ready in '" + text2 + "' — re-hello (peer-ready resync)."));
					Net.SendHello(null);
				}
			}
			if (_resend.DueForCheck((double)Time.unscaledTime) && _resend.PayloadChanged(Net.BuildHello()))
			{
				Plugin.Log.LogMessage((object)"[NETKIT] hello payload changed since last send — re-hello (extension refresh; peers re-diff without a scene change).");
				Net.SendHello(null);
			}
		}

		internal static void OnPeerConnected(PhotonPlayer newPlayer)
		{
			if (newPlayer != null && PhotonNetwork.inRoom)
			{
				Net.SendHello(newPlayer);
				_ledger.Expect(newPlayer.ID, (double)Time.unscaledTime);
			}
		}

		internal static void OnSelfJoinedRoom()
		{
			if (!PhotonNetwork.inRoom || PhotonNetwork.room == null)
			{
				return;
			}
			ResetLedger();
			if (PhotonNetwork.room.PlayerCount <= 1)
			{
				return;
			}
			Net.SendHello(null);
			PhotonPlayer[] otherPlayers = PhotonNetwork.otherPlayers;
			foreach (PhotonPlayer val in otherPlayers)
			{
				if (val != null)
				{
					_ledger.Expect(val.ID, (double)Time.unscaledTime);
				}
			}
		}

		internal static void OnPeerDisconnected(PhotonPlayer player)
		{
			if (player == null)
			{
				return;
			}
			int actor = player.ID;
			_resend.ForgetActor(actor);
			Entry entry = default(Entry);
			_peers.TryGet(actor, ref entry);
			_peers.Remove(actor);
			EventFanout.ForEach<NetChannel>((IEnumerable<NetChannel>)Net.Channels, (Action<NetChannel>)delegate(NetChannel ch)
			{
				if (!(ch.Id == "nk"))
				{
					ch.MarkLost(actor, new PeerInfo
					{
						Actor = actor,
						ChannelVersion = (HelloCodec.VersionOf(ref entry.Hello, ch.Id) ?? ""),
						Extension = (HelloCodec.ExtensionOf(ref entry.Hello, ch.Id) ?? ""),
						IsMaster = entry.IsMaster
					});
				}
			}, (Action<NetChannel, Exception>)delegate(NetChannel ch, Exception e)
			{
				Plugin.Log.LogWarning((object)("[NETKIT] peer-lost teardown for channel '" + ch.Id + "' threw " + $"(actor {actor}) — later channels still evaluated: {e}"));
			});
		}

		private static void ResetLedger()
		{
			if (_ledger.PendingCount > 0 || _ledger.HelloedCount > 0)
			{
				Plugin.Log.LogMessage((object)($"[NETKIT] peer ledger reset on room join ({_ledger.HelloedCount} hello(s), " + $"{_ledger.PendingCount} pending — actor IDs restart per room)."));
			}
			_ledger.Reset();
			_peers.Clear();
			_buildWarned.Clear();
			_incompatWarned.Clear();
			_malformedWarned.Clear();
			_resend.Reset();
			foreach (NetChannel channel in Net.Channels)
			{
				channel.ResetPeers();
			}
		}
	}
	internal enum SendTarget
	{
		Others,
		Master,
		All,
		Player
	}
	internal interface INetTransport
	{
		string Name { get; }

		bool Attached { get; }

		bool TryAttach();

		bool Send(SendTarget target, PhotonPlayer specific, string channel, string verb, int seq, string extra, string payload);

		bool SendLoopback(string channel, string verb, int seq, string extra, string payload);

		string AttachInfo();
	}
	[HarmonyPatch(typeof(NetworkLevelLoader), "OnPhotonPlayerConnected")]
	internal static class NetKitPeerJoinedHook
	{
		[HarmonyPostfix]
		private static void Postfix(PhotonPlayer _newPlayer)
		{
			try
			{
				HelloService.OnPeerConnected(_newPlayer);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"[NETKIT] peer-connected hook threw: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(NetworkLevelLoader), "OnJoinedRoom")]
	internal static class NetKitSelfJoinedHook
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			try
			{
				HelloService.OnSelfJoinedRoom();
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"[NETKIT] self-joined hook threw: {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(NetworkLevelLoader), "OnPhotonPlayerDisconnected")]
	internal static class NetKitPeerLeftHook
	{
		[HarmonyPostfix]
		private static void Postfix(PhotonPlayer _player)
		{
			try
			{
				HelloService.OnPeerDisconnected(_player);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"[NETKIT] peer-disconnected hook threw: {arg}");
			}
		}
	}
	public enum NetTransportKind
	{
		Rpc,
		Event,
		Sim
	}
	public static class Net
	{
		public const string RpcName = "NK_Bus";

		public const string InternalChannelId = "nk";

		public const string HelloVerb = "nk.hello";

		public const string TestVerb = "nk.test";

		public const int ProtoVersion = 1;

		public static readonly string LocalBuildStamp = BuildStamp.Read(typeof(Net).Assembly);

		private static readonly Dictionary<string, NetChannel> _channels = new Dictionary<string, NetChannel>(StringComparer.Ordinal);

		private static readonly List<NetChannel> _ordered = new List<NetChannel>();

		private static INetTransport _transport;

		private static NetChannel _internal;

		private static int _seqTx;

		private static bool _unavailableLogged;

		private static bool _inited;

		internal static string LastTestPayload;

		private const int UnknownChannelWarnCap = 32;

		private static readonly HashSet<string> _unknownChannelWarned = new HashSet<string>(StringComparer.Ordinal);

		private static readonly HashSet<int> _loopbackPeers = new HashSet<int>();

		private static readonly PendingOnce<NetChannel> _pendingReplays = new PendingOnce<NetChannel>();

		public static bool Attached
		{
			get
			{
				if (_transport != null)
				{
					return _transport.Attached;
				}
				return false;
			}
		}

		public static bool InRoom => PhotonNetwork.inRoom;

		public static bool IsMaster => PhotonNetwork.isMasterClient;

		public static bool IsGuestInRoom => PhotonNetwork.isNonMasterClientInRoom;

		internal static INetTransport Transport => _transport;

		internal static IReadOnlyList<NetChannel> Channels => _ordered;

		internal static NetChannel Internal => _internal;

		public static int ChannelCount => _ordered.Count;

		internal static int NextSeqPreview => _seqTx;

		public static event Action<RoomChange> OnRoomChanged;

		public static event Action<string> OnSceneReady;

		internal static void RaiseRoomChanged(RoomChange change)
		{
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			foreach (NetChannel item in _ordered)
			{
				try
				{
					item.AbortRequestsOnRoomChange();
				}
				catch (Exception arg)
				{
					Plugin.Log.LogWarning((object)$"[NETKIT] room-change request drain for channel '{item.Id}' threw: {arg}");
				}
			}
			Action<RoomChange> onRoomChanged = Net.OnRoomChanged;
			if (onRoomChanged == null)
			{
				return;
			}
			Delegate[] invocationList = onRoomChanged.GetInvocationList();
			foreach (Delegate obj in invocationList)
			{
				try
				{
					((Action<RoomChange>)obj)(change);
				}
				catch (Exception arg2)
				{
					Plugin.Log.LogWarning((object)("[NETKIT] an OnRoomChanged subscriber threw " + $"({obj.Method.DeclaringType?.FullName}.{obj.Method.Name}): {arg2}"));
				}
			}
		}

		internal static void RaiseSceneReady(string scene)
		{
			Action<string> onSceneReady = Net.OnSceneReady;
			if (onSceneReady == null)
			{
				return;
			}
			Delegate[] invocationList = onSceneReady.GetInvocationList();
			foreach (Delegate obj in invocationList)
			{
				try
				{
					((Action<string>)obj)(scene);
				}
				catch (Exception arg)
				{
					Plugin.Log.LogWarning((object)("[NETKIT] an OnSceneReady subscriber threw " + $"({obj.Method.DeclaringType?.FullName}.{obj.Method.Name}): {arg}"));
				}
			}
		}

		internal static void Init(NetTransportKind kind, int eventCode)
		{
			if (!_inited)
			{
				_inited = true;
				INetTransport transport;
				switch (kind)
				{
				default:
				{
					INetTransport netTransport = new RpcRelayTransport();
					transport = netTransport;
					break;
				}
				case NetTransportKind.Sim:
				{
					INetTransport netTransport = new SimTransport();
					transport = netTransport;
					break;
				}
				case NetTransportKind.Event:
				{
					INetTransport netTransport = new EventTransport(eventCode);
					transport = netTransport;
					break;
				}
				}
				_transport = transport;
				_internal = RegisterChannel("nk", "0.2.10", new ChannelOptions
				{
					LogTag = "NETKIT"
				});
				_internal.Register("nk.hello", HelloService.OnHello);
				_internal.Register("nk.test", delegate(NetMessage m)
				{
					LastTestPayload = m.Payload;
				});
				_transport.TryAttach();
				Plugin.Log.LogMessage((object)($"[NETKIT] transport={_transport.Name} attached={_transport.Attached} — " + _transport.AttachInfo()));
			}
		}

		public static NetChannel RegisterChannel(string id, string version, ChannelOptions options = null)
		{
			if (string.IsNullOrEmpty(id))
			{
				throw new ArgumentException("channel id required", "id");
			}
			if (_channels.TryGetValue(id, out var value))
			{
				if (options != null)
				{
					Plugin.Log.LogWarning((object)("[NETKIT] RegisterChannel('" + id + "') called again WITH options — the id is already registered (v" + value.Version + ", tag [" + value.LogTag + "]) and the new options are DISCARDED. Two consumers claiming one channel id share the first caller's configuration."));
				}
				return value;
			}
			NetChannel netChannel = new NetChannel(id, version, options);
			Plugin.Log.LogMessage((object)("[NETKIT] channel '" + id + "' v" + netChannel.Version + " registered (tag [" + netChannel.LogTag + "])."));
			_channels[id] = netChannel;
			_ordered.Add(netChannel);
			_pendingReplays.Add(netChannel);
			return netChannel;
		}

		internal static bool TryGetChannel(string id, out NetChannel ch)
		{
			return _channels.TryGetValue(id ?? "", out ch);
		}

		internal static bool Send(NetChannel ch, SendTarget target, PhotonPlayer specific, string verb, string extra, string payload)
		{
			if (ch == null)
			{
				return false;
			}
			if (!PhotonNetwork.inRoom)
			{
				ch.CountDrop(verb, "not-in-room");
				return false;
			}
			if (_transport == null)
			{
				ch.CountDrop(verb, "not-inited");
				return false;
			}
			if (!_transport.TryAttach())
			{
				ch.CountDrop(verb, "not-attached");
				if (!_unavailableLogged)
				{
					_unavailableLogged = true;
					Plugin.Log.LogWarning((object)("[NETKIT] send dropped — transport '" + _transport.Name + "' never attached (" + _transport.AttachInfo() + "). Co-op messages are disabled until it does (retried every send)."));
				}
				return false;
			}
			_unavailableLogged = false;
			int num = ++_seqTx;
			bool flag = _transport.Send(target, specific, ch.Id, verb, num, extra ?? "", payload ?? "");
			float unscaledTime = Time.unscaledTime;
			if (flag)
			{
				ch.Counters.RecordSend(verb, (double)unscaledTime);
				if (!ch.Quiet.IsQuiet(verb))
				{
					ch.Trace.Add((double)unscaledTime, $"{Dir()} send {verb} seq={num} {TargetTag(target, specific)}");
					if (Plugin.VerboseNet == null || Plugin.VerboseNet.Value)
					{
						Plugin.Log.LogInfo((object)$"[{ch.LogTag}] {Dir()} send {verb} seq={num} {TargetTag(target, specific)}");
					}
				}
			}
			else
			{
				ch.CountDrop(verb, "send-failed");
			}
			return flag;
		}

		internal static bool SendLoopback(NetChannel ch, string verb, string extra, string payload)
		{
			if (ch == null)
			{
				return false;
			}
			if (_transport == null)
			{
				ch.CountDrop(verb, "not-inited");
				return false;
			}
			if (!_transport.TryAttach())
			{
				ch.CountDrop(verb, "not-attached");
				return false;
			}
			_unavailableLogged = false;
			int seq = ++_seqTx;
			bool flag = _transport.SendLoopback(ch.Id, verb, seq, extra ?? "", payload ?? "");
			if (flag)
			{
				ch.Counters.RecordSend(verb, (double)Time.unscaledTime);
			}
			else
			{
				ch.CountDrop(verb, "loopback-failed");
			}
			return flag;
		}

		internal static void DeliverRpc(string channel, string verb, int seq, string extra, string payload, PhotonMessageInfo info)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			int actor = ((info.sender != null) ? info.sender.ID : 0);
			bool isMaster = info.sender != null && info.sender.IsMasterClient;
			bool isSelf = info.sender != null && PhotonNetwork.player != null && info.sender.ID == PhotonNetwork.player.ID;
			Deliver(channel, verb, seq, extra, payload, actor, isMaster, isSelf);
		}

		internal static void DeliverEvent(string channel, string verb, int seq, string extra, string payload, int senderId)
		{
			PhotonPlayer val = PhotonPlayer.Find(senderId);
			bool isMaster = val != null && val.IsMasterClient;
			bool isSelf = PhotonNetwork.player != null && senderId == PhotonNetwork.player.ID;
			Deliver(channel, verb, seq, extra, payload, senderId, isMaster, isSelf);
		}

		private static void Deliver(string channel, string verb, int seq, string extra, string payload, int actor, bool isMaster, bool isSelf)
		{
			if (!_channels.TryGetValue(channel ?? "", out var value))
			{
				if (_unknownChannelWarned.Count < 32 && _unknownChannelWarned.Add(channel ?? "?"))
				{
					Plugin.Log.LogWarning((object)($"[NETKIT] traffic for unregistered channel '{channel}' (actor {actor}) " + "— a mod that isn't installed here, or a newer build. Ignored. (warned once per channel" + $", capped at {32} distinct names)"));
				}
				return;
			}
			float unscaledTime = Time.unscaledTime;
			value.Counters.RecordRecv(verb, (double)unscaledTime);
			if (!value.Quiet.IsQuiet(verb))
			{
				value.Trace.Add((double)unscaledTime, $"{RecvDir(isMaster)} recv {verb} seq={seq} actor={actor}");
				if (Plugin.VerboseNet == null || Plugin.VerboseNet.Value)
				{
					Plugin.Log.LogInfo((object)$"[{value.LogTag}] {RecvDir(isMaster)} recv {verb} seq={seq} actor={actor}");
				}
			}
			value.Deliver(new NetMessage
			{
				Verb = verb,
				Payload = payload,
				Extra = extra,
				SenderActor = actor,
				SenderIsMaster = isMaster,
				SenderIsSelf = isSelf
			});
		}

		internal static string BuildHello()
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			HelloMsg val = new HelloMsg
			{
				Channels = new List<ChannelHello>()
			};
			val.Proto = 1;
			val.Stamp = LocalBuildStamp;
			val.SceneReady = HelloService.LocalSceneReady();
			foreach (NetChannel item in _ordered)
			{
				if (!(item.Id == "nk"))
				{
					val.Channels.Add(new ChannelHello
					{
						Channel = item.Id,
						Version = item.Version,
						Extension = item.HelloExtensionValue()
					});
				}
			}
			return HelloCodec.Encode(val);
		}

		public static void RegisterLoopbackPeer(int actor)
		{
			if (_loopbackPeers.Add(actor))
			{
				DeliverEvent("nk", "nk.hello", 0, "", BuildHello(), actor);
				Plugin.Log.LogMessage((object)($"[NETKIT] loopback peer registered: actor {actor} now answers " + "hellos as a same-bundle install (dev rig)."));
			}
		}

		public static void UnregisterLoopbackPeer(int actor)
		{
			if (_loopbackPeers.Remove(actor))
			{
				Plugin.Log.LogMessage((object)$"[NETKIT] loopback peer unregistered: actor {actor}.");
			}
		}

		internal static bool SendHello(PhotonPlayer to)
		{
			if (HelloService.HelloMuted)
			{
				_internal?.CountDrop("nk.hello", "muted");
				return false;
			}
			string payload = BuildHello();
			bool flag = ((to != null) ? _internal.SendToPlayer(to, "nk.hello", payload) : _internal.SendToOthers("nk.hello", payload));
			if (flag)
			{
				HelloService.RecordHelloSent(payload, (to != null) ? new int?(to.ID) : ((int?)null));
			}
			if (flag && _loopbackPeers.Count > 0)
			{
				foreach (int loopbackPeer in _loopbackPeers)
				{
					if (to == null || to.ID == loopbackPeer)
					{
						DeliverEvent("nk", "nk.hello", 0, "", BuildHello(), loopbackPeer);
					}
				}
			}
			return flag;
		}

		public static string Dump()
		{
			return Diagnostics.Dump();
		}

		internal static void Tick()
		{
			List<NetChannel> list = _pendingReplays.Drain();
			if (list != null)
			{
				foreach (NetChannel item in list)
				{
					HelloService.ReplayForChannel(item);
				}
			}
			HelloService.Tick();
			Diagnostics.HeartbeatTick();
			float unscaledTime = Time.unscaledTime;
			for (int i = 0; i < _ordered.Count; i++)
			{
				_ordered[i].RequestTick(unscaledTime);
				_ordered[i].StoreTick(unscaledTime);
			}
		}

		private static string Dir()
		{
			if (!PhotonNetwork.isMasterClient)
			{
				return "[G→M]";
			}
			return "[M→G]";
		}

		private static string RecvDir(bool senderIsMaster)
		{
			if (!senderIsMaster)
			{
				return "[G→M]";
			}
			return "[M→G]";
		}

		private static string TargetTag(SendTarget t, PhotonPlayer specific)
		{
			if (t != SendTarget.Player || specific == null)
			{
				return $"to={t}";
			}
			return $"to=actor{specific.ID}";
		}
	}
	public struct NetMessage
	{
		public string Verb;

		public string Payload;

		public string Extra;

		public int SenderActor;

		public bool SenderIsMaster;

		public bool SenderIsSelf;
	}
	public struct PeerInfo
	{
		public int Actor;

		public string ChannelVersion;

		public string Extension;

		public bool IsMaster;
	}
	public sealed class ChannelOptions
	{
		public string LogTag;

		public Func<string> HelloExtension;

		public Func<string> HeartbeatFragment;

		public string[] QuietVerbs;
	}
	public sealed class NetChannel
	{
		private struct VerbEntry
		{
			public Action<NetMessage> Handler;

			public HandlerRole Role;
		}

		private readonly Dictionary<string, VerbEntry> _verbs = new Dictionary<string, VerbEntry>(StringComparer.Ordinal);

		internal readonly NetCounters Counters = new NetCounters();

		internal readonly TraceRing Trace = new TraceRing(32);

		internal readonly QuietVerbSet Quiet;

		private readonly HashSet<int> _ready = new HashSet<int>();

		private readonly HashSet<int> _sceneReady = new HashSet<int>();

		private List<ReplicatedStore> _stores;

		private RequestHub _requests;

		internal const string ExtensionProviderThrew = "provider-threw";

		internal const string HeartbeatProviderThrew = "hb-frag-threw";

		private bool _extensionThrewLogged;

		private bool _heartbeatThrewLogged;

		public string Id { get; }

		public string Version { get; }

		public string LogTag { get; }

		internal ChannelOptions Options { get; }

		internal IReadOnlyList<ReplicatedStore> Stores => _stores;

		private RequestHub Requests => _requests ?? (_requests = new RequestHub(this));

		public int ReadyCount => _ready.Count;

		internal IEnumerable<string> Verbs => _verbs.Keys;

		public event Action<PeerInfo> OnPeerReady;

		public event Action<PeerInfo> OnPeerSceneReady;

		public event Action<PeerInfo> OnPeerExtensionChanged;

		public event Action<PeerInfo> OnPeerLost;

		internal NetChannel(string id, string version, ChannelOptions options)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			Id = id;
			Version = version ?? "";
			Options = options ?? new ChannelOptions();
			LogTag = ((!string.IsNullOrEmpty(Options.LogTag)) ? Options.LogTag : id.ToUpperInvariant());
			Quiet = (QuietVerbSet)((Options.QuietVerbs == null) ? ((object)QuietVerbSet.Empty) : ((object)new QuietVerbSet((IEnumerable<string>)Options.QuietVerbs)));
		}

		public void Register(string verb, Action<NetMessage> handler)
		{
			Register(verb, handler, (HandlerRole)0);
		}

		public void Register(string verb, Action<NetMessage> handler, HandlerRole role)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			if (!string.IsNullOrEmpty(verb) && handler != null)
			{
				_verbs[verb] = new VerbEntry
				{
					Handler = handler,
					Role = role
				};
			}
		}

		public bool SendToMaster(string verb, string payload, string extra = "")
		{
			return Net.Send(this, SendTarget.Master, null, verb, extra, payload);
		}

		public bool SendToOthers(string verb, string payload, string extra = "")
		{
			return Net.Send(this, SendTarget.Others, null, verb, extra, payload);
		}

		public bool SendToAll(string verb, string payload, string extra = "")
		{
			return Net.Send(this, SendTarget.All, null, verb, extra, payload);
		}

		public bool SendToPlayer(PhotonPlayer player, string verb, string payload, string extra = "")
		{
			if (player != null)
			{
				return Net.Send(this, SendTarget.Player, player, verb, extra, payload);
			}
			return false;
		}

		public bool SendToActor(int actorId, string verb, string payload, string extra = "")
		{
			PhotonPlayer val = PhotonPlayer.Find(actorId);
			if (val == null)
			{
				CountDrop(verb, "actor-gone");
				return false;
			}
			return Net.Send(this, SendTarget.Player, val, verb, extra, payload);
		}

		public bool SendToAllLoopback(string verb, string payload, string extra = "")
		{
			return Net.SendLoopback(this, verb, extra, payload);
		}

		public void CountDrop(string verb, string reason)
		{
			Counters.RecordDrop(verb, reason, (double)Time.unscaledTime);
		}

		public StateMirror Mirror(string verb, MirrorTarget target, Func<string> build, Func<string, string> quantize = null)
		{
			return Mirror(verb, target, build, new MirrorOptions
			{
				Quantize = quantize
			});
		}

		public StateMirror Mirror(string verb, MirrorTarget target, Func<string> build, MirrorOptions options)
		{
			if (string.IsNullOrEmpty(verb))
			{
				throw new ArgumentException("mirror verb required", "verb");
			}
			if (build == null)
			{
				throw new ArgumentNullException("build");
			}
			return new StateMirror(this, verb, target, build, options);
		}

		public ReplicatedStore RegisterStore(string name, StoreOptions options)
		{
			if (string.IsNullOrEmpty(name))
			{
				throw new ArgumentException("store name required", "name");
			}
			if (_stores != null)
			{
				foreach (ReplicatedStore store in _stores)
				{
					if (string.Equals(store.Name, name, StringComparison.Ordinal))
					{
						Plugin.Log.LogWarning((object)("[" + LogTag + "] RegisterStore('" + name + "') called again — the name is already registered on this channel and the new options are DISCARDED."));
						return store;
					}
				}
			}
			ReplicatedStore replicatedStore = new ReplicatedStore(this, name, options);
			(_stores ?? (_stores = new List<ReplicatedStore>())).Add(replicatedStore);
			Plugin.Log.LogMessage((object)("[" + LogTag + "] store '" + name + "' registered " + $"(authority={options?.Authority ?? StoreAuthority.Master}, verbs " + "'" + options?.Verbs?.Announce + "'/'" + options?.Verbs?.Release + "')."));
			return replicatedStore;
		}

		internal void StoreTick(float now)
		{
			if (_stores != null)
			{
				for (int i = 0; i < _stores.Count; i++)
				{
					_stores[i].Tick(now);
				}
			}
		}

		public bool SendRequest(string verb, string payload, float timeoutSeconds, Action<RequestResult> onResult, string extra = "", bool singleFlight = true)
		{
			return Requests.SendRequest(verb, payload, timeoutSeconds, onResult, extra, singleFlight);
		}

		public void RegisterRequestHandler(string verb, HandlerRole role, Action<NetMessage, Action<string>> handler)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			Requests.RegisterRequestHandler(verb, role, handler);
		}

		public bool TryGetPendingRequest(string verb, out string token, out float secondsLeft)
		{
			if (_requests != null)
			{
				return _requests.TryGetPending(verb, out token, out secondsLeft);
			}
			token = null;
			secondsLeft = 0f;
			return false;
		}

		internal void RequestTick(float now)
		{
			_requests?.Tick(now);
		}

		internal void AbortRequestsOnRoomChange()
		{
			_requests?.DrainAllAsTimedOut();
		}

		public bool IsPeerReady(int actor)
		{
			return _ready.Contains(actor);
		}

		public bool IsPeerSceneReady(int actor)
		{
			return _sceneReady.Contains(actor);
		}

		internal bool HasVerb(string verb)
		{
			return _verbs.ContainsKey(verb);
		}

		internal void Deliver(in NetMessage msg)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			if (_verbs.TryGetValue(msg.Verb ?? "", out var value))
			{
				string text = RoleGuard.Evaluate(value.Role, PhotonNetwork.isMasterClient, msg.SenderIsMaster, msg.SenderIsSelf);
				if (text == null)
				{
					try
					{
						value.Handler(msg);
						return;
					}
					catch (Exception ex)
					{
						Counters.RecordDrop(msg.Verb, "handler-threw", (double)Time.unscaledTime);
						Plugin.Log.LogWarning((object)$"[{LogTag}] handler '{msg.Verb}' threw (actor {msg.SenderActor}): {ex}");
						return;
					}
				}
				Counters.RecordDrop(msg.Verb, text, (double)Time.unscaledTime);
			}
			else
			{
				Counters.RecordDrop(msg.Verb, "no-handler", (double)Time.unscaledTime);
				if (Diagnostics.NoHandler.Record(Id, msg.Verb))
				{
					Plugin.Log.LogWarning((object)("[NETKIT] no handler for verb '" + msg.Verb + "' on channel '" + Id + "' — " + $"dropped (actor {msg.SenderActor}) (further drops counted silently)"));
				}
			}
		}

		private void RaisePeerEvent(Action<PeerInfo> ev, string name, PeerInfo info)
		{
			EventFanout.Raise<PeerInfo>(ev, info, (Action<Delegate, Exception>)delegate(Delegate d, Exception e)
			{
				Plugin.Log.LogWarning((object)("[" + LogTag + "] an " + name + " subscriber threw " + $"({d.Method.DeclaringType?.FullName}.{d.Method.Name}): {e}"));
			});
		}

		internal void MarkReady(int actor, PeerInfo info)
		{
			if (_ready.Add(actor))
			{
				RaisePeerEvent(this.OnPeerReady, "OnPeerReady", info);
			}
		}

		internal void MarkLost(int actor, PeerInfo info)
		{
			_sceneReady.Remove(actor);
			if (_ready.Remove(actor))
			{
				RaisePeerEvent(this.OnPeerLost, "OnPeerLost", info);
			}
		}

		internal bool SetSceneReady(int actor, bool ready)
		{
			if (!ready)
			{
				_sceneReady.Remove(actor);
				return false;
			}
			return _sceneReady.Add(actor);
		}

		internal void RaiseSceneReady(PeerInfo info)
		{
			RaisePeerEvent(this.OnPeerSceneReady, "OnPeerSceneReady", info);
		}

		internal void RaiseExtensionChanged(PeerInfo info)
		{
			RaisePeerEvent(this.OnPeerExtensionChanged, "OnPeerExtensionChanged", info);
		}

		internal void ResetPeers()
		{
			_ready.Clear();
			_sceneReady.Clear();
		}

		internal string HelloExtensionValue()
		{
			if (Options.HelloExtension == null)
			{
				return "";
			}
			try
			{
				return Options.HelloExtension() ?? "";
			}
			catch (Exception arg)
			{
				if (!_extensionThrewLogged)
				{
					_extensionThrewLogged = true;
					Plugin.Log.LogWarning((object)("[" + LogTag + "] the hello-extension provider for channel '" + Id + "' THREW — this channel's hello extension is now the fixed sentinel 'provider-threw', so peers " + $"see a changed extension once and then a stable one: {arg}"));
				}
				return "provider-threw";
			}
		}

		internal string HeartbeatFragmentValue()
		{
			if (Options.HeartbeatFragment == null)
			{
				return "";
			}
			try
			{
				return Options.HeartbeatFragment() ?? "";
			}
			catch (Exception arg)
			{
				if (!_heartbeatThrewLogged)
				{
					_heartbeatThrewLogged = true;
					Plugin.Log.LogWarning((object)("[" + LogTag + "] the heartbeat-fragment provider for channel '" + Id + "' THREW — the " + string.Format("heartbeat line will carry '{0}' from here: {1}", "hb-frag-threw", arg)));
				}
				return "hb-frag-threw";
			}
		}
	}
	[BepInPlugin("cobalt.netkit", "NetKit", "0.2.10")]
	[BepInDependency("cobalt.forgekit", "0.4.13")]
	public class Plugin : BaseUnityPlugin
	{
		public const string GUID = "cobalt.netkit";

		public const string NAME = "NetKit";

		public const string VERSION = "0.2.10";

		public const string COMPAT_SINCE = "0.2.4";

		internal static ModLog Log = new ModLog(Logger.CreateLogSource("NetKit"), (LogTier)3);

		public static ConfigEntry<NetTransportKind> Transport;

		public static ConfigEntry<int> EventCode;

		public static ConfigEntry<float> HeartbeatSeconds;

		public static ConfigEntry<float> HelloWarnSeconds;

		public static ConfigEntry<float> HelloRefreshSeconds;

		public static ConfigEntry<bool> VerboseNet;

		public static ConfigEntry<int> DisconnectTimeoutMs;

		public static ConfigEntry<int> SimActor;

		public static ConfigEntry<int> SimDelayMs;

		public static ConfigEntry<int> SimJitterMs;

		public static ConfigEntry<int> SimDropPct;

		public static ConfigEntry<bool> SimOrdered;

		public static ConfigEntry<bool> SimEchoHello;

		public static ConfigEntry<bool> SimEchoAll;

		public static ConfigEntry<int> SimSeed;

		private CommandRegistry _commands;

		private CommandChannel _channel;

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static void DeclareKitContracts()
		{
			KitContract.Declare("NetKit", "cobalt.forgekit", "0.4.13");
		}

		private void TryDeclareKitContracts()
		{
			try
			{
				DeclareKitContracts();
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("[CONTRACT] kit handshake unavailable (" + ex.GetType().Name + ") — is ForgeKit older than this mod?"));
			}
		}

		internal void Awake()
		{
			//IL_0246: Unknown result type (might be due to invalid IL or missing references)
			//IL_024c: Expected O, but got Unknown
			//IL_02a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b1: Expected O, but got Unknown
			//IL_02d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0311: Expected O, but got Unknown
			//IL_030c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0316: Expected O, but got Unknown
			TryDeclareKitContracts();
			Log = ModLog.Bind((BaseUnityPlugin)(object)this, ((BaseUnityPlugin)this).Logger);
			Transport = ((BaseUnityPlugin)this).Config.Bind<NetTransportKind>("Net", "Transport", NetTransportKind.Rpc, "Which Photon backend carries co-op traffic. Rpc (default) = the NK_Bus [PunRPC] relay piggybacked on CharacterManager's PhotonView (live-proven). Event = PhotonNetwork.RaiseEvent (one event code, no GameObject) — implemented + selftest-covered but NOT yet live-verified for its offline loopback / two-box behavior, so it stays opt-in until a spike clears it. Sim = DEV ONLY: no Photon traffic at all — a simulated wire + one fabricated remote actor (the [Net] Sim* settings; drive it with the `sim` verb). Never ship a profile with Sim set. Boot-time (transport is chosen once at Awake).");
			EventCode = ((BaseUnityPlugin)this).Config.Bind<int>("Net", "EventCode", 177, "The Photon custom event code used by the Event transport (0..199; 200+ are reserved). Only consulted when Transport=Event. Change only if it collides with another mod's RaiseEvent code.");
			HeartbeatSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Net", "HeartbeatSeconds", 30f, "Every N seconds while in a room, log one heartbeat line PER CHANNEL (role, peers ready, the consumer's fragment, PhotonNetwork.time) so a post-hoc log names when a session went quiet. 0 = off. Live via reloadcfg on the consumer side.");
			HelloWarnSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Net", "HelloWarnSeconds", 10f, "How long after a peer joins to wait for its nk.hello before warning it appears UNMODDED. The absence detector is what tells a modded peer from an unmodded one.");
			HelloRefreshSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Net", "HelloRefreshSeconds", 5f, "Every N seconds while in a room, compare the hello payload against the last one sent and RE-SEND it to peers when it changed — this is what carries a mid-session table retune (e.g. a host-side reloadtaming) to the other box with no scene change, so the handshake drift diff re-runs. Both roles. 0 = off (a retune then only rides the guest's next scene-ready re-hello, and a HOST-side retune never re-diffs). Boot-time.");
			VerboseNet = ((BaseUnityPlugin)this).Config.Bind<bool>("Net", "VerboseNet", true, "DEPRECATED — use [Diag] LogLevel (the wire trace rides the Verbose tier). Setting this false still suppresses every NK_Bus send/receive line regardless of LogLevel. netdump's counters + ring buffer are unaffected either way.");
			DisconnectTimeoutMs = ((BaseUnityPlugin)this).Config.Bind<int>("Net", "DisconnectTimeoutMs", 0, "Override Photon's PeerBase.DisconnectTimeout (vanilla 10000 ms). At 10 s any peer silent for longer than that during a long additive/donor load, a GC hitch, or laggy Wi-Fi is torn down and re-read by our hello logic as 'guest went away / unmodded'. Widen to 30000-60000 to ride those out. 0 = leave vanilla (no patch applied — byte-identical install). Negative = invalid, ignored (vanilla). Boot-time (a getter patch applied once at Awake). TRADE-OFF: a genuinely dead peer now takes up to this window to be declared gone; solo unaffected.");
			SimActor = ((BaseUnityPlugin)this).Config.Bind<int>("Net", "SimActor", 9, "Sim transport only: the fabricated remote peer's actor number. Keep it clear of real actor numbers (Photon assigns 1..N from 1) so log lines are unambiguous.");
			SimDelayMs = ((BaseUnityPlugin)this).Config.Bind<int>("Net", "SimDelayMs", 120, "Sim transport only: base one-way latency of the simulated wire, in milliseconds.");
			SimJitterMs = ((BaseUnityPlugin)this).Config.Bind<int>("Net", "SimJitterMs", 40, "Sim transport only: up to this many extra milliseconds of random jitter per envelope.");
			SimDropPct = ((BaseUnityPlugin)this).Config.Bind<int>("Net", "SimDropPct", 0, "Sim transport only: percentage of envelopes the simulated wire loses (0..100). Photon reliable RPCs never drop in reality — nonzero is a hostile-wire stress model.");
			SimOrdered = ((BaseUnityPlugin)this).Config.Bind<bool>("Net", "SimOrdered", true, "Sim transport only: true (default) preserves send order like Photon's reliable-ordered RPCs; false lets jitter reorder deliveries (hostile-timing stress model).");
			SimEchoHello = ((BaseUnityPlugin)this).Config.Bind<bool>("Net", "SimEchoHello", true, "Sim transport only: the sim peer answers an nk.hello with a same-bundle hello of its own, so every channel arms readiness toward it through the normal handshake.");
			SimEchoAll = ((BaseUnityPlugin)this).Config.Bind<bool>("Net", "SimEchoAll", false, "Sim transport only: the sim peer parrots EVERY envelope back verbatim as itself — a dumb fuzzer for handler idempotence/authorization. Distorts request protocols; off by default.");
			SimSeed = ((BaseUnityPlugin)this).Config.Bind<int>("Net", "SimSeed", 0, "Sim transport only: RNG seed for jitter/drop. 0 = derive from the clock and log it. Setting the logged value replays the same drop/jitter draw sequence (delivery timing still depends on frame cadence).");
			Net.Init(Transport.Value, EventCode.Value);
			HelloService.Configure(HelloWarnSeconds.Value, HelloRefreshSeconds.Value);
			Diagnostics.Init();
			Harmony val = new Harmony("cobalt.netkit");
			val.PatchAll(typeof(NetKitPeerJoinedHook));
			val.PatchAll(typeof(NetKitSelfJoinedHook));
			val.PatchAll(typeof(NetKitPeerLeftHook));
			TimeoutPatch.Configure(DisconnectTimeoutMs.Value);
			val.PatchAll(typeof(TimeoutPatch));
			_commands = new CommandRegistry(ModLog.op_Implicit(Log));
			RegisterVerbs();
			_channel = new CommandChannel("NetKit_cmd.txt", ModLog.op_Implicit(Log), _commands, 0.5f, true, true, new CatalogInfo
			{
				ModGuid = "cobalt.netkit",
				ModName = "NetKit",
				ModVersion = "0.2.10",
				ConfigSource = () => ((BaseUnityPlugin)this).Config
			});
			CommonVerbs.RegisterConfigVerbs(_commands, ModLog.op_Implicit(Log), (Func<ConfigFile>)(() => ((BaseUnityPlugin)this).Config), (Action)null, true);
			Log.LogMessage((object)(string.Format("{0} {1} loaded — transport {2}; ", "NetKit", "0.2.10", Transport.Value) + "'help' in BepInEx/config/NetKit_cmd.txt lists the verbs."));
			Log.LogMessage((object)("[NETKIT] build " + BuildStamp.Read(((object)this).GetType().Assembly) + " @ " + ((object)this).GetType().Assembly.Location));
		}

		internal void Update()
		{
			_channel.Tick();
			Net.Tick();
			(Net.Transport as SimTransport)?.Tick();
			PoolPressure.Tick();
		}

		private void RegisterVerbs()
		{
			_commands.Register("netdump", "Co-op census on THIS machine (both roles): transport/attach + per-channel verbs/peers/counters/ring buffer + hello ledger + PUN-signature counters + the unknown-view table.", (Action<string[]>)delegate
			{
				Log.LogMessage((object)Diagnostics.Dump());
			});
			_commands.Register("viewdump", "Photon view-registry census ('viewdump'): registered views + the manuallyAllocatedViewIds ledger (leak candidates / deferred corpse releases) as [PHOTON-RECON] lines — same body DonorKit's photondump forwards to (A10 re-homing 2026-08-15).", (Action<string[]>)delegate
			{
				ViewRegistryDump.Dump(Log.Sink);
			});
			_commands.Register("selftest", "Run the NetKit self-test ([SELFTEST] PASS/FAIL … DONE): hello codec, compat calc, peer-ledger timing, counters, and — in a room — the transport loopback.", (Action<string[]>)delegate
			{
				SelfTest();
			});
			_commands.Register("netmute", "netmute [on|off|status] (bare = status). ON suppresses OUTGOING nk.hello sends so this box reads as UNMODDED to peers — the staging tool for incompatible-peer testplan rows. Incoming handling unchanged. Session-only (not persisted), default off.", (Action<string[]>)NetMute);
			_commands.Register("sim", "Sim-transport control ([Net] Transport=Sim only). 'sim status' = link + counters; 'sim join' = the sim peer introduces itself (injects its hello — channels arm readiness); 'sim leave' = the sim peer departs (peer-lost teardown); 'sim reset' = rebuild the wire from current [Net] Sim* config; 'sim send <channel> <verb> [payload…]' = inject one envelope AS the sim actor.", (Action<string[]>)SimVerb);
		}

		private void SimVerb(string[] args)
		{
			if (!(Net.Transport is SimTransport simTransport))
			{
				Log.LogMessage((object)("[NETKIT] sim: transport is '" + (Net.Transport?.Name ?? "none") + "' — set [Net] Transport=Sim and relaunch to use the simulated peer."));
				return;
			}
			simTransport.TryAttach();
			switch (((args != null && args.Length > 1) ? args[1] : "").Trim().ToLowerInvariant())
			{
			case "join":
				simTransport.InjectHello();
				Log.LogMessage((object)($"[NETKIT] sim: hello injected from sim actor {simTransport.SimActor} — " + "channel readiness now follows the normal handshake (see netdump)."));
				break;
			case "leave":
				simTransport.Leave();
				Log.LogMessage((object)$"[NETKIT] sim: actor {simTransport.SimActor} departed (peer-lost teardown raised).");
				break;
			case "reset":
				simTransport.Rebuild();
				Log.LogMessage((object)("[NETKIT] sim: wire rebuilt — " + simTransport.AttachInfo()));
				break;
			case "send":
			{
				if (args.Length < 4)
				{
					Log.LogMessage((object)"[NETKIT] sim send <channel> <verb> [payload…]");
					break;
				}
				string payload = ((args.Length > 4) ? string.Join(" ", args, 4, args.Length - 4) : "");
				simTransport.InjectEnvelope(args[2], args[3], "", payload);
				Log.LogMessage((object)$"[NETKIT] sim: injected '{args[2]}.{args[3]}' as actor {simTransport.SimActor}.");
				break;
			}
			default:
				Log.LogMessage((object)("[NETKIT] sim status: " + simTransport.AttachInfo()));
				break;
			}
		}

		private void NetMute(string[] args)
		{
			switch (((args != null && args.Length > 1) ? args[1] : "").Trim().ToLowerInvariant())
			{
			case "on":
			case "true":
			case "1":
				if (!HelloService.HelloMuted)
				{
					HelloService.HelloMuted = true;
					Log.LogWarning((object)"[NETKIT] hello muted — this box now reads as UNMODDED to peers (netmute off to restore)");
				}
				else
				{
					Log.LogMessage((object)"[NETKIT] netmute: already muted.");
				}
				break;
			case "off":
			case "false":
			case "0":
				if (HelloService.HelloMuted)
				{
					HelloService.HelloMuted = false;
					Log.LogMessage((object)"[NETKIT] hello UNMUTED — nk.hello sends restored (this box reads as MODDED again).");
				}
				else
				{
					Log.LogMessage((object)"[NETKIT] netmute: already unmuted.");
				}
				break;
			default:
				Log.LogMessage((object)("[NETKIT] netmute status: hello is " + (HelloService.HelloMuted ? "MUTED (this box reads as UNMODDED to peers)" : "active (normal)") + "."));
				break;
			}
		}

		private void SelfTest()
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected O, but got Unknown
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Invalid comparison between Unknown and I4
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Invalid comparison between Unknown and I4
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Invalid comparison between Unknown and I4
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Expected O, but got Unknown
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0258: Unknown result type (might be due to invalid IL or missing references)
			//IL_025f: Expected O, but got Unknown
			SelfTestHarness val = new SelfTestHarness(ModLog.op_Implicit(Log));
			val.Begin("NetKit");
			try
			{
				HelloMsg val2 = new HelloMsg
				{
					Proto = 1,
					Channels = new List<ChannelHello>
					{
						new ChannelHello
						{
							Channel = "ck",
							Version = "0.4;0\\x",
							Extension = "hash=ab\ncd"
						},
						new ChannelHello
						{
							Channel = "sk",
							Version = "0.3.0",
							Extension = ""
						}
					}
				};
				HelloMsg val3 = val2;
				HelloMsg val4 = default(HelloMsg);
				bool flag = HelloCodec.TryDecode(HelloCodec.Encode(val3), ref val4);
				val.Check("hello codec round-trips (incl. ';'/'\\'/newline escaping)", flag && val4.Proto == val3.Proto && val4.Channels.Count == 2 && HelloCodec.VersionOf(ref val4, "ck") == "0.4;0\\x" && HelloCodec.ExtensionOf(ref val4, "ck") == "hash=ab\ncd" && HelloCodec.VersionOf(ref val4, "sk") == "0.3.0");
				val.Check("hello codec refuses malformed payload", !HelloCodec.TryDecode("1;notacount", ref val2));
				val.Check("compat: same proto+version = Compatible", (int)ChannelCompat.Evaluate(1, 1, "0.4.0", "0.4.0") == 0);
				val.Check("compat: proto mismatch", (int)ChannelCompat.Evaluate(1, 2, "0.4.0", "0.4.0") == 1);
				val.Check("compat: peer missing channel", (int)ChannelCompat.Evaluate(1, 1, "0.4.0", (string)null) == 3);
				PeerLedger val5 = new PeerLedger();
				val5.Expect(7, 0.0);
				List<int> list = val5.DueForWarning(5.0, 10.0);
				List<int> list2 = val5.DueForWarning(11.0, 10.0);
				List<int> list3 = val5.DueForWarning(20.0, 10.0);
				val.Check("ledger: warns once past the window, then never again", list == null && list2 != null && list2.Count == 1 && list2[0] == 7 && list3 == null);
				val5.Reset();
				val.Check("ledger: reset forgets everything", val5.PendingCount == 0 && val5.HelloedCount == 0);
				NetCounters val6 = new NetCounters();
				val6.RecordSend("spawn", 1.0);
				val6.RecordRecv("spawn", 2.0);
				val6.RecordDrop("spawn", "no-handler", 3.0);
				val.Check("counters: send/recv/drop recorded", !val6.Empty && val6.TotalSends == 1 && val6.TotalRecvs == 1 && val6.TotalDrops == 1);
				int num = default(int);
				val.Check("pun: unknown-view id parsed from the PUN warn text", PunLog.TryParseViewId("Received OnSerialization for view ID 24007. We have no such PhotonView!", ref num) && num == 24007);
				val.Check("registry: internal 'nk' channel registered", Net.ChannelCount >= 1 && Net.TryGetChannel("nk", out var _));
				if (PhotonNetwork.inRoom)
				{
					val.Check("transport: attached", Net.Attached || Net.Transport.TryAttach());
					Net.LastTestPayload = null;
					bool flag2 = Net.Internal.SendToAllLoopback("nk.test", "loopback-proof");
					val.Check("transport: NK_Bus loopback dispatched through the active transport", flag2 && Net.LastTestPayload == "loopback-proof");
				}
				else
				{
					Log.LogMessage((object)"[SELFTEST] (not in a room — transport loopback skipped; load a save for the live check.)");
				}
			}
			catch (Exception ex)
			{
				val.Exception(ex);
			}
			val.Done();
		}
	}
	internal static class PoolPressure
	{
		private const float ProbeSeconds = 30f;

		private static float _probeAt;

		private static bool _warned;

		static PoolPressure()
		{
			Net.OnRoomChanged += delegate
			{
				_warned = false;
			};
		}

		internal static void Tick()
		{
			if (Time.unscaledTime - _probeAt < 30f)
			{
				return;
			}
			_probeAt = Time.unscaledTime;
			if (_warned)
			{
				return;
			}
			int count;
			try
			{
				List<int> manuallyAllocatedViewIds = PhotonNetwork.manuallyAllocatedViewIds;
				if (manuallyAllocatedViewIds == null)
				{
					return;
				}
				count = manuallyAllocatedViewIds.Count;
			}
			catch
			{
				return;
			}
			if (LeasePolicy.ShouldWarnMintCount(count))
			{
				_warned = true;
				Plugin.Log.LogWarning((object)($"[NETKIT] manual viewID pool pressure: {count} id(s) allocated this room " + "(hard-fail at 999) — something is allocating without releasing. Run 'viewdump' for the census; a scene change reclaims the pool, a relaunch is never required to read this."));
			}
		}
	}
	public enum StoreAuthority
	{
		Master,
		Owner,
		PeerOwned
	}
	public sealed class StoreVerbs
	{
		public string Announce;

		public string Release;
	}
	public sealed class StoreOptions
	{
		public StoreAuthority Authority;

		public Func<string, int, int, OwnerResolution> ResolveUidOwner;

		public float RefreshSeconds = 30f;

		public bool FlushOnPeerReady = true;

		public bool ClearOnOwnerLost = true;

		public float OwnerMissingSeconds = 45f;

		public bool ClearOnRoomChange = true;

		public RebindRule RebindRule;

		public bool ReapAbsentActors;

		public StoreVerbs Verbs;
	}
	public struct RecordMeta
	{
		public int SenderActor;

		public bool IsRebind;

		public bool IsRefresh;

		public string Extra;
	}
	public sealed class ReplicatedStore
	{
		private struct PendingRelease
		{
			public string WireKey;

			public string Reason;
		}

		private readonly NetChannel _ch;

		private readonly StoreOptions _opt;

		private readonly RecordTable _table = new RecordTable();

		private readonly AnnounceBook _book;

		private readonly Dictionary<string, PendingRelease> _pendingRelease = new Dictionary<string, PendingRelease>(StringComparer.Ordinal);

		private const float TickSeconds = 2f;

		private float _nextTickAt;

		public string Name { get; }

		private StoreAuthorityKind Kind => (StoreAuthorityKind)_opt.Authority;

		public int Count => _table.Count;

		public event Action<string, string, RecordMeta> OnSet;

		public event Action<string, string, RecordMeta> OnCleared;

		internal ReplicatedStore(NetChannel ch, string name, StoreOptions options)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Invalid comparison between Unknown and I4
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Invalid comparison between Unknown and I4
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Expected O, but got Unknown
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			_ch = ch;
			Name = name;
			_opt = options ?? new StoreOptions();
			if (_opt.Verbs == null || string.IsNullOrEmpty(_opt.Verbs.Announce) || string.IsNullOrEmpty(_opt.Verbs.Release))
			{
				throw new ArgumentException("store '" + name + "': StoreOptions.Verbs.Announce/Release are required (the wire bytes are the consumer's contract).");
			}
			if ((int)Kind == 2 && !_opt.ClearOnRoomChange)
			{
				throw new ArgumentException("store '" + name + "': PeerOwned authority requires ClearOnRoomChange (its row key is DERIVED from the sender's ACTOR NUMBER, and actor numbers are scoped to a room — carrying rows across a room change leaves ghosts keyed to actors that mean someone else, or nobody, in the new room. The presence reap can't see it either: it compares actor numbers with no room identity. Refusing rather than shipping a store that silently rots.");
			}
			if ((int)Kind == 2 && _opt.ResolveUidOwner != null)
			{
				throw new ArgumentException("store '" + name + "': PeerOwned authority cannot take a ResolveUidOwner binding — its row key is DERIVED from the sender actor, so there is no claimed uid left to authorize. Refusing rather than silently ignoring the callback.");
			}
			_book = new AnnounceBook((double)_opt.RefreshSeconds);
			HandlerRole role = StoreRoles.For(Kind);
			_ch.Register(_opt.Verbs.Announce, OnAnnounceMsg, role);
			_ch.Register(_opt.Verbs.Release, OnReleaseMsg, role);
			_ch.OnPeerLost += delegate(PeerInfo info)
			{
				ClearActorRows(info.Actor, $"owner (actor {info.Actor}) disconnected");
			};
			_ch.OnPeerReady += OnPeerReady;
			_ch.OnPeerSceneReady += OnPeerSceneReady;
			Net.OnRoomChanged += OnRoomChanged;
		}

		public bool Announce(string key, string payload, string extra = "")
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Invalid comparison between Unknown and I4
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Invalid comparison between Unknown and I4
			if (payload == null)
			{
				return false;
			}
			if (StoreRoles.KeyFromSender(Kind) && (!Net.InRoom || LocalActor() == 0))
			{
				return false;
			}
			double num = Time.unscaledTime;
			MirrorDecision val = _book.Decide(key, payload, extra, num);
			if ((int)val != 1 && (int)val != 2)
			{
				return true;
			}
			string text = default(string);
			int num2 = default(int);
			RecordKey.TryParse(key, ref text, ref num2);
			string text2 = RecordKey.Wire(text, num2);
			string extra2 = RecordKey.EncodeExtraSlot(text2, extra);
			bool flag = StoreRoles.BroadcastsToOthers(Kind);
			if (flag)
			{
				ApplyLocal(text2, payload, extra, num);
			}
			if (!(flag ? _ch.SendToOthers(_opt.Verbs.Announce, payload, extra2) : _ch.SendToMaster(_opt.Verbs.Announce, payload, extra2)))
			{
				return false;
			}
			_book.OnLanded(key, payload, extra, num);
			_pendingRelease.Remove(RecordKey.Canonical(key));
			return true;
		}

		public bool Release(string key, string reason = "")
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			string text = default(string);
			int num = default(int);
			RecordKey.TryParse(key, ref text, ref num);
			string wireKey = RecordKey.Wire(text, num);
			_book.Forget(key);
			if (StoreRoles.KeyFromSender(Kind) && (!Net.InRoom || LocalActor() == 0))
			{
				return false;
			}
			RecordRow val = default(RecordRow);
			if (StoreRoles.BroadcastsToOthers(Kind) && _table.Clear(SenderKey(wireKey, LocalActor()), ref val))
			{
				RaiseCleared(val.Key, (reason.Length > 0) ? reason : "released by owner", new RecordMeta
				{
					SenderActor = LocalActor(),
					Extra = val.Extra
				});
			}
			bool flag = SendRelease(wireKey, reason);
			if (!flag && Net.InRoom)
			{
				_pendingRelease[RecordKey.Canonical(key)] = new PendingRelease
				{
					WireKey = wireKey,
					Reason = reason
				};
			}
			else
			{
				_pendingRelease.Remove(RecordKey.Canonical(key));
			}
			return flag;
		}

		public void Invalidate(string key)
		{
			_book.Invalidate(key);
		}

		public bool TryGet(string key, out RecordRow row)
		{
			return _table.TryGet(key, ref row);
		}

		public List<RecordRow> RowsSnapshot()
		{
			return _table.RowsSnapshot();
		}

		public bool ClearLocal(string key, string reason)
		{
			RecordRow val = default(RecordRow);
			if (!_table.Clear(key, ref val))
			{
				return false;
			}
			RaiseCleared(val.Key, reason ?? "cleared", new RecordMeta
			{
				SenderActor = val.ActorId,
				Extra = val.Extra
			});
			return true;
		}

		private void OnAnnounceMsg(NetMessage msg)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Expected I4, but got Unknown
			//IL_023f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0241: Invalid comparison between Unknown and I4
			//IL_02b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b5: Invalid comparison between Unknown and I4
			//IL_034b: Unknown result type (might be due to invalid IL or missing references)
			//IL_034d: Invalid comparison between Unknown and I4
			//IL_0356: Unknown result type (might be due to invalid IL or missing references)
			//IL_0358: Invalid comparison between Unknown and I4
			string text = default(string);
			string wireKey = default(string);
			RecordKey.DecodeExtraSlot(msg.Extra, ref wireKey, ref text);
			wireKey = SenderKey(wireKey, msg.SenderActor);
			Func<string, int, int, OwnerResolution> func = (StoreRoles.OwnerBindingApplies(Kind) ? _opt.ResolveUidOwner : null);
			RecordRow val2 = default(RecordRow);
			RecordApply val = _table.Apply(wireKey, msg.Payload, text, msg.SenderActor, (double)Time.unscaledTime, (Func<int, bool>)ActorPresent, func, ref val2);
			switch ((int)val)
			{
			case 0:
				_ch.CountDrop(_opt.Verbs.Announce, "empty-key");
				return;
			case 1:
				_ch.CountDrop(_opt.Verbs.Announce, "owner-unresolvable");
				Plugin.Log.LogMessage((object)("[" + _ch.LogTag + "] store '" + Name + "': announce for '" + wireKey + "' (actor " + $"{msg.SenderActor}) DEFERRED — owner unresolvable; binding waits for the re-announce."));
				return;
			case 2:
				_ch.CountDrop(_opt.Verbs.Announce, "not-owner");
				Plugin.Log.LogWarning((object)("[" + _ch.LogTag + "] store '" + Name + "': announce for '" + wireKey + "' REFUSED — " + $"sender actor {msg.SenderActor} does not own that uid."));
				return;
			case 3:
				_ch.CountDrop(_opt.Verbs.Announce, "rebind-refused");
				Plugin.Log.LogWarning((object)("[" + _ch.LogTag + "] store '" + Name + "': announce for '" + wireKey + "' REFUSED — " + $"sender actor {msg.SenderActor} tried to rebind a row whose bound actor is still in the room."));
				return;
			}
			if ((int)val == 4)
			{
				Plugin.Log.LogMessage((object)("[" + _ch.LogTag + "] store '" + Name + "': row '" + val2.Key + "' created " + $"(actor {msg.SenderActor})."));
			}
			else if ((int)val == 7)
			{
				Plugin.Log.LogMessage((object)("[" + _ch.LogTag + "] store '" + Name + "': row '" + val2.Key + "' REBOUND to actor " + $"{msg.SenderActor} (previous owner left the room — reconnect)."));
			}
			RaiseSet(val2.Key, val2.Payload, new RecordMeta
			{
				SenderActor = msg.SenderActor,
				IsRebind = ((int)val == 7),
				IsRefresh = ((int)val == 6),
				Extra = val2.Extra
			});
		}

		private void OnReleaseMsg(NetMessage msg)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Invalid comparison between Unknown and I4
			string text = default(string);
			string wireKey = default(string);
			RecordKey.DecodeExtraSlot(msg.Extra, ref wireKey, ref text);
			wireKey = SenderKey(wireKey, msg.SenderActor);
			RecordRow val2 = default(RecordRow);
			RecordRelease val = _table.Release(wireKey, msg.SenderActor, ref val2);
			if ((int)val != 0)
			{
				if ((int)val == 1)
				{
					_ch.CountDrop(_opt.Verbs.Release, "wrong-sender");
					Plugin.Log.LogWarning((object)("[" + _ch.LogTag + "] store '" + Name + "': release for '" + wireKey + "' REFUSED — " + $"sender actor {msg.SenderActor} is not the bound owner actor."));
				}
				else
				{
					string reason = (string.IsNullOrEmpty(msg.Payload) ? "released by owner" : ("owner release: " + msg.Payload));
					RaiseCleared(val2.Key, reason, new RecordMeta
					{
						SenderActor = msg.SenderActor,
						Extra = val2.Extra
					});
				}
			}
			else
			{
				_ch.CountDrop(_opt.Verbs.Release, "no-row");
			}
		}

		private void OnPeerReady(PeerInfo info)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			if (_opt.FlushOnPeerReady)
			{
				if (!StoreRoles.BroadcastsToOthers(Kind))
				{
					_book.InvalidateAll();
				}
				else if (!_ch.IsPeerSceneReady(info.Actor))
				{
					Plugin.Log.LogMessage((object)($"[{_ch.LogTag}] store '{Name}': actor {info.Actor} is channel-ready but " + "still LOADING — flush deferred to its scene-ready hello (N-3; a stuck load leaves this as the last line for that actor)."));
				}
			}
		}

		private void OnPeerSceneReady(PeerInfo info)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			if (_opt.FlushOnPeerReady && StoreRoles.BroadcastsToOthers(Kind))
			{
				int num = FlushTo(info.Actor);
				if (num > 0)
				{
					Plugin.Log.LogMessage((object)($"[{_ch.LogTag}] store '{Name}': flushed {num} record(s) to actor " + $"{info.Actor} (peer scene-ready — set is idempotent)."));
				}
			}
		}

		public int FlushTo(int actor, Action<string> onLanded = null)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			if (!StoreRoles.BroadcastsToOthers(Kind))
			{
				return 0;
			}
			int num = 0;
			foreach (AnnounceEntry item in _book.FlushSnapshot())
			{
				if (!_ch.SendToActor(actor, _opt.Verbs.Announce, item.Payload, RecordKey.EncodeExtraSlot(item.WireKey, item.Extra)))
				{
					continue;
				}
				num++;
				if (onLanded != null)
				{
					try
					{
						onLanded(item.WireKey);
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("[" + _ch.LogTag + "] store '" + Name + "': FlushTo onLanded callback threw for '" + item.WireKey + "': " + ex.Message));
					}
				}
			}
			return num;
		}

		private void OnRoomChanged(RoomChange change)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			_book.Reset();
			_pendingRelease.Clear();
			if (!_opt.ClearOnRoomChange)
			{
				return;
			}
			string reason = "room changed ('" + (change.OldRoomName ?? "none") + "' → '" + (change.NewRoomName ?? "none") + "')";
			foreach (RecordRow item in _table.ClearAll())
			{
				RaiseCleared(item.Key, reason, new RecordMeta
				{
					SenderActor = item.ActorId,
					Extra = item.Extra
				});
			}
		}

		private void ClearActorRows(int actorId, string reason)
		{
			foreach (RecordRow item in _table.ClearActor(actorId))
			{
				RaiseCleared(item.Key, reason, new RecordMeta
				{
					SenderActor = actorId,
					Extra = item.Extra
				});
			}
		}

		internal void Tick(float now)
		{
			if (!(now < _nextTickAt))
			{
				_nextTickAt = now + 2f;
				RetryPendingReleases();
				OwnerMissingBackstop(now);
				PresenceReap(now);
			}
		}

		private void PresenceReap(float now)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			if (!_opt.ReapAbsentActors || !StoreRoles.BroadcastsToOthers(Kind) || _table.Count == 0 || !Net.InRoom)
			{
				return;
			}
			int num = LocalActor();
			List<RecordRow> list = null;
			foreach (RecordRow item in _table.RowsSnapshot())
			{
				if (item.ActorId != num && !ActorPresent(item.ActorId))
				{
					(list ?? (list = new List<RecordRow>())).Add(item);
				}
			}
			if (list == null)
			{
				return;
			}
			RecordRow val = default(RecordRow);
			foreach (RecordRow item2 in list)
			{
				if (_table.Clear(item2.Key, ref val))
				{
					RaiseCleared(val.Key, $"actor {val.ActorId} no longer in room (presence reap)", new RecordMeta
					{
						SenderActor = val.ActorId,
						Extra = val.Extra
					});
				}
			}
		}

		private void RetryPendingReleases()
		{
			if (_pendingRelease.Count == 0)
			{
				return;
			}
			if (!Net.InRoom)
			{
				_pendingRelease.Clear();
				return;
			}
			List<string> list = null;
			foreach (KeyValuePair<string, PendingRelease> item in _pendingRelease)
			{
				if (SendRelease(item.Value.WireKey, item.Value.Reason))
				{
					(list ?? (list = new List<string>())).Add(item.Key);
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (string item2 in list)
			{
				_pendingRelease.Remove(item2);
			}
		}

		private void OwnerMissingBackstop(float now)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Invalid comparison between Unknown and I4
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Invalid comparison between Unknown and I4
			if (!StoreRoles.OwnerBindingApplies(Kind) || !_opt.ClearOnOwnerLost || _opt.ResolveUidOwner == null || _table.Count == 0 || !Net.IsMaster)
			{
				return;
			}
			List<RecordRow> list = null;
			foreach (RecordRow item in _table.RowsSnapshot())
			{
				bool flag;
				try
				{
					flag = (int)_opt.ResolveUidOwner(item.OwnerUid, item.Slot, item.ActorId) > 0;
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("[" + _ch.LogTag + "] store '" + Name + "': owner resolver threw for '" + item.Key + "': " + ex.Message + " — treated as resolvable this tick."));
					flag = true;
				}
				if ((int)_table.TickRow(item, flag, (double)now, (double)_opt.OwnerMissingSeconds) == 1)
				{
					(list ?? (list = new List<RecordRow>())).Add(item);
				}
			}
			if (list == null)
			{
				return;
			}
			RecordRow val = default(RecordRow);
			foreach (RecordRow item2 in list)
			{
				if (_table.Clear(item2.Key, ref val))
				{
					RaiseCleared(val.Key, $"owner unresolvable for {_opt.OwnerMissingSeconds:F0}s (backstop)", new RecordMeta
					{
						SenderActor = val.ActorId,
						Extra = val.Extra
					});
				}
			}
		}

		private bool SendRelease(string wireKey, string reason)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			if (!StoreRoles.BroadcastsToOthers(Kind))
			{
				return _ch.SendToMaster(_opt.Verbs.Release, reason ?? "", wireKey);
			}
			return _ch.SendToOthers(_opt.Verbs.Release, reason ?? "", wireKey);
		}

		private string SenderKey(string wireKey, int actor)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			if (!StoreRoles.KeyFromSender(Kind))
			{
				return wireKey;
			}
			string text = default(string);
			int num = default(int);
			RecordKey.TryParse(wireKey, ref text, ref num);
			return RecordKey.Wire(actor.ToString(CultureInfo.InvariantCulture), num);
		}

		private void ApplyLocal(string wireKey, string payload, string extra, double now)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Invalid comparison between Unknown and I4
			int num = LocalActor();
			RecordRow val2 = default(RecordRow);
			RecordApply val = _table.Apply(SenderKey(wireKey, num), payload, extra, num, now, (Func<int, bool>)ActorPresent, (Func<string, int, int, OwnerResolution>)null, ref val2);
			if (val2 != null)
			{
				RaiseSet(val2.Key, val2.Payload, new RecordMeta
				{
					SenderActor = num,
					IsRefresh = ((int)val == 6),
					Extra = val2.Extra
				});
			}
		}

		private static int LocalActor()
		{
			try
			{
				return (PhotonNetwork.player != null) ? PhotonNetwork.player.ID : 0;
			}
			catch
			{
				return 0;
			}
		}

		private static bool ActorPresent(int actorId)
		{
			try
			{
				return PhotonPlayer.Find(actorId) != null;
			}
			catch
			{
				return false;
			}
		}

		private void RaiseSet(string key, string payload, RecordMeta meta)
		{
			Action<string, string, RecordMeta> action = this.OnSet;
			if (action == null)
			{
				return;
			}
			try
			{
				action(key, payload, meta);
			}
			catch (Exception ex)
			{
				_ch.CountDrop(_opt.Verbs.Announce, "callback-threw");
				Plugin.Log.LogWarning((object)$"[{_ch.LogTag}] store '{Name}': an OnSet subscriber threw for '{key}': {ex}");
			}
		}

		private void RaiseCleared(string key, string reason, RecordMeta meta)
		{
			Action<string, string, RecordMeta> action = this.OnCleared;
			if (action == null)
			{
				return;
			}
			try
			{
				action(key, reason, meta);
			}
			catch (Exception ex)
			{
				_ch.CountDrop(_opt.Verbs.Release, "callback-threw");
				Plugin.Log.LogWarning((object)$"[{_ch.LogTag}] store '{Name}': an OnCleared subscriber threw for '{key}': {ex}");
			}
		}

		internal string Dump()
		{
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			float unscaledTime = Time.unscaledTime;
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append($"[{_ch.LogTag}] store '{Name}' authority={_opt.Authority} rows={_table.Count} " + $"announces={_book.Count} pendingReleases={_pendingRelease.Count} " + string.Format("refresh={0:F0}s binding={1}", _opt.RefreshSeconds, (_opt.ResolveUidOwner != null) ? "on" : "off") + ((StoreRoles.BroadcastsToOthers(Kind) && _opt.ReapAbsentActors) ? " reap=on" : ""));
			bool flag = StoreRoles.BroadcastsToOthers(Kind) && _opt.ReapAbsentActors;
			int num = (flag ? LocalActor() : 0);
			foreach (RecordRow item in _table.RowsSnapshot())
			{
				double num2 = (double)unscaledTime - item.LastSetAt;
				bool flag2 = _opt.RefreshSeconds > 0f && num2 > (double)_opt.RefreshSeconds * 2.5;
				string text = ((item.OwnerMissingSince > 0.0) ? $" ownerMissing={(double)unscaledTime - item.OwnerMissingSince:F0}s" : "");
				stringBuilder.AppendLine();
				string text2 = (flag ? (" present=" + ((item.ActorId == num || ActorPresent(item.ActorId)) ? "yes" : "no")) : "");
				stringBuilder.Append($"[{_ch.LogTag}]   row '{item.Key}' actor={item.ActorId} age={num2:F0}s " + (flag2 ? "STALE" : "fresh") + text2 + text + ((item.Extra.Length > 0) ? (" extra='" + item.Extra + "'") : ""));
			}
			return stringBuilder.ToString();
		}
	}
	public enum RequestOutcome
	{
		Ok,
		TimedOut,
		RefusedNoSend
	}
	public struct RequestResult
	{
		public RequestOutcome Outcome;

		public string Result;

		public string Token;

		public string Refusal;

		public const string ReasonInFlight = "in-flight";

		public const string ReasonSendFailed = "send-failed";

		public static RequestResult Answered(string token, string result)
		{
			return new RequestResult
			{
				Outcome = RequestOutcome.Ok,
				Token = (token ?? ""),
				Result = (result ?? "")
			};
		}

		public static RequestResult Expired(string token)
		{
			return new RequestResult
			{
				Outcome = RequestOutcome.TimedOut,
				Token = (token ?? "")
			};
		}

		public static RequestResult Refused(string refusal, string token = "")
		{
			return new RequestResult
			{
				Outcome = RequestOutcome.RefusedNoSend,
				Token = (token ?? ""),
				Refusal = (refusal ?? "")
			};
		}
	}
	internal sealed class RequestHub
	{
		private const string AckSuffix = ".ack";

		private readonly NetChannel _ch;

		private readonly RequestBook _book = new RequestBook();

		private readonly Dictionary<string, Action<RequestResult>> _callbacks = new Dictionary<string, Action<RequestResult>>(StringComparer.Ordinal);

		private readonly HashSet<string> _ackRoutes = new HashSet<string>(StringComparer.Ordinal);

		internal RequestHub(NetChannel ch)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			_ch = ch;
		}

		internal static string AckVerbFor(string verb)
		{
			return verb + ".ack";
		}

		internal bool SendRequest(string verb, string payload, float timeoutSeconds, Action<RequestResult> onResult, string extra, bool singleFlight)
		{
			if (string.IsNullOrEmpty(verb) || onResult == null)
			{
				return false;
			}
			EnsureAckRoute(verb);
			int num = ((PhotonNetwork.player != null) ? PhotonNetwork.player.ID : 0);
			string refusal = default(string);
			string text = _book.Begin(verb, num, (double)Time.unscaledTime, (double)timeoutSeconds, singleFlight, ref refusal);
			if (text == null)
			{
				Invoke(onResult, RequestResult.Refused(refusal), verb);
				return false;
			}
			_callbacks[text] = onResult;
			if (!_ch.SendToMaster(verb, RequestCodec.EncodeRequest(text, payload), extra ?? ""))
			{
				_callbacks.Remove(text);
				_book.Abort(text);
				Invoke(onResult, RequestResult.Refused("send-failed", text), verb);
				return false;
			}
			return true;
		}

		private void EnsureAckRoute(string verb)
		{
			if (_ackRoutes.Add(verb))
			{
				string ackVerb = AckVerbFor(verb);
				_ch.Register(ackVerb, delegate(NetMessage msg)
				{
					OnAck(ackVerb, msg);
				}, (HandlerRole)4);
			}
		}

		private void OnAck(string ackVerb, NetMessage msg)
		{
			string text = default(string);
			string result = default(string);
			if (!RequestCodec.TryParseAck(msg.Payload, ref text, ref result))
			{
				_ch.CountDrop(ackVerb, "unparseable");
				return;
			}
			string text2 = default(string);
			if (!_book.TryResolve(text, ref text2))
			{
				_ch.CountDrop(ackVerb, "stale-token");
				return;
			}
			if (_callbacks.TryGetValue(text, out var value))
			{
				_callbacks.Remove(text);
				Invoke(value, RequestResult.Answered(text, result), text2);
				return;
			}
			_ch.CountDrop(ackVerb, "ack-no-callback");
			Plugin.Log.LogWarning((object)("[" + _ch.LogTag + "] request '" + text2 + "' ack (token " + text + ") resolved but NO callback was registered — the requester will never be told. This should be impossible; please report it."));
		}

		internal void Tick(float now)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			if (_book.PendingCount == 0)
			{
				return;
			}
			List<Expired> list = _book.TakeExpired((double)now);
			if (list == null)
			{
				return;
			}
			foreach (Expired item in list)
			{
				_ch.CountDrop(item.Verb, "request-timeout");
				if (_callbacks.TryGetValue(item.Token, out var value))
				{
					_callbacks.Remove(item.Token);
					Invoke(value, RequestResult.Expired(item.Token), item.Verb);
				}
			}
		}

		internal void DrainAllAsTimedOut()
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing r