using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using Burst2Flame.Observable;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using ModsMenu;
using Steamworks;
using Steamworks.Data;
using StolenRealmModAPI;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ModNet")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+170bee66ef82872534ee913dc7aac45a42464837")]
[assembly: AssemblyProduct("ModNet")]
[assembly: AssemblyTitle("ModNet")]
[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]
[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 ModNet
{
internal static class MenuCard
{
private const string Warn = "#E0A040";
private const string Dim = "#9A8F85";
internal static void Register()
{
ModsMenuApi.AddSection("com.helvetica.modnet", (Action<SectionBuilder>)Build);
}
private static void Build(SectionBuilder b)
{
b.AddSubheader("Players");
if (!Session.IsMultiplayer)
{
b.AddLabel("Not in a multiplayer game.");
}
else
{
if (!Session.IsReady)
{
b.AddLabel("Connecting... (starts a few seconds after your characters appear)");
}
Dictionary<string, string> mine = Session.LocalMods();
foreach (PlayerInfo player in Session.Players)
{
AddPlayer(b, player, mine);
}
b.AddLabel(string.Format("<color={0}>{1} Messages sent {2}, received {3}. ", "#9A8F85", Session.LobbyInfo(), Transport.Sent, Transport.Received) + "Updated when this card opens.</color>");
}
if (Plugin.TestTools.Value)
{
b.AddSubheader("Tests");
b.AddButton("Ping", (Action)delegate
{
Notify(Session.Ping());
}, "Measures the round trip to every modded player (BepInEx log).");
b.AddButton("Payload size test", (Action)delegate
{
Notify(Session.SizeTest());
}, "Sends 250 to 4000 characters and logs which sizes arrive on each PC.");
}
}
private static void AddPlayer(SectionBuilder b, PlayerInfo p, Dictionary<string, string> mine)
{
string text = p.Name + (p.IsMe ? " (you)" : "");
if (!p.HasModNet)
{
b.AddLabel(text + ": <color=#9A8F85>no ModNet (or not connected yet). They see the vanilla game.</color>");
return;
}
List<string> list = new List<string>();
IEnumerable<string> keys = p.Mods.Keys;
IEnumerable<string> second;
if (!p.IsMe)
{
IEnumerable<string> keys2 = mine.Keys;
second = keys2;
}
else
{
second = Enumerable.Empty<string>();
}
foreach (string item in from g in (from g in keys.Concat(second)
where g.StartsWith("com.helvetica.") && g != "com.helvetica.modnet"
select g).Distinct()
orderby DisplayName(g)
select g)
{
string value;
bool num = p.Mods.TryGetValue(item, out value);
mine.TryGetValue(item, out var value2);
if (!num)
{
list.Add("<color=#E0A040>no " + DisplayName(item) + "</color>");
}
else if (!p.IsMe && value2 != null && value2 != value)
{
list.Add("<color=#E0A040>" + DisplayName(item) + " " + value + " (you: " + value2 + ")</color>");
}
else
{
list.Add(DisplayName(item) + " " + value);
}
}
string value3;
string text2 = (p.Mods.TryGetValue("com.helvetica.modnet", out value3) ? value3 : "?");
b.AddLabel(text + " <color=#9A8F85>ModNet " + text2 + "</color>\n " + ((list.Count > 0) ? string.Join(", ", list) : "no other mods"));
}
private static string DisplayName(string guid)
{
if (!Chainloader.PluginInfos.TryGetValue(guid, out var value))
{
return Session.Short(guid);
}
return value.Metadata.Name;
}
private static void Notify(string message)
{
Plugin.ModLog.LogInfo((object)message);
NotificationManager instance = NotificationManager.instance;
if ((Object)(object)instance != (Object)null)
{
instance.DisplayNotification(message);
}
}
}
public static class ModNetApi
{
public const string Guid = "com.helvetica.modnet";
public const int Protocol = 1;
public const int MaxPayload = 1000;
public static bool Ready => Session.IsReady;
public static bool IsMultiplayer => Session.IsMultiplayer;
public static int MyOwnerId => Session.MyOwnerId;
public static IReadOnlyList<PlayerInfo> Players => Session.Players;
public static event Action BecameReady;
public static event Action<PlayerInfo> PlayerHello;
public static event Action<int> PlayerLeft;
public static void Register(string channel, Action<NetMessage> handler)
{
Transport.Register(channel, handler);
}
public static bool Send(string channel, string payload, Character subject = null)
{
return Transport.Send(channel, payload, subject);
}
public static PlayerInfo GetPlayer(int ownerId)
{
return Session.Players.FirstOrDefault((PlayerInfo p) => p.OwnerId == ownerId);
}
public static bool EveryoneHas(string pluginGuid, string version = null)
{
if (!Session.IsMultiplayer)
{
return true;
}
string value;
if (Session.Players.Count > 0)
{
return Session.Players.All((PlayerInfo p) => p.Mods.TryGetValue(pluginGuid, out value) && (version == null || value == version));
}
return false;
}
internal static void RaiseReady()
{
Invoke(ModNetApi.BecameReady, delegate(Action h)
{
h();
});
}
internal static void RaiseHello(PlayerInfo p)
{
Invoke(ModNetApi.PlayerHello, delegate(Action<PlayerInfo> h)
{
h(p);
});
}
internal static void RaiseLeft(int ownerId)
{
Invoke(ModNetApi.PlayerLeft, delegate(Action<int> h)
{
h(ownerId);
});
}
private static void Invoke<T>(T evt, Action<T> call) where T : Delegate
{
if (evt == null)
{
return;
}
Delegate[] invocationList = evt.GetInvocationList();
foreach (Delegate obj in invocationList)
{
try
{
call((T)obj);
}
catch (Exception arg)
{
Plugin.ModLog.LogError((object)$"ModNet event handler {obj.Method.DeclaringType?.Name}.{obj.Method.Name} failed: {arg}");
}
}
}
}
public sealed class NetMessage
{
public string Channel { get; internal set; }
public string Payload { get; internal set; }
public Character Character { get; internal set; }
public int SenderOwnerId { get; internal set; }
public PlayerInfo Sender => ModNetApi.GetPlayer(SenderOwnerId);
}
public sealed class PlayerInfo
{
internal Dictionary<string, string> ModsInternal = new Dictionary<string, string>();
public int OwnerId { get; internal set; }
public string Name { get; internal set; }
public bool IsMe { get; internal set; }
public bool HasModNet { get; internal set; }
public ulong SteamId { get; internal set; }
public IReadOnlyDictionary<string, string> Mods => ModsInternal;
public bool Has(string pluginGuid, string version = null)
{
if (ModsInternal.TryGetValue(pluginGuid, out var value))
{
if (version != null)
{
return value == version;
}
return true;
}
return false;
}
}
[BepInPlugin("com.helvetica.modnet", "ModNet", "0.1.3")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : StolenRealmMod
{
public const string PluginGuid = "com.helvetica.modnet";
public const string PluginName = "ModNet";
public const string PluginVersion = "0.1.3";
internal static ManualLogSource ModLog;
internal static ConfigEntry<bool> Enabled;
internal static ConfigEntry<bool> LogTraffic;
internal static ConfigEntry<bool> TestTools;
protected override void OnModLoaded()
{
ModLog = ((BaseUnityPlugin)this).Logger;
Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Exchange messages with other modded players in multiplayer games (shared emotes, mod list, exact Steam nicknames...). Off: our mods behave as if nobody else had them.");
LogTraffic = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "LogTraffic", false, "Log every message sent and received (turn on when reporting a multiplayer problem).");
TestTools = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "TestTools", false, "Show the Ping and Payload size test buttons on this card.");
Session.Install();
if (Chainloader.PluginInfos.ContainsKey("com.helvetica.modsmenu"))
{
MenuCard.Register();
}
((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} v{1} loaded (protocol {2}).", "ModNet", "0.1.3", 1));
}
}
internal class Session : MonoBehaviour
{
private sealed class Tracked
{
public PlayerInfo Info;
public bool Connected;
public bool HelloRaised;
}
internal const string Channel = "modnet";
private const float SettleSeconds = 3f;
private static readonly List<PlayerInfo> PlayerList = new List<PlayerInfo>();
private static readonly Dictionary<int, Tracked> ById = new Dictionary<int, Tracked>();
private static readonly Random Rng = new Random();
private int _generation = int.MinValue;
private float _readySince = -1f;
private float _nextSync;
private float _replyAt = -1f;
private string _lastError;
private static readonly Dictionary<int, float> Pings = new Dictionary<int, float>();
private static int _nextPing;
private static Character _sender;
private static Session Instance;
internal static bool IsMultiplayer { get; private set; }
internal static bool IsReady { get; private set; }
internal static int MyOwnerId { get; private set; } = -1;
internal static IReadOnlyList<PlayerInfo> Players => PlayerList;
internal static Root Root
{
get
{
NetworkingManager instance = NetworkingManager.Instance;
if (!((Object)(object)instance != (Object)null) || instance.NetworkManager == null)
{
return null;
}
return ((Observer<Root>)(object)instance.NetworkManager).Root;
}
}
internal static void Install()
{
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
//IL_0010: Expected O, but got Unknown
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
GameObject val = new GameObject("ModNet");
Object.DontDestroyOnLoad((Object)val);
((Object)val).hideFlags = (HideFlags)61;
val.AddComponent<Session>();
Transport.Register("modnet", OnMessage);
}
private void Update()
{
try
{
Tick();
Transport.Flush();
}
catch (Exception ex)
{
if (ex.ToString() != _lastError)
{
_lastError = ex.ToString();
Plugin.ModLog.LogError((object)ex);
}
}
}
private void Tick()
{
NetworkingManager instance = NetworkingManager.Instance;
int num = (((Object)(object)instance != (Object)null) ? instance.SessionGeneration : int.MinValue);
bool flag = (Object)(object)instance != (Object)null && !instance.IsPlayingAsSinglePlayer && Root != null;
if (num != _generation || flag != IsMultiplayer)
{
_generation = num;
IsMultiplayer = flag;
Reset();
}
if (!flag)
{
return;
}
if (Time.unscaledTime >= _nextSync)
{
_nextSync = Time.unscaledTime + 1f;
SyncPlayers();
}
Character val = (_sender = FindSender());
if (val == null || !Plugin.Enabled.Value)
{
if (IsReady)
{
Plugin.ModLog.LogInfo((object)"ModNet: paused (no characters in the world, or turned off).");
}
IsReady = false;
_readySince = -1f;
return;
}
if (_readySince < 0f)
{
_readySince = Time.unscaledTime;
}
if (!IsReady && Time.unscaledTime - _readySince >= 3f)
{
IsReady = true;
MyOwnerId = val.OwnerID;
SyncPlayers();
Plugin.ModLog.LogInfo((object)(string.Format("ModNet ready: I am #{0}, {1}, ", MyOwnerId, instance.IsServer ? "host" : "client") + $"{PlayerList.Count} player(s). {LobbyInfo()}"));
SendHello(request: true);
ModNetApi.RaiseReady();
}
if (IsReady && _replyAt >= 0f && Time.unscaledTime >= _replyAt)
{
_replyAt = -1f;
SendHello(request: false);
}
}
private static void Reset()
{
_sender = null;
IsReady = false;
MyOwnerId = -1;
ById.Clear();
PlayerList.Clear();
Pings.Clear();
}
internal static Character SenderCharacter()
{
return _sender;
}
private static Character FindSender()
{
//IL_0021: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
//IL_0027: Unknown result type (might be due to invalid IL or missing references)
//IL_0029: Invalid comparison between Unknown and I4
//IL_002b: Unknown result type (might be due to invalid IL or missing references)
//IL_002e: Unknown result type (might be due to invalid IL or missing references)
//IL_0030: Invalid comparison between Unknown and I4
NetworkingManager instance = NetworkingManager.Instance;
GUIManager instance2 = GUIManager.instance;
if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null)
{
return null;
}
GUIState currentGuiState = instance2.CurrentGuiState;
if ((int)currentGuiState == 5 || (int)currentGuiState == 0 || (int)currentGuiState == 1)
{
return null;
}
List<Character> myCharacters = instance.MyCharacters;
List<Character> allPlayerCharacters = instance.AllPlayerCharacters;
if (myCharacters == null || allPlayerCharacters == null)
{
return null;
}
foreach (Character item in myCharacters)
{
if (item != null && (Object)(object)item.PlayerMovement != (Object)null && allPlayerCharacters.Contains(item))
{
return item;
}
}
return null;
}
internal static bool IsMine(Character c)
{
try
{
return c != null && c.Owned;
}
catch (Exception)
{
return false;
}
}
private static void SyncPlayers()
{
Root root = Root;
ObservableDictionary<int, string> val = ((root != null) ? root.ConnectionNameDict : null);
if (val == null)
{
return;
}
ObservableDictionary<int, bool> activeConnections = root.ActiveConnections;
HashSet<int> present = new HashSet<int>();
bool flag = default(bool);
string text = default(string);
foreach (int item in val.Keys.ToList())
{
if (activeConnections == null || !activeConnections.TryGetValue(item, ref flag) || flag)
{
present.Add(item);
Tracked tracked = Get(item);
tracked.Connected = true;
if (val.TryGetValue(item, ref text) && !string.IsNullOrEmpty(text))
{
tracked.Info.Name = text;
}
}
}
foreach (Tracked item2 in ById.Values.Where((Tracked t) => t.Connected && !present.Contains(t.Info.OwnerId)).ToList())
{
ById.Remove(item2.Info.OwnerId);
Plugin.ModLog.LogInfo((object)$"ModNet: {item2.Info.Name} (#{item2.Info.OwnerId}) left.");
ModNetApi.RaiseLeft(item2.Info.OwnerId);
}
if (MyOwnerId >= 0)
{
Tracked tracked2 = Get(MyOwnerId);
PlayerInfo info = tracked2.Info;
bool isMe = (tracked2.Info.HasModNet = true);
info.IsMe = isMe;
tracked2.Info.SteamId = MySteamId();
tracked2.Info.ModsInternal = LocalMods();
}
PlayerList.Clear();
PlayerList.AddRange(from t in ById.Values
where t.Connected || t.Info.HasModNet
select t.Info into p
orderby p.OwnerId
select p);
}
private static Tracked Get(int id)
{
if (!ById.TryGetValue(id, out var value))
{
Dictionary<int, Tracked> byId = ById;
int key = id;
Tracked obj = new Tracked
{
Info = new PlayerInfo
{
OwnerId = id,
Name = "Player #" + id
}
};
value = obj;
byId[key] = obj;
}
return value;
}
private static void SendHello(bool request)
{
IEnumerable<string> enumerable = from kv in LocalMods()
orderby (!kv.Key.StartsWith("com.helvetica.")) ? 1 : 0, kv.Key
select kv.Key + "=" + kv.Value;
string text = $"h|{(request ? 1 : 0)}|{MySteamId()}|";
List<string> list = new List<string>();
int num = text.Length;
foreach (string item in enumerable)
{
if (num + item.Length + 1 > 1000)
{
break;
}
list.Add(item);
num += item.Length + 1;
}
Transport.Send("modnet", text + string.Join(",", list), null, unlimited: true);
}
private void ScheduleReply()
{
if (_replyAt < 0f)
{
_replyAt = Time.unscaledTime + 0.2f + (float)Rng.NextDouble() * 0.8f;
}
}
private static void OnHello(NetMessage m, string[] f)
{
if (f.Length < 4)
{
return;
}
Tracked tracked = Get(m.SenderOwnerId);
tracked.Info.HasModNet = true;
ulong.TryParse(f[2], out var result);
tracked.Info.SteamId = result;
Dictionary<string, string> dictionary = new Dictionary<string, string>();
string[] array = f[3].Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string text in array)
{
int num = text.IndexOf('=');
if (num > 0)
{
dictionary[text.Substring(0, num)] = text.Substring(num + 1);
}
}
tracked.Info.ModsInternal = dictionary;
SyncPlayers();
if (f[1] == "1")
{
Instance?.ScheduleReply();
}
if (!tracked.HelloRaised)
{
tracked.HelloRaised = true;
Plugin.ModLog.LogInfo((object)($"ModNet: hello from {tracked.Info.Name} (#{tracked.Info.OwnerId}): " + string.Join(", ", from kv in dictionary
where kv.Key.StartsWith("com.helvetica.")
select Short(kv.Key) + " " + kv.Value)));
ModNetApi.RaiseHello(tracked.Info);
}
}
private void Awake()
{
Instance = this;
}
internal static Dictionary<string, string> LocalMods()
{
return (from i in Chainloader.PluginInfos.Values
where ((i != null) ? i.Metadata : null) != null
group i by i.Metadata.GUID).ToDictionary((IGrouping<string, PluginInfo> g) => g.Key, (IGrouping<string, PluginInfo> g) => g.First().Metadata.Version.ToString());
}
internal static string Short(string guid)
{
if (!guid.StartsWith("com.helvetica."))
{
return guid;
}
return guid.Substring("com.helvetica.".Length);
}
private static ulong MySteamId()
{
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
try
{
return SteamClient.IsValid ? SteamClient.SteamId.Value : 0;
}
catch (Exception)
{
return 0uL;
}
}
internal static string LobbyInfo()
{
//IL_0038: Unknown result type (might be due to invalid IL or missing references)
//IL_003d: Unknown result type (might be due to invalid IL or missing references)
try
{
SteamManager instance = SteamManager.instance;
Lobby? val = (((Object)(object)instance != (Object)null) ? instance.CurrentLobby : ((Lobby?)null));
object result;
if (!val.HasValue)
{
result = "Steam lobby: none.";
}
else
{
Lobby value = val.Value;
result = $"Steam lobby: {((Lobby)(ref value)).MemberCount} member(s).";
}
return (string)result;
}
catch (Exception ex)
{
return "Steam lobby: unknown (" + ex.GetType().Name + ").";
}
}
private static void OnMessage(NetMessage m)
{
string[] array = m.Payload.Split('|');
switch (array[0])
{
case "h":
OnHello(m, array);
break;
case "p":
Transport.Send("modnet", $"q|{array[1]}|{m.SenderOwnerId}", null);
break;
case "q":
{
if (array.Length >= 3 && array[2] == MyOwnerId.ToString() && int.TryParse(array[1], out var result) && Pings.TryGetValue(result, out var value))
{
Plugin.ModLog.LogInfo((object)$"ModNet ping: {Name(m)} answered in {(Time.unscaledTime - value) * 1000f:0} ms (round trip).");
}
break;
}
case "s":
{
if (array.Length >= 3 && int.TryParse(array[1], out var result2))
{
bool flag = array[2].Length == result2 && array[2].All((char ch) => ch == 'x');
Plugin.ModLog.LogInfo((object)string.Format("ModNet size test: {0} chars from {1} arrived {2}.", result2, Name(m), flag ? "intact" : $"DAMAGED ({array[2].Length} chars)"));
Transport.Send("modnet", $"t|{result2}|{m.SenderOwnerId}|{(flag ? 1 : 0)}", null);
}
break;
}
case "t":
if (array.Length >= 4 && array[2] == MyOwnerId.ToString())
{
Plugin.ModLog.LogInfo((object)("ModNet size test: " + Name(m) + " got " + array[1] + " chars " + ((array[3] == "1") ? "intact" : "DAMAGED") + "."));
}
break;
}
}
private static string Name(NetMessage m)
{
return m.Sender?.Name ?? ("#" + m.SenderOwnerId);
}
internal static string Ping()
{
if (!IsReady)
{
return "Not ready: join a multiplayer game first.";
}
int key = ++_nextPing;
Pings[key] = Time.unscaledTime;
if (!Transport.Send("modnet", "p|" + key, null))
{
return "Ping not sent (see the log).";
}
return "Ping sent; answers go to the BepInEx log.";
}
internal static string SizeTest()
{
if (!IsReady)
{
return "Not ready: join a multiplayer game first.";
}
int[] array = new int[5] { 250, 500, 1000, 2000, 4000 };
foreach (int num in array)
{
bool flag = Transport.Send("modnet", $"s|{num}|{new string('x', num)}", null, unlimited: true);
Plugin.ModLog.LogInfo((object)string.Format("ModNet size test: sent {0} chars: {1}.", num, flag ? "ok" : "FAILED locally"));
if (!flag)
{
break;
}
}
return "Size test sent (250 to 4000 chars); results go to the BepInEx log on every PC.";
}
}
internal static class Transport
{
private struct Pending
{
public string Channel;
public string Payload;
public Character Subject;
public bool Unlimited;
}
internal const string Prefix = "~srm";
internal static readonly string Marker = "~srm" + 1 + "|";
private const float PerSecond = 10f;
private const float BurstSize = 20f;
private static float _tokens = 20f;
private static float _lastRefill;
private static readonly Dictionary<string, List<Action<NetMessage>>> Handlers = new Dictionary<string, List<Action<NetMessage>>>(StringComparer.Ordinal);
private static readonly HashSet<string> Warned = new HashSet<string>();
private static bool _sending;
private static int _receiving;
private static readonly Queue<Pending> Deferred = new Queue<Pending>();
internal static int Sent;
internal static int Received;
internal static void Register(string channel, Action<NetMessage> handler)
{
if (!string.IsNullOrEmpty(channel) && handler != null)
{
if (channel.IndexOf('|') >= 0)
{
throw new ArgumentException("Channel names can't contain '|'.", "channel");
}
if (!Handlers.TryGetValue(channel, out var value))
{
value = (Handlers[channel] = new List<Action<NetMessage>>());
}
value.Add(handler);
}
}
internal static bool Send(string channel, string payload, Character subject, bool unlimited = false)
{
if (!Plugin.Enabled.Value || !Session.IsReady)
{
return false;
}
if (string.IsNullOrEmpty(channel) || channel.IndexOf('|') >= 0)
{
WarnOnce("bad channel name: " + channel);
return false;
}
if (payload == null)
{
payload = "";
}
if (!unlimited && payload.Length > 1000)
{
WarnOnce($"{channel}: payload of {payload.Length} chars is over the {1000} limit; not sent.");
return false;
}
if (_receiving > 0)
{
Deferred.Enqueue(new Pending
{
Channel = channel,
Payload = payload,
Subject = subject,
Unlimited = unlimited
});
return true;
}
return SendNow(channel, payload, subject, unlimited, queued: false);
}
internal static void Flush()
{
if (Deferred.Count == 0)
{
return;
}
if (!Plugin.Enabled.Value || !Session.IsReady)
{
Deferred.Clear();
return;
}
while (Deferred.Count > 0)
{
Pending pending = Deferred.Dequeue();
if (pending.Subject == null || (pending.Subject != null && Session.IsMine(pending.Subject)))
{
SendNow(pending.Channel, pending.Payload, pending.Subject, pending.Unlimited, queued: true);
}
}
}
private static bool SendNow(string channel, string payload, Character subject, bool unlimited, bool queued)
{
if (!unlimited && !TakeToken())
{
WarnOnce($"{channel}: sending too fast; some messages were dropped (limit {10f}/s).");
return false;
}
Character val = subject ?? Session.SenderCharacter();
if (val == null || !Session.IsMine(val))
{
WarnOnce(channel + ": the subject must be one of your characters.");
return false;
}
try
{
if ((Object)(object)val.PlayerMovement == (Object)null)
{
return false;
}
}
catch (Exception)
{
return false;
}
Root root = Session.Root;
if (root == null)
{
return false;
}
string text = Marker + channel + "|" + payload;
_sending = true;
try
{
root.SetAnimationTrigger(val, text);
}
catch (Exception arg)
{
Plugin.ModLog.LogError((object)$"ModNet: sending on '{channel}' failed: {arg}");
return false;
}
finally
{
_sending = false;
}
Sent++;
if (Plugin.LogTraffic.Value)
{
Plugin.ModLog.LogInfo((object)string.Format("ModNet → {0} ({1} chars{2}): {3}", channel, payload.Length, queued ? ", queued during a receive" : "", Preview(payload)));
}
return true;
}
internal static void Receive(Character character, string wire)
{
if (_sending)
{
return;
}
if (!wire.StartsWith(Marker, StringComparison.Ordinal))
{
WarnOnce("ignoring a message from another ModNet protocol version: " + Preview(wire));
return;
}
int num = wire.IndexOf('|', Marker.Length);
if (num < 0)
{
return;
}
string text = wire.Substring(Marker.Length, num - Marker.Length);
string text2 = wire.Substring(num + 1);
if (character == null)
{
WarnOnce(text + ": message arrived without a character; ignored.");
return;
}
int ownerID;
try
{
ownerID = character.OwnerID;
}
catch (Exception)
{
return;
}
if (ownerID == Session.MyOwnerId)
{
return;
}
Received++;
if (Plugin.LogTraffic.Value)
{
Plugin.ModLog.LogInfo((object)$"ModNet ← {text} from #{ownerID} ({text2.Length} chars): {Preview(text2)}");
}
if (!Plugin.Enabled.Value || !Handlers.TryGetValue(text, out var value))
{
return;
}
NetMessage obj = new NetMessage
{
Channel = text,
Payload = text2,
Character = character,
SenderOwnerId = ownerID
};
_receiving++;
try
{
Action<NetMessage>[] array = value.ToArray();
foreach (Action<NetMessage> action in array)
{
try
{
action(obj);
}
catch (Exception arg)
{
Plugin.ModLog.LogError((object)$"ModNet handler for '{text}' failed: {arg}");
}
}
}
finally
{
_receiving--;
}
}
private static bool TakeToken()
{
float unscaledTime = Time.unscaledTime;
_tokens = Mathf.Min(20f, _tokens + (unscaledTime - _lastRefill) * 10f);
_lastRefill = unscaledTime;
if (_tokens < 1f)
{
return false;
}
_tokens -= 1f;
return true;
}
private static string Preview(string s)
{
if (s.Length > 80)
{
return s.Substring(0, 77) + "...";
}
return s;
}
internal static void WarnOnce(string message)
{
if (Warned.Add(message))
{
Plugin.ModLog.LogWarning((object)("ModNet: " + message));
}
}
}
[HarmonyPatch(typeof(Root), "SetAnimationTrigger")]
internal static class CarrierPatch
{
private static bool Prefix(Character character, string triggerName)
{
if (triggerName == null || !triggerName.StartsWith("~srm", StringComparison.Ordinal))
{
return true;
}
try
{
Transport.Receive(character, triggerName);
}
catch (Exception arg)
{
Plugin.ModLog.LogError((object)$"ModNet: receive failed: {arg}");
}
return false;
}
}
}