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