Decompiled source of NetKit v0.1.0

NetKit/NetKit.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
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 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.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+f5c3eb8170447e518d8135c1b536619a5acb5844")]
[assembly: AssemblyProduct("NetKit")]
[assembly: AssemblyTitle("NetKit")]
[assembly: AssemblyMetadata("BuildStamp", "f5c3eb8 2026-07-24")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.0.0")]
[module: UnverifiableCode]
namespace NetKit;

internal static class Diagnostics
{
	private static readonly PunSignatureCounters _signatures = new PunSignatureCounters();

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

	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)
		{
			float unscaledTime = Time.unscaledTime;
			_signatures.Record(num, (double)unscaledTime);
			int num2 = default(int);
			if (num == 0 && PunLog.TryParseViewId(condition, ref num2) && _unknownViews.Record(num2, (double)unscaledTime))
			{
				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)."));
			}
		}
	}

	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"))
			{
				string text = channel.HeartbeatFragmentValue();
				if (UnknownViewWarnCount > 0)
				{
					text = (string.IsNullOrEmpty(text) ? "" : (text + " ")) + $"unknownViewWarns={UnknownViewWarnCount}";
				}
				Plugin.Log.LogMessage((object)Heartbeat.Line(channel.LogTag, isMasterClient, num2, channel.ReadyCount, channel.Counters.TotalSends, channel.Counters.TotalRecvs, channel.Counters.TotalDrops, text, time));
			}
		}
	}

	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_0294: Unknown result type (might be due to invalid IL or missing references)
		//IL_02f8: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_030b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0325: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_02bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_02cc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0360: 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}");
			}
		}
		stringBuilder.AppendLine(NoHandler.Summary("NETKIT"));
		if (HelloService.PeerHellos.Count == 0)
		{
			stringBuilder.AppendLine("[NETKIT] no peer hellos received.");
		}
		foreach (KeyValuePair<int, HelloMsg> peerHello in HelloService.PeerHellos)
		{
			List<string> list2 = new List<string>();
			if (peerHello.Value.Channels != null)
			{
				foreach (ChannelHello channel2 in peerHello.Value.Channels)
				{
					list2.Add(channel2.Channel + "=" + channel2.Version);
				}
			}
			string text = (string.IsNullOrEmpty(peerHello.Value.Stamp) ? "(none)" : peerHello.Value.Stamp);
			string text2 = (BuildStampCompat.ShouldWarn(Net.LocalBuildStamp, peerHello.Value.Stamp) ? " ⚠DIFFERS" : "");
			stringBuilder.AppendLine($"[NETKIT] peer actor {peerHello.Key}: proto v{peerHello.Value.Proto} " + ((PhotonPlayer.Find(peerHello.Key) != 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}";
		}
		catch (Exception ex)
		{
			return "[NETKIT] loader: n/a (read 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)
		{
			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 Dictionary<int, HelloMsg> _peerHellos = new Dictionary<int, HelloMsg>();

	private static readonly PeerLedger _ledger = new PeerLedger();

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

	private static string _lastHelloScene;

	private static float _warnSeconds = 10f;

	internal static bool HelloMuted;

	internal static IReadOnlyDictionary<int, HelloMsg> PeerHellos => _peerHellos;

	internal static PeerLedger Ledger => _ledger;

	internal static void Configure(float warnSeconds)
	{
		_warnSeconds = ((warnSeconds > 0f) ? warnSeconds : 10f);
	}

	internal static void OnHello(NetMessage msg)
	{
		//IL_0083: Unknown result type (might be due to invalid IL or missing references)
		//IL_008f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a0: 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)
		_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");
			Plugin.Log.LogWarning((object)$"[NETKIT] malformed hello from actor {msg.SenderActor}: '{msg.Payload}'.");
		}
		else
		{
			HelloMsg value;
			bool hadPrev = _peerHellos.TryGetValue(msg.SenderActor, out value);
			_peerHellos[msg.SenderActor] = val;
			WarnOnBuildMismatch(msg.SenderActor, val.Stamp);
			ApplyReadiness(msg.SenderActor, value, 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_0055: 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_012f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
		bool flag = false;
		foreach (NetChannel channel in Net.Channels)
		{
			if (channel.Id == "nk")
			{
				continue;
			}
			string text = HelloCodec.VersionOf(ref hello, channel.Id);
			string text2 = HelloCodec.ExtensionOf(ref hello, channel.Id) ?? "";
			Kind val = ChannelCompat.Evaluate(1, hello.Proto, channel.Version, text);
			PeerInfo info = new PeerInfo
			{
				Actor = actor,
				ChannelVersion = (text ?? ""),
				Extension = text2,
				IsMaster = isMaster
			};
			if (ChannelCompat.IsReady(val))
			{
				if (channel.IsPeerReady(actor))
				{
					string text3 = (hadPrev ? (HelloCodec.ExtensionOf(ref prev, channel.Id) ?? "") : "");
					if (ExtensionChange.ShouldRaiseChanged(true, hadPrev, text3, text2))
					{
						channel.RaiseExtensionChanged(info);
					}
				}
				else
				{
					channel.MarkReady(actor, info);
				}
			}
			else
			{
				channel.MarkLost(actor, info);
				flag = true;
			}
		}
		if (flag)
		{
			Plugin.Log.LogWarning((object)($"[NETKIT] hello from actor {actor} (proto v{hello.Proto}) is INCOMPATIBLE on one or " + "more channels — those features stay inert toward this peer (netdump shows per-channel readiness)."));
		}
	}

	internal static void Tick()
	{
		//IL_010e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0113: Unknown result type (might be due to invalid IL or missing references)
		if (!PhotonNetwork.inRoom)
		{
			if (_ledger.PendingCount > 0)
			{
				_ledger.Reset();
			}
			_lastHelloScene = null;
			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.")));
				}
			}
		}
		if (!PhotonNetwork.isMasterClient && (Object)(object)NetworkLevelLoader.Instance != (Object)null && NetworkLevelLoader.Instance.IsOverallLoadingDone && (Object)(object)CharacterManager.Instance != (Object)null && (Object)(object)CharacterManager.Instance.GetFirstLocalCharacter() != (Object)null)
		{
			Scene activeScene = SceneManager.GetActiveScene();
			string name = ((Scene)(ref activeScene)).name;
			if (!string.Equals(name, _lastHelloScene, StringComparison.Ordinal))
			{
				_lastHelloScene = name;
				Plugin.Log.LogMessage((object)("[NETKIT] scene-ready in '" + name + "' — re-hello (peer-ready resync)."));
				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 iD = player.ID;
		_peerHellos.TryGetValue(iD, out var value);
		foreach (NetChannel channel in Net.Channels)
		{
			if (!(channel.Id == "nk"))
			{
				channel.MarkLost(iD, new PeerInfo
				{
					Actor = iD,
					ChannelVersion = (HelloCodec.VersionOf(ref value, channel.Id) ?? ""),
					Extension = (HelloCodec.ExtensionOf(ref value, channel.Id) ?? ""),
					IsMaster = false
				});
			}
		}
		_peerHellos.Remove(iD);
	}

	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();
		_peerHellos.Clear();
		_buildWarned.Clear();
		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)
	{
		HelloService.OnPeerConnected(_newPlayer);
	}
}
[HarmonyPatch(typeof(NetworkLevelLoader), "OnJoinedRoom")]
internal static class NetKitSelfJoinedHook
{
	[HarmonyPostfix]
	private static void Postfix()
	{
		HelloService.OnSelfJoinedRoom();
	}
}
[HarmonyPatch(typeof(NetworkLevelLoader), "OnPhotonPlayerDisconnected")]
internal static class NetKitPeerLeftHook
{
	[HarmonyPostfix]
	private static void Postfix(PhotonPlayer _player)
	{
		HelloService.OnPeerDisconnected(_player);
	}
}
public enum NetTransportKind
{
	Rpc,
	Event
}
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 static readonly HashSet<string> _unknownChannelWarned = new HashSet<string>(StringComparer.Ordinal);

	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;

	internal static void Init(NetTransportKind kind, int eventCode)
	{
		if (!_inited)
		{
			_inited = true;
			INetTransport transport;
			if (kind != NetTransportKind.Event)
			{
				INetTransport netTransport = new RpcRelayTransport();
				transport = netTransport;
			}
			else
			{
				INetTransport netTransport = new EventTransport(eventCode);
				transport = netTransport;
			}
			_transport = transport;
			_internal = RegisterChannel("nk", "0.1.0", 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))
		{
			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);
		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.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;
		}
		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);
			ch.Trace.Add((double)unscaledTime, $"{Dir()} send {verb} seq={num} {TargetTag(target, specific)}");
			if (Plugin.VerboseNet != null && Plugin.VerboseNet.Value)
			{
				Plugin.Log.LogMessage((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.TryAttach())
		{
			ch.CountDrop(verb, "not-attached");
			return 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.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)"));
			}
			return;
		}
		float unscaledTime = Time.unscaledTime;
		value.Counters.RecordRecv(verb, (double)unscaledTime);
		value.Trace.Add((double)unscaledTime, $"{RecvDir(isMaster)} recv {verb} seq={seq} actor={actor}");
		if (Plugin.VerboseNet != null && Plugin.VerboseNet.Value)
		{
			Plugin.Log.LogMessage((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_0051: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_0086: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a6: 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;
		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);
	}

	internal static bool SendHello(PhotonPlayer to)
	{
		if (HelloService.HelloMuted)
		{
			_internal?.CountDrop("nk.hello", "muted");
			return false;
		}
		string payload = BuildHello();
		if (to == null)
		{
			return _internal.SendToOthers("nk.hello", payload);
		}
		return _internal.SendToPlayer(to, "nk.hello", payload);
	}

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

	internal static void Tick()
	{
		HelloService.Tick();
		Diagnostics.HeartbeatTick();
	}

	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 sealed class NetChannel
{
	private readonly Dictionary<string, Action<NetMessage>> _verbs = new Dictionary<string, Action<NetMessage>>(StringComparer.Ordinal);

	internal readonly NetCounters Counters = new NetCounters();

	internal readonly TraceRing Trace = new TraceRing(32);

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

	public string Id { get; }

	public string Version { get; }

	public string LogTag { get; }

	internal ChannelOptions Options { get; }

	public int ReadyCount => _ready.Count;

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

	public event Action<PeerInfo> OnPeerReady;

	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
		Id = id;
		Version = version ?? "";
		Options = options ?? new ChannelOptions();
		LogTag = ((!string.IsNullOrEmpty(Options.LogTag)) ? Options.LogTag : id.ToUpperInvariant());
	}

	public void Register(string verb, Action<NetMessage> handler)
	{
		if (!string.IsNullOrEmpty(verb) && handler != null)
		{
			_verbs[verb] = handler;
		}
	}

	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 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 bool IsPeerReady(int actor)
	{
		return _ready.Contains(actor);
	}

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

	internal void Deliver(in NetMessage msg)
	{
		if (_verbs.TryGetValue(msg.Verb ?? "", out var value))
		{
			try
			{
				value(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, "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)"));
		}
	}

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

	internal void MarkLost(int actor, PeerInfo info)
	{
		if (_ready.Remove(actor))
		{
			this.OnPeerLost?.Invoke(info);
		}
	}

	internal void RaiseExtensionChanged(PeerInfo info)
	{
		this.OnPeerExtensionChanged?.Invoke(info);
	}

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

	internal string HelloExtensionValue()
	{
		if (Options.HelloExtension == null)
		{
			return "";
		}
		try
		{
			return Options.HelloExtension() ?? "";
		}
		catch (Exception ex)
		{
			return "provider-threw:" + ex.Message;
		}
	}

	internal string HeartbeatFragmentValue()
	{
		if (Options.HeartbeatFragment == null)
		{
			return "";
		}
		try
		{
			return Options.HeartbeatFragment() ?? "";
		}
		catch (Exception ex)
		{
			return "hb-frag-threw:" + ex.Message;
		}
	}
}
[BepInPlugin("cobalt.netkit", "NetKit", "0.1.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : BaseUnityPlugin
{
	public const string GUID = "cobalt.netkit";

	public const string NAME = "NetKit";

	public const string VERSION = "0.1.0";

	internal static ManualLogSource Log = Logger.CreateLogSource("NetKit");

	public static ConfigEntry<NetTransportKind> Transport;

	public static ConfigEntry<int> EventCode;

	public static ConfigEntry<float> HeartbeatSeconds;

	public static ConfigEntry<float> HelloWarnSeconds;

	public static ConfigEntry<bool> VerboseNet;

	public static ConfigEntry<int> DisconnectTimeoutMs;

	private CommandRegistry _commands;

	private CommandChannel _channel;

	internal void Awake()
	{
		//IL_0109: Unknown result type (might be due to invalid IL or missing references)
		//IL_010f: Expected O, but got Unknown
		//IL_0165: Unknown result type (might be due to invalid IL or missing references)
		//IL_016f: Expected O, but got Unknown
		//IL_018d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0197: Expected O, but got Unknown
		Log = ((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. 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.");
		VerboseNet = ((BaseUnityPlugin)this).Config.Bind<bool>("Net", "VerboseNet", true, "Log every NK_Bus send/receive as one line under the owning channel's tag (direction-tagged, seq-numbered so host+guest logs join offline). Control-plane traffic is small; netdump's counters + ring buffer remain when this is off.");
		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.");
		Net.Init(Transport.Value, EventCode.Value);
		HelloService.Configure(HelloWarnSeconds.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(Log);
		RegisterVerbs();
		_channel = new CommandChannel("NetKit_cmd.txt", Log, _commands, 0.5f, true, true);
		Log.LogMessage((object)(string.Format("{0} {1} loaded — transport {2}; ", "NetKit", "0.1.0", 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();
	}

	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("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);
	}

	private void NetMute(string[] args)
	{
		switch (((args != null && args.Length != 0) ? args[0] : "").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_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Expected O, but got Unknown
		//IL_0018: 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_005a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0064: Unknown result type (might be due to invalid IL or missing references)
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_009a: Unknown result type (might be due to invalid IL or missing references)
		//IL_009c: Unknown result type (might be due to invalid IL or missing references)
		//IL_009d: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
		//IL_014a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0150: Invalid comparison between Unknown and I4
		//IL_0169: Unknown result type (might be due to invalid IL or missing references)
		//IL_016f: Invalid comparison between Unknown and I4
		//IL_0184: Unknown result type (might be due to invalid IL or missing references)
		//IL_018a: Invalid comparison between Unknown and I4
		//IL_0191: Unknown result type (might be due to invalid IL or missing references)
		//IL_0198: Expected O, but got Unknown
		//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_0253: Unknown result type (might be due to invalid IL or missing references)
		//IL_025a: Expected O, but got Unknown
		SelfTestHarness val = new SelfTestHarness(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 sealed class RpcRelayTransport : INetTransport
{
	private NkBusRelay _relay;

	private PhotonView _view;

	public string Name => "Rpc";

	public bool Attached
	{
		get
		{
			if ((Object)(object)_relay != (Object)null)
			{
				return (Object)(object)_view != (Object)null;
			}
			return false;
		}
	}

	public bool TryAttach()
	{
		if (Attached)
		{
			return true;
		}
		try
		{
			CharacterManager instance = CharacterManager.Instance;
			PhotonView val = (((Object)(object)instance != (Object)null) ? ((MonoBehaviour)instance).photonView : null);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			GameObject gameObject = ((Component)val).gameObject;
			_relay = gameObject.GetComponent<NkBusRelay>() ?? gameObject.AddComponent<NkBusRelay>();
			_view = val;
			val.RefreshRpcMonoBehaviourCache();
			Plugin.Log.LogMessage((object)$"[NETKIT] Rpc relay attached to '{((Object)gameObject).name}' (viewID {val.viewID}).");
			return true;
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("[NETKIT] Rpc relay attach threw: " + ex.Message));
			return false;
		}
	}

	public bool Send(SendTarget target, PhotonPlayer specific, string channel, string verb, int seq, string extra, string payload)
	{
		//IL_005b: Unknown result type (might be due to invalid IL or missing references)
		if (!TryAttach())
		{
			return false;
		}
		try
		{
			if (target == SendTarget.Player)
			{
				if (specific == null)
				{
					return false;
				}
				_view.RPC("NK_Bus", specific, new object[5] { channel, verb, seq, extra, payload });
			}
			else
			{
				_view.RPC("NK_Bus", ToPhotonTargets(target), new object[5] { channel, verb, seq, extra, payload });
			}
			return true;
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)$"[NETKIT] Rpc send '{channel}.{verb}' seq={seq} failed: {ex.Message}");
			return false;
		}
	}

	public bool SendLoopback(string channel, string verb, int seq, string extra, string payload)
	{
		if (!PhotonNetwork.inRoom || !TryAttach())
		{
			return false;
		}
		try
		{
			_view.RPC("NK_Bus", (PhotonTargets)0, new object[5] { channel, verb, seq, extra, payload });
			return true;
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("[NETKIT] Rpc loopback '" + channel + "." + verb + "' failed: " + ex.Message));
			return false;
		}
	}

	public string AttachInfo()
	{
		if (!Attached)
		{
			return "Rpc relay unattached (CharacterManager view unresolved)";
		}
		return $"host '{((Object)((Component)_view).gameObject).name}' viewID {_view.viewID} relay-in-cache={RelayInRpcCache()}";
	}

	private bool RelayInRpcCache()
	{
		try
		{
			MonoBehaviour[] rpcMonoBehaviours = _view.RpcMonoBehaviours;
			if (rpcMonoBehaviours == null)
			{
				return false;
			}
			MonoBehaviour[] array = rpcMonoBehaviours;
			foreach (MonoBehaviour val in array)
			{
				if (val is NkBusRelay)
				{
					return true;
				}
			}
			return false;
		}
		catch
		{
			return false;
		}
	}

	private static PhotonTargets ToPhotonTargets(SendTarget t)
	{
		return (PhotonTargets)(t switch
		{
			SendTarget.Master => 2, 
			SendTarget.All => 0, 
			_ => 1, 
		});
	}
}
public class NkBusRelay : MonoBehaviour
{
	[PunRPC]
	public void NK_Bus(string channel, string verb, int seq, string extra, string payload, PhotonMessageInfo info)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		Net.DeliverRpc(channel, verb, seq, extra, payload, info);
	}
}
[HarmonyPatch]
internal static class TimeoutPatch
{
	internal static int OverrideMs;

	internal static bool Configure(int configured)
	{
		bool flag = default(bool);
		OverrideMs = TimeoutConfig.Resolve(configured, ref flag);
		if (flag)
		{
			Plugin.Log.LogWarning((object)($"[NETKIT] DisconnectTimeoutMs={configured} is negative — ignored, " + $"leaving Photon's vanilla {10000} ms."));
		}
		if (OverrideMs > 0)
		{
			Plugin.Log.LogMessage((object)($"[NETKIT] DisconnectTimeout override armed: {OverrideMs} ms " + $"(vanilla {10000})"));
		}
		return OverrideMs > 0;
	}

	private static bool Prepare()
	{
		return OverrideMs > 0;
	}

	private static MethodBase TargetMethod()
	{
		return AccessTools.PropertyGetter(typeof(PeerBase), "DisconnectTimeout");
	}

	private static void Postfix(ref int __result)
	{
		__result = OverrideMs;
	}
}

NetKit/NetKit.Core.dll

Decompiled a day 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("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+f5c3eb8170447e518d8135c1b536619a5acb5844")]
[assembly: AssemblyProduct("NetKit.Core")]
[assembly: AssemblyTitle("NetKit.Core")]
[assembly: AssemblyMetadata("BuildStamp", "f5c3eb8 2026-07-24")]
[assembly: AssemblyVersion("1.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 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 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 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 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 ?? ""));
			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];
			}
			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 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>();

		public bool Empty => _trace.Count == 0;

		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;
			}
		}

		private VerbTrace For(string verb)
		{
			verb = verb ?? "?";
			if (!_trace.TryGetValue(verb, out VerbTrace value))
			{
				value = (_trace[verb] = new VerbTrace());
			}
			return value;
		}

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

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

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

		public string Summary(string tag, double now)
		{
			if (_trace.Count == 0)
			{
				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));
			}
			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 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;
		}

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

		private readonly int _cap;

		public int IdCount => _rows.Count;

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

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

		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 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;
				}
			}
		}
	}
}