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 list = new List(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 peerHello in HelloService.PeerHellos) { List list2 = new List(); 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 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()); } 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 _peerHellos = new Dictionary(); private static readonly PeerLedger _ledger = new PeerLedger(); private static readonly HashSet _buildWarned = new HashSet(); private static string _lastHelloScene; private static float _warnSeconds = 10f; internal static bool HelloMuted; internal static IReadOnlyDictionary 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 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 _channels = new Dictionary(StringComparer.Ordinal); private static readonly List _ordered = new List(); 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 _unknownChannelWarned = new HashSet(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 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() }; 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 HelloExtension; public Func HeartbeatFragment; } public sealed class NetChannel { private readonly Dictionary> _verbs = new Dictionary>(StringComparer.Ordinal); internal readonly NetCounters Counters = new NetCounters(); internal readonly TraceRing Trace = new TraceRing(32); private readonly HashSet _ready = new HashSet(); public string Id { get; } public string Version { get; } public string LogTag { get; } internal ChannelOptions Options { get; } public int ReadyCount => _ready.Count; internal IEnumerable Verbs => _verbs.Keys; public event Action OnPeerReady; public event Action OnPeerExtensionChanged; public event Action 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 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 Transport; public static ConfigEntry EventCode; public static ConfigEntry HeartbeatSeconds; public static ConfigEntry HelloWarnSeconds; public static ConfigEntry VerboseNet; public static ConfigEntry 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("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("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("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("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("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("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)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)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)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 { 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 list = val5.DueForWarning(5.0, 10.0); List list2 = val5.DueForWarning(11.0, 10.0); List 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() ?? gameObject.AddComponent(); _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; } }