using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Threading; using BepInEx; using BepInEx.Logging; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: AssemblyCompany("BreakoutMods")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("BreakoutMods Valheim client/server networking helper API.")] [assembly: AssemblyFileVersion("0.2.1.0")] [assembly: AssemblyInformationalVersion("0.2.1")] [assembly: AssemblyProduct("BreakoutNet")] [assembly: AssemblyTitle("BreakoutNet")] [assembly: AssemblyVersion("0.2.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 BreakoutMods.BreakoutNet { public sealed class BreakoutNetworkReadyEvent : IBreakoutEvent { public bool IsServer { get; } public bool IsClient { get; } public bool IsDedicatedServer { get; } public bool IsListenServer { get; } public BreakoutNetworkReadyEvent(bool isServer, bool isClient, bool isDedicatedServer, bool isListenServer) { IsServer = isServer; IsClient = isClient; IsDedicatedServer = isDedicatedServer; IsListenServer = isListenServer; } } public sealed class BreakoutWorldLeftEvent : IBreakoutEvent { } public sealed class BreakoutPeerChangedEvent : IBreakoutEvent { public long PeerId { get; } public string PlayerName { get; } public bool IsServerPeer { get; } public bool IsLocalPeer { get; } public BreakoutPeerChangedEvent(long peerId, string playerName, bool isServerPeer, bool isLocalPeer) { PeerId = peerId; PlayerName = playerName ?? string.Empty; IsServerPeer = isServerPeer; IsLocalPeer = isLocalPeer; } } public sealed class BreakoutRpcObservedEvent : IBreakoutEvent { public long SenderPeerId { get; } public bool IsFromServer { get; } public bool IsServerSide { get; } public string RpcName { get; } public string SenderModGuid { get; } public string MessageTypeName { get; } public int Sequence { get; } public BreakoutRpcObservedEvent(long senderPeerId, bool isFromServer, bool isServerSide, string rpcName, string senderModGuid, string messageTypeName, int sequence) { SenderPeerId = senderPeerId; IsFromServer = isFromServer; IsServerSide = isServerSide; RpcName = rpcName ?? string.Empty; SenderModGuid = senderModGuid ?? string.Empty; MessageTypeName = messageTypeName ?? string.Empty; Sequence = sequence; } } public sealed class BreakoutRpcRejectedEvent : IBreakoutEvent { public long SenderPeerId { get; } public string RpcName { get; } public string Reason { get; } public string Category { get; } public BreakoutRpcRejectedEvent(long senderPeerId, string rpcName, string reason, string category) { SenderPeerId = senderPeerId; RpcName = rpcName ?? string.Empty; Reason = reason ?? string.Empty; Category = category ?? "unknown"; } } public sealed class BreakoutEvents { private readonly string modGuid; private readonly BreakoutModApp owner; internal BreakoutEvents(string modGuid, BreakoutModApp owner) { this.modGuid = modGuid; this.owner = owner; } public BreakoutSubscription Subscribe(Action handler) where TEvent : class, IBreakoutEvent { return Track(BreakoutEventRegistry.SubscribeScoped(modGuid, handler)); } public BreakoutSubscription Subscribe(string eventName, Action handler) where TEvent : class, IBreakoutEvent { return Track(BreakoutEventRegistry.SubscribeNamed(modGuid, eventName, handler)); } public void Publish(TEvent evt) where TEvent : class, IBreakoutEvent { BreakoutEventRegistry.PublishScoped(modGuid, evt); } public void Publish(string eventName, TEvent evt) where TEvent : class, IBreakoutEvent { BreakoutEventRegistry.PublishNamed(modGuid, eventName, evt); } private BreakoutSubscription Track(BreakoutSubscription subscription) { if (owner != null) { owner.Track(subscription); } return subscription; } } public sealed class BreakoutHooks { private readonly BreakoutModApp owner; internal BreakoutHooks(string modGuid, BreakoutModApp owner) { this.owner = owner; } public BreakoutSubscription OnNetworkReady(Action handler) { return Track(BreakoutCoreHookRegistry.Subscribe(handler)); } public BreakoutSubscription OnWorldLeft(Action handler) { return Track(BreakoutCoreHookRegistry.Subscribe(handler)); } public BreakoutSubscription OnPeerJoined(Action handler) { return Track(BreakoutCoreHookRegistry.Subscribe("breakoutnet.core.peer.joined", handler)); } public BreakoutSubscription OnPeerLeft(Action handler) { return Track(BreakoutCoreHookRegistry.Subscribe("breakoutnet.core.peer.left", handler)); } public BreakoutSubscription OnRpcReceived(Action handler) { return Track(BreakoutCoreHookRegistry.Subscribe(handler)); } public BreakoutSubscription OnRpcRejected(Action handler) { return Track(BreakoutCoreHookRegistry.Subscribe(handler)); } private BreakoutSubscription Track(BreakoutSubscription subscription) { if (owner != null) { owner.Track(subscription); } return subscription; } } public sealed class BreakoutModApp : IDisposable { private readonly List modules = new List(); private readonly List subscriptions = new List(); private bool disposed; public BreakoutModuleContext Context { get; } public string ModGuid => Context.ModGuid; internal BreakoutModApp(string modGuid) { Context = new BreakoutModuleContext(modGuid, this); } internal void AddModule(IBreakoutModule module) { modules.Add(module); } internal void Track(BreakoutSubscription subscription) { if (subscription != null) { subscriptions.Add(subscription); } } public void Dispose() { if (disposed) { return; } disposed = true; BreakoutSubscription[] array = subscriptions.ToArray(); for (int i = 0; i < array.Length; i++) { array[i].Dispose(); } IBreakoutModule[] array2 = modules.ToArray(); for (int i = 0; i < array2.Length; i++) { if (array2[i] is IDisposable disposable) { disposable.Dispose(); } } subscriptions.Clear(); modules.Clear(); } } public sealed class BreakoutModBuilder { private readonly BaseUnityPlugin plugin; private readonly string modGuid; private readonly List sharedModules = new List(); private readonly List serverModules = new List(); private readonly List clientModules = new List(); internal BreakoutModBuilder(BaseUnityPlugin plugin, string modGuid) { if (string.IsNullOrWhiteSpace(modGuid)) { throw new ArgumentException("Mod GUID cannot be empty.", "modGuid"); } this.plugin = plugin; this.modGuid = modGuid; } public BreakoutModBuilder AddShared() where TModule : IBreakoutModule, new() { sharedModules.Add(typeof(TModule)); return this; } public BreakoutModBuilder AddServer() where TModule : IBreakoutModule, new() { serverModules.Add(typeof(TModule)); return this; } public BreakoutModBuilder AddClient() where TModule : IBreakoutModule, new() { clientModules.Add(typeof(TModule)); return this; } public BreakoutModApp Build() { BreakoutModApp breakoutModApp = new BreakoutModApp(modGuid); BreakoutModuleContext context = breakoutModApp.Context; CreateModules(sharedModules, context, breakoutModApp); CreateModules(serverModules, context, breakoutModApp); if (!Application.isBatchMode) { CreateModules(clientModules, context, breakoutModApp); } BreakoutModAppOwner breakoutModAppOwner = ((Component)plugin).gameObject.GetComponent(); if ((Object)(object)breakoutModAppOwner == (Object)null) { breakoutModAppOwner = ((Component)plugin).gameObject.AddComponent(); } breakoutModAppOwner.Add(breakoutModApp); return breakoutModApp; } private static void CreateModules(IEnumerable moduleTypes, BreakoutModuleContext context, BreakoutModApp app) { foreach (Type moduleType in moduleTypes) { IBreakoutModule breakoutModule = (IBreakoutModule)Activator.CreateInstance(moduleType); breakoutModule.Initialize(context); app.AddModule(breakoutModule); } } } public abstract class BreakoutModuleBase : IBreakoutModule { protected BreakoutModuleContext Context { get; private set; } public virtual void Initialize(BreakoutModuleContext context) { Context = context; } } public abstract class BreakoutSharedModule : BreakoutModuleBase { } public abstract class BreakoutServerModule : BreakoutModuleBase { } public abstract class BreakoutClientModule : BreakoutModuleBase { } public sealed class BreakoutModuleContext { public string ModGuid { get; } public BreakoutEvents Events { get; } public BreakoutHooks Hooks { get; } internal BreakoutModApp Owner { get; } internal BreakoutModuleContext(string modGuid, BreakoutModApp owner) { if (string.IsNullOrWhiteSpace(modGuid)) { throw new ArgumentException("Mod GUID cannot be empty.", "modGuid"); } ModGuid = modGuid; Owner = owner; Events = new BreakoutEvents(modGuid, owner); Hooks = new BreakoutHooks(modGuid, owner); } } public static class BreakoutNet { public static BreakoutModuleContext ForMod(string modGuid) { return new BreakoutModuleContext(modGuid, null); } public static BreakoutModBuilder ForPlugin(BaseUnityPlugin plugin, string modGuid) { if ((Object)(object)plugin == (Object)null) { throw new ArgumentNullException("plugin"); } return new BreakoutModBuilder(plugin, modGuid); } } public static class BreakoutPeers { private static readonly IReadOnlyList EmptyPeers = new List(); public static ZNetPeer ServerPeer { get { if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { return null; } return ZNet.instance.GetServerPeer(); } } public static IReadOnlyList ConnectedPeers { get { if ((Object)(object)ZNet.instance == (Object)null) { return EmptyPeers; } return ZNet.instance.GetConnectedPeers(); } } public static bool TryGetPeer(long peerId, out ZNetPeer peer) { peer = null; if ((Object)(object)ZNet.instance == (Object)null) { return false; } ZNetPeer serverPeer = ServerPeer; if (serverPeer != null && serverPeer.m_uid == peerId) { peer = serverPeer; return true; } foreach (ZNetPeer connectedPeer in ZNet.instance.GetConnectedPeers()) { if (connectedPeer != null && connectedPeer.m_uid == peerId) { peer = connectedPeer; return true; } } return false; } } public static class BreakoutRpc { public static class Server { public static void Register(string rpcName, BreakoutRpcHandler handler) where TRequest : IBreakoutSerializable, new() { BreakoutRpcRegistry.RegisterServer(rpcName, handler, BreakoutRpcRateLimit.Default); } public static void Register(string rpcName, BreakoutRpcHandler handler, BreakoutRpcRateLimit rateLimit) where TRequest : IBreakoutSerializable, new() { BreakoutRpcRegistry.RegisterServer(rpcName, handler, rateLimit); } public static bool SendToClient(long peerId, string rpcName, TMessage message, string senderModGuid = null) where TMessage : IBreakoutSerializable, new() { return BreakoutRpcRegistry.SendToPeer(peerId, rpcName, message, senderModGuid); } public static bool Broadcast(string rpcName, TMessage message, string senderModGuid = null) where TMessage : IBreakoutSerializable, new() { return BreakoutRpcRegistry.Broadcast(rpcName, message, senderModGuid); } public static bool BroadcastToAll(string rpcName, TMessage message, string senderModGuid = null) where TMessage : IBreakoutSerializable, new() { return Broadcast(rpcName, message, senderModGuid); } public static int BroadcastExcept(long senderPeerId, string rpcName, TMessage message, string senderModGuid = null) where TMessage : IBreakoutSerializable, new() { return BreakoutRpcRegistry.BroadcastExcept(senderPeerId, rpcName, message, senderModGuid); } public static int BroadcastNear(Vector3 position, float radius, string rpcName, TMessage message, string senderModGuid = null) where TMessage : IBreakoutSerializable, new() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return BreakoutRpcRegistry.BroadcastNear(position, radius, rpcName, message, senderModGuid); } } public static class Client { public static void Register(string rpcName, BreakoutRpcHandler handler) where TMessage : IBreakoutSerializable, new() { BreakoutRpcRegistry.RegisterClient(rpcName, handler); } public static bool SendToServer(string rpcName, TRequest request, string senderModGuid = null) where TRequest : IBreakoutSerializable, new() { return BreakoutRpcRegistry.SendToServer(rpcName, request, senderModGuid); } } } public sealed class BreakoutRpcContext { public long SenderPeerId { get; } public bool IsFromServer { get; } public bool IsServerSide { get; } public string RpcName { get; } public string SenderModGuid { get; } public int Sequence { get; } public bool IsRejected { get; private set; } public string RejectionReason { get; private set; } internal BreakoutRpcContext(long senderPeerId, bool isFromServer, bool isServerSide, string rpcName, string senderModGuid, int sequence) { SenderPeerId = senderPeerId; IsFromServer = isFromServer; IsServerSide = isServerSide; RpcName = rpcName; SenderModGuid = senderModGuid; Sequence = sequence; } public void Reject(string reason) { IsRejected = true; RejectionReason = (string.IsNullOrWhiteSpace(reason) ? "Rejected by handler." : reason); BreakoutLog.Warning("Rejected RPC '{0}' from peer {1}: {2}", RpcName, SenderPeerId, RejectionReason); } } public delegate void BreakoutRpcHandler(BreakoutRpcContext context, TMessage message) where TMessage : IBreakoutSerializable, new(); public sealed class BreakoutRpcRateLimit { private static readonly BreakoutRpcRateLimit DefaultPolicy = new BreakoutRpcRateLimit(enabled: true, 12f, 8f); private static readonly BreakoutRpcRateLimit UnlimitedPolicy = new BreakoutRpcRateLimit(enabled: false, 0f, 0f); public bool Enabled { get; private set; } public float Capacity { get; private set; } public float RefillPerSecond { get; private set; } public static BreakoutRpcRateLimit Default => DefaultPolicy; public static BreakoutRpcRateLimit Unlimited => UnlimitedPolicy; private BreakoutRpcRateLimit(bool enabled, float capacity, float refillPerSecond) { Enabled = enabled; Capacity = Math.Max(1f, capacity); RefillPerSecond = Math.Max(0f, refillPerSecond); } public static BreakoutRpcRateLimit TokenBucket(float capacity, float refillPerSecond) { return new BreakoutRpcRateLimit(enabled: true, capacity, refillPerSecond); } public static BreakoutRpcRateLimit ForMessagesPerSecond(float messagesPerSecond, float burstSeconds) { float num = Math.Max(1f, messagesPerSecond); float num2 = Math.Max(1f, burstSeconds); return TokenBucket(num * num2, num); } } public static class BreakoutSettingsSync { public static class Client { public static void Register(string settingsName, Action applySettings) where TSettings : IBreakoutSerializable, new() { BreakoutSettingsSyncRegistry.RegisterClientSettings(settingsName, applySettings); } } public static void RegisterServerSettings(string settingsName, Func getSettings) where TSettings : IBreakoutSerializable, new() { BreakoutSettingsSyncRegistry.RegisterServerSettings(settingsName, getSettings); } public static void BroadcastNow() { BreakoutSettingsSyncRegistry.BroadcastServerSettings(); } } public static class BreakoutSide { public static bool IsServer { get { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } } public static bool IsClient => !Application.isBatchMode; public static bool IsDedicatedServer { get { if (Application.isBatchMode) { return IsServer; } return false; } } public static bool IsListenServer { get { if (IsServer) { return !Application.isBatchMode; } return false; } } public static bool IsInWorld { get { if ((Object)(object)ZNet.instance != (Object)null) { return ZRoutedRpc.instance != null; } return false; } } } public sealed class BreakoutSubscription : IDisposable { private readonly Action unsubscribe; private bool disposed; public bool IsDisposed => disposed; internal BreakoutSubscription(Action unsubscribe) { this.unsubscribe = unsubscribe ?? ((Action)delegate { }); } public void Dispose() { if (!disposed) { disposed = true; unsubscribe(); } } } public interface IBreakoutEvent { } public interface IBreakoutModule { void Initialize(BreakoutModuleContext context); } public interface IBreakoutSerializable { void Write(ZPackage package); void Read(ZPackage package); } internal static class BreakoutCoreHookRegistry { public const string PeerJoinedHookName = "breakoutnet.core.peer.joined"; public const string PeerLeftHookName = "breakoutnet.core.peer.left"; private const string CoreModGuid = "com.breakoutmods.valheim.breakoutnet"; public static BreakoutSubscription Subscribe(Action handler) where TEvent : class, IBreakoutEvent { return BreakoutEventRegistry.SubscribeNamed("com.breakoutmods.valheim.breakoutnet", typeof(TEvent).FullName, handler); } public static BreakoutSubscription Subscribe(string hookName, Action handler) where TEvent : class, IBreakoutEvent { return BreakoutEventRegistry.SubscribeNamed("com.breakoutmods.valheim.breakoutnet", hookName, handler); } public static void PublishNetworkReady() { BreakoutEventRegistry.PublishNamed("com.breakoutmods.valheim.breakoutnet", typeof(BreakoutNetworkReadyEvent).FullName, new BreakoutNetworkReadyEvent(BreakoutSide.IsServer, BreakoutSide.IsClient, BreakoutSide.IsDedicatedServer, BreakoutSide.IsListenServer)); } public static void PublishWorldLeft() { BreakoutEventRegistry.PublishNamed("com.breakoutmods.valheim.breakoutnet", typeof(BreakoutWorldLeftEvent).FullName, new BreakoutWorldLeftEvent()); } public static void PublishPeerJoined(BreakoutPeerChangedEvent evt) { BreakoutEventRegistry.PublishNamed("com.breakoutmods.valheim.breakoutnet", "breakoutnet.core.peer.joined", evt); } public static void PublishPeerLeft(BreakoutPeerChangedEvent evt) { BreakoutEventRegistry.PublishNamed("com.breakoutmods.valheim.breakoutnet", "breakoutnet.core.peer.left", evt); } public static void PublishRpcReceived(BreakoutRpcObservedEvent evt) { BreakoutEventRegistry.PublishNamed("com.breakoutmods.valheim.breakoutnet", typeof(BreakoutRpcObservedEvent).FullName, evt); } public static void PublishRpcRejected(long senderPeerId, string rpcName, string reason, string category) { BreakoutEventRegistry.PublishNamed("com.breakoutmods.valheim.breakoutnet", typeof(BreakoutRpcRejectedEvent).FullName, new BreakoutRpcRejectedEvent(senderPeerId, rpcName, reason, category)); } } internal static class BreakoutEventRegistry { private interface ISubscription { void Invoke(object evt, string publisherModGuid); } private sealed class Subscription : ISubscription where TEvent : class, IBreakoutEvent { private readonly string subscriberModGuid; private readonly string eventName; private readonly Action handler; public Subscription(string subscriberModGuid, string eventName, Action handler) { this.subscriberModGuid = subscriberModGuid; this.eventName = eventName; this.handler = handler; } public void Invoke(object evt, string publisherModGuid) { if (!(evt is TEvent obj)) { BreakoutLog.Warning("Rejected event '{0}' for subscriber '{1}' because payload type '{2}' does not match '{3}'.", eventName, subscriberModGuid, (evt != null) ? evt.GetType().FullName : "null", typeof(TEvent).FullName); return; } try { handler(obj); } catch (Exception ex) { BreakoutLog.Error("Event '{0}' handler for subscriber '{1}' failed. Publisher='{2}', payload='{3}': {4}", eventName, subscriberModGuid, publisherModGuid, typeof(TEvent).FullName, ex); } } } private struct EventKey { private readonly string scope; private readonly string typeName; private EventKey(string scope, Type type) { this.scope = scope ?? string.Empty; typeName = type.FullName; } public static EventKey Scoped(string modGuid, Type type) { return new EventKey("scope:" + modGuid, type); } public static EventKey Named(string eventName, Type type) { return new EventKey("name:" + eventName, type); } public override int GetHashCode() { return (((scope != null) ? scope.GetHashCode() : 0) * 397) ^ ((typeName != null) ? typeName.GetHashCode() : 0); } public override bool Equals(object obj) { if (!(obj is EventKey eventKey)) { return false; } if (string.Equals(scope, eventKey.scope, StringComparison.Ordinal)) { return string.Equals(typeName, eventKey.typeName, StringComparison.Ordinal); } return false; } public override string ToString() { return scope + ":" + typeName; } } private sealed class TestEvent : IBreakoutEvent { } private static readonly Dictionary> Subscriptions = new Dictionary>(); public static BreakoutSubscription SubscribeScoped(string modGuid, Action handler) where TEvent : class, IBreakoutEvent { if (handler == null) { throw new ArgumentNullException("handler"); } EventKey key = EventKey.Scoped(modGuid, typeof(TEvent)); return AddSubscription(key, new Subscription(modGuid, key.ToString(), handler)); } public static BreakoutSubscription SubscribeNamed(string modGuid, string eventName, Action handler) where TEvent : class, IBreakoutEvent { if (string.IsNullOrWhiteSpace(eventName)) { throw new ArgumentException("Event name cannot be empty.", "eventName"); } if (handler == null) { throw new ArgumentNullException("handler"); } return AddSubscription(EventKey.Named(eventName, typeof(TEvent)), new Subscription(modGuid, eventName, handler)); } public static void PublishScoped(string modGuid, TEvent evt) where TEvent : class, IBreakoutEvent { Publish(EventKey.Scoped(modGuid, typeof(TEvent)), modGuid, evt); } public static void PublishNamed(string modGuid, string eventName, TEvent evt) where TEvent : class, IBreakoutEvent { if (string.IsNullOrWhiteSpace(eventName)) { throw new ArgumentException("Event name cannot be empty.", "eventName"); } Publish(EventKey.Named(eventName, typeof(TEvent)), modGuid, evt); } private static BreakoutSubscription AddSubscription(EventKey key, ISubscription subscription) { if (!Subscriptions.TryGetValue(key, out var value)) { value = new List(); Subscriptions[key] = value; } value.Add(subscription); return new BreakoutSubscription(delegate { RemoveSubscription(key, subscription); }); } private static void RemoveSubscription(EventKey key, ISubscription subscription) { if (Subscriptions.TryGetValue(key, out var value)) { value.Remove(subscription); if (value.Count == 0) { Subscriptions.Remove(key); } } } private static void Publish(EventKey key, string publisherModGuid, TEvent evt) where TEvent : class, IBreakoutEvent { List value; if (evt == null) { BreakoutLog.Warning("Ignored null event '{0}' from mod '{1}'.", key, publisherModGuid); } else if (Subscriptions.TryGetValue(key, out value)) { ISubscription[] array = value.ToArray(); for (int i = 0; i < array.Length; i++) { array[i].Invoke(evt, publisherModGuid); } } } internal static bool EventWithMultipleSubscribersIsDeliveredForTest() { int count = 0; BreakoutSubscription breakoutSubscription = SubscribeScoped("test.mod", delegate { count++; }); BreakoutSubscription breakoutSubscription2 = SubscribeScoped("test.mod", delegate { count++; }); PublishScoped("test.mod", new TestEvent()); breakoutSubscription.Dispose(); breakoutSubscription2.Dispose(); return count == 2; } internal static bool DisposedSubscriptionStopsDeliveryForTest() { int count = 0; SubscribeScoped("test.mod", delegate { count++; }).Dispose(); PublishScoped("test.mod", new TestEvent()); return count == 0; } internal static bool SubscriberExceptionDoesNotStopDispatchForTest() { int count = 0; BreakoutSubscription breakoutSubscription = SubscribeScoped("test.mod", delegate { throw new InvalidOperationException("test"); }); BreakoutSubscription breakoutSubscription2 = SubscribeScoped("test.mod", delegate { count++; }); PublishScoped("test.mod", new TestEvent()); breakoutSubscription.Dispose(); breakoutSubscription2.Dispose(); return count == 1; } } internal static class BreakoutLog { private static readonly BreakoutRateLimiter MalformedLogLimiter = new BreakoutRateLimiter(4f, 0.5f); internal static ManualLogSource Logger { get; set; } public static void Info(string message, params object[] args) { ManualLogSource logger = Logger; if (logger != null) { logger.LogInfo((object)Format(message, args)); } } public static void Warning(string message, params object[] args) { ManualLogSource logger = Logger; if (logger != null) { logger.LogWarning((object)Format(message, args)); } } public static void Error(string message, params object[] args) { ManualLogSource logger = Logger; if (logger != null) { logger.LogError((object)Format(message, args)); } } public static void Malformed(long peerId, string message, params object[] args) { string key = "malformed:" + peerId; if (MalformedLogLimiter.Allow(key)) { Warning(message, args); } } private static string Format(string message, object[] args) { if (args != null && args.Length != 0) { return string.Format(message, args); } return message; } } internal sealed class BreakoutModAppOwner : MonoBehaviour { private readonly List apps = new List(); public void Add(BreakoutModApp app) { if (app != null && !apps.Contains(app)) { apps.Add(app); } } private void OnDestroy() { BreakoutModApp[] array = apps.ToArray(); for (int i = 0; i < array.Length; i++) { array[i].Dispose(); } apps.Clear(); } } [BepInPlugin("com.breakoutmods.valheim.breakoutnet", "BreakoutNet", "0.2.1")] public sealed class BreakoutNetPlugin : BaseUnityPlugin { public const string PluginGuid = "com.breakoutmods.valheim.breakoutnet"; public const string PluginName = "BreakoutNet"; public const string PluginVersion = "0.2.1"; private GameObject runnerObject; private void Awake() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown BreakoutLog.Logger = ((BaseUnityPlugin)this).Logger; runnerObject = new GameObject("BreakoutNet"); Object.DontDestroyOnLoad((Object)(object)runnerObject); runnerObject.AddComponent(); BreakoutLog.Info("{0} {1} loaded.", "BreakoutNet", "0.2.1"); } private void OnDestroy() { if ((Object)(object)runnerObject != (Object)null) { Object.Destroy((Object)(object)runnerObject); runnerObject = null; } } } internal sealed class BreakoutNetRunner : MonoBehaviour { internal const string RoutedRpcName = "BreakoutNet.RPC"; private ZRoutedRpc registeredInstance; private float nextSettingsBroadcast; private bool broadcastedThisSession; private readonly HashSet knownPeers = new HashSet(); private void Update() { if (ZRoutedRpc.instance == null) { if (registeredInstance != null) { BreakoutCoreHookRegistry.PublishWorldLeft(); knownPeers.Clear(); } registeredInstance = null; broadcastedThisSession = false; return; } if (registeredInstance != ZRoutedRpc.instance) { registeredInstance = ZRoutedRpc.instance; registeredInstance.Register("BreakoutNet.RPC", (Action)OnRoutedRpc); broadcastedThisSession = false; BreakoutLog.Info("Registered routed RPC endpoint '{0}'.", "BreakoutNet.RPC"); BreakoutCoreHookRegistry.PublishNetworkReady(); } UpdatePeerHooks(); if (BreakoutSide.IsServer) { if (!broadcastedThisSession) { BreakoutSettingsSyncRegistry.BroadcastServerSettings(); broadcastedThisSession = true; nextSettingsBroadcast = Time.time + 10f; } if (Time.time >= nextSettingsBroadcast) { BreakoutSettingsSyncRegistry.BroadcastServerSettings(); nextSettingsBroadcast = Time.time + 10f; } } } private static void OnRoutedRpc(long senderPeerId, ZPackage package) { BreakoutRpcRegistry.Dispatch(senderPeerId, package); } private void UpdatePeerHooks() { if ((Object)(object)ZNet.instance == (Object)null) { return; } HashSet hashSet = new HashSet(); foreach (ZNetPeer connectedPeer in ZNet.instance.GetConnectedPeers()) { if (connectedPeer != null) { hashSet.Add(connectedPeer.m_uid); if (!knownPeers.Contains(connectedPeer.m_uid)) { BreakoutCoreHookRegistry.PublishPeerJoined(CreatePeerEvent(connectedPeer, isServerPeer: false)); } } } ZNetPeer val = ((!ZNet.instance.IsServer()) ? ZNet.instance.GetServerPeer() : null); if (val != null) { hashSet.Add(val.m_uid); if (!knownPeers.Contains(val.m_uid)) { BreakoutCoreHookRegistry.PublishPeerJoined(CreatePeerEvent(val, isServerPeer: true)); } } foreach (long knownPeer in knownPeers) { if (!hashSet.Contains(knownPeer)) { BreakoutCoreHookRegistry.PublishPeerLeft(new BreakoutPeerChangedEvent(knownPeer, string.Empty, isServerPeer: false, knownPeer == ZNet.GetUID())); } } knownPeers.Clear(); foreach (long item in hashSet) { knownPeers.Add(item); } } private static BreakoutPeerChangedEvent CreatePeerEvent(ZNetPeer peer, bool isServerPeer) { string empty = string.Empty; try { empty = peer.m_playerName; } catch { empty = string.Empty; } return new BreakoutPeerChangedEvent(peer.m_uid, empty, isServerPeer, peer.m_uid == ZNet.GetUID()); } } internal sealed class BreakoutRateLimiter { private struct Bucket { public float Tokens; public float LastUpdate; } private readonly Dictionary buckets = new Dictionary(); private readonly float capacity; private readonly float refillPerSecond; public BreakoutRateLimiter(float capacity, float refillPerSecond) { this.capacity = Math.Max(1f, capacity); this.refillPerSecond = Math.Max(0f, refillPerSecond); } public bool Allow(string key) { return Allow(key, Time.realtimeSinceStartup); } internal bool Allow(string key, float now) { return Allow(key, now, capacity, refillPerSecond); } internal bool Allow(string key, float now, float bucketCapacity, float bucketRefillPerSecond) { if (string.IsNullOrWhiteSpace(key)) { key = "unknown"; } bucketCapacity = Math.Max(1f, bucketCapacity); bucketRefillPerSecond = Math.Max(0f, bucketRefillPerSecond); if (!buckets.TryGetValue(key, out var value)) { Bucket bucket = default(Bucket); bucket.Tokens = bucketCapacity; bucket.LastUpdate = now; value = bucket; } float num = Math.Max(0f, now - value.LastUpdate); value.Tokens = Math.Min(bucketCapacity, value.Tokens + num * bucketRefillPerSecond); value.LastUpdate = now; if (value.Tokens < 1f) { buckets[key] = value; return false; } value.Tokens -= 1f; buckets[key] = value; return true; } } internal sealed class BreakoutRpcEnvelope { public const int CurrentProtocolVersion = 1; public int ProtocolVersion { get; private set; } public string RpcName { get; private set; } public string SenderModGuid { get; private set; } public string MessageTypeName { get; private set; } public int Sequence { get; private set; } public static void WriteHeader(ZPackage package, string rpcName, string senderModGuid, string messageTypeName, int sequence) { package.Write(1); package.Write(rpcName ?? string.Empty); package.Write(string.IsNullOrWhiteSpace(senderModGuid) ? "com.breakoutmods.valheim.breakoutnet" : senderModGuid); package.Write(messageTypeName ?? string.Empty); package.Write(sequence); } public static bool TryReadHeader(ZPackage package, out BreakoutRpcEnvelope envelope, out string reason) { envelope = null; reason = null; if (package == null) { reason = "Package is null."; return false; } try { envelope = new BreakoutRpcEnvelope { ProtocolVersion = package.ReadInt(), RpcName = package.ReadString(), SenderModGuid = package.ReadString(), MessageTypeName = package.ReadString(), Sequence = package.ReadInt() }; } catch (Exception ex) { reason = "Malformed BreakoutNet envelope: " + ex.Message; return false; } if (envelope.ProtocolVersion != 1) { reason = "Unsupported protocol version " + envelope.ProtocolVersion + "."; return false; } if (string.IsNullOrWhiteSpace(envelope.RpcName)) { reason = "RPC name is empty."; return false; } if (string.IsNullOrWhiteSpace(envelope.MessageTypeName)) { reason = "Message type name is empty."; return false; } return true; } } internal static class BreakoutRpcRegistry { private interface IHandler { string MessageTypeName { get; } BreakoutRpcRateLimit RateLimit { get; } void Invoke(BreakoutRpcContext context, ZPackage package); } private sealed class Handler : IHandler where TMessage : IBreakoutSerializable, new() { private readonly BreakoutRpcHandler handler; private readonly BreakoutRpcRateLimit rateLimit; public string MessageTypeName => typeof(TMessage).FullName; public BreakoutRpcRateLimit RateLimit => rateLimit; public Handler(BreakoutRpcHandler handler, BreakoutRpcRateLimit rateLimit) { this.handler = handler ?? throw new ArgumentNullException("handler"); this.rateLimit = rateLimit ?? BreakoutRpcRateLimit.Default; } public void Invoke(BreakoutRpcContext context, ZPackage package) { try { TMessage message = new TMessage(); message.Read(package); handler(context, message); } catch (Exception ex) { BreakoutLog.Error("RPC '{0}' handler failed for peer {1}: {2}", context.RpcName, context.SenderPeerId, ex); } } } private static readonly Dictionary ServerHandlers = new Dictionary(); private static readonly Dictionary ClientHandlers = new Dictionary(); private static readonly BreakoutRateLimiter InboundClientLimiter = new BreakoutRateLimiter(12f, 8f); private static int sequence; public static void RegisterServer(string rpcName, BreakoutRpcHandler handler, BreakoutRpcRateLimit rateLimit) where TMessage : IBreakoutSerializable, new() { Register(ServerHandlers, rpcName, new Handler(handler, rateLimit)); BreakoutLog.Info("Registered server RPC '{0}' for {1} with {2}.", rpcName, typeof(TMessage).FullName, DescribeRateLimit(rateLimit)); } public static void RegisterClient(string rpcName, BreakoutRpcHandler handler) where TMessage : IBreakoutSerializable, new() { Register(ClientHandlers, rpcName, new Handler(handler, BreakoutRpcRateLimit.Unlimited)); BreakoutLog.Info("Registered client RPC '{0}' for {1}.", rpcName, typeof(TMessage).FullName); } public static bool SendToServer(string rpcName, TMessage message, string senderModGuid) where TMessage : IBreakoutSerializable, new() { if (!CanSend(rpcName, message)) { return false; } if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) { return DispatchLocalAsServer(rpcName, message, senderModGuid); } ZNetPeer serverPeer = BreakoutPeers.ServerPeer; if (serverPeer == null) { BreakoutLog.Warning("Cannot send RPC '{0}' to server because no server peer is available.", rpcName); return false; } SendPackage(serverPeer.m_uid, rpcName, message, senderModGuid); return true; } public static bool SendToPeer(long peerId, string rpcName, TMessage message, string senderModGuid) where TMessage : IBreakoutSerializable, new() { if (!BreakoutSide.IsServer) { BreakoutLog.Warning("Cannot send server RPC '{0}' to peer {1}; this side is not server-authoritative.", rpcName, peerId); return false; } if (!CanSend(rpcName, message)) { return false; } if (BreakoutSide.IsListenServer && peerId == ZNet.GetUID()) { return DispatchLocalAsClient(rpcName, message, senderModGuid); } SendPackage(peerId, rpcName, message, senderModGuid); return true; } public static bool Broadcast(string rpcName, TMessage message, string senderModGuid) where TMessage : IBreakoutSerializable, new() { if (!BreakoutSide.IsServer) { BreakoutLog.Warning("Cannot broadcast RPC '{0}'; this side is not server-authoritative.", rpcName); return false; } if (!CanSend(rpcName, message)) { return false; } foreach (ZNetPeer connectedPeer in BreakoutPeers.ConnectedPeers) { if (connectedPeer != null) { SendPackage(connectedPeer.m_uid, rpcName, message, senderModGuid); } } if (BreakoutSide.IsListenServer) { DispatchLocalAsClient(rpcName, message, senderModGuid); } return true; } public static int BroadcastExcept(long senderPeerId, string rpcName, TMessage message, string senderModGuid) where TMessage : IBreakoutSerializable, new() { if (!BreakoutSide.IsServer || !CanSend(rpcName, message)) { return 0; } int num = 0; foreach (ZNetPeer connectedPeer in BreakoutPeers.ConnectedPeers) { if (connectedPeer != null && connectedPeer.m_uid != senderPeerId) { SendPackage(connectedPeer.m_uid, rpcName, message, senderModGuid); num++; } } if (BreakoutSide.IsListenServer && senderPeerId != ZNet.GetUID() && DispatchLocalAsClient(rpcName, message, senderModGuid)) { num++; } return num; } public static int BroadcastNear(Vector3 position, float radius, string rpcName, TMessage message, string senderModGuid) where TMessage : IBreakoutSerializable, new() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: 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_00a5: 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) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) if (!BreakoutSide.IsServer || !CanSend(rpcName, message)) { return 0; } float num = Mathf.Max(0f, radius) * Mathf.Max(0f, radius); int num2 = 0; Vector3 val; foreach (ZNetPeer connectedPeer in BreakoutPeers.ConnectedPeers) { if (connectedPeer != null) { val = connectedPeer.GetRefPos() - position; if (!(((Vector3)(ref val)).sqrMagnitude > num)) { SendPackage(connectedPeer.m_uid, rpcName, message, senderModGuid); num2++; } } } if (BreakoutSide.IsListenServer && (Object)(object)Player.m_localPlayer != (Object)null) { val = ((Component)Player.m_localPlayer).transform.position - position; if (((Vector3)(ref val)).sqrMagnitude <= num && DispatchLocalAsClient(rpcName, message, senderModGuid)) { num2++; } } return num2; } public static void Dispatch(long senderPeerId, ZPackage package) { bool isServer = BreakoutSide.IsServer; bool isFromServer = IsSenderServer(senderPeerId); DispatchToHandlers(senderPeerId, package, isServer, isFromServer); } internal static bool TryCreateDispatchPlanForTest(bool isServerSide, bool isFromServer, string rpcName, out string reason) { reason = null; if (string.IsNullOrWhiteSpace(rpcName)) { reason = "RPC name is empty."; return false; } if (!isServerSide && !isFromServer) { reason = "Client-side RPC must come from server."; return false; } return true; } private static void DispatchToHandlers(long senderPeerId, ZPackage package, bool isServerSide, bool isFromServer) { if (!BreakoutRpcEnvelope.TryReadHeader(package, out var envelope, out var reason)) { BreakoutLog.Malformed(senderPeerId, "Rejected malformed BreakoutNet packet from peer {0}: {1}", senderPeerId, reason); BreakoutCoreHookRegistry.PublishRpcRejected(senderPeerId, string.Empty, reason, "malformed"); return; } BreakoutCoreHookRegistry.PublishRpcReceived(new BreakoutRpcObservedEvent(senderPeerId, isFromServer, isServerSide, envelope.RpcName, envelope.SenderModGuid, envelope.MessageTypeName, envelope.Sequence)); if (!(isServerSide ? ServerHandlers : ClientHandlers).TryGetValue(envelope.RpcName, out var value)) { BreakoutLog.Malformed(senderPeerId, "Rejected unregistered RPC '{0}' from peer {1}.", envelope.RpcName, senderPeerId); BreakoutCoreHookRegistry.PublishRpcRejected(senderPeerId, envelope.RpcName, "Unregistered RPC.", "unregistered"); return; } if (!isServerSide && !isFromServer) { BreakoutLog.Malformed(senderPeerId, "Rejected client-side RPC '{0}' from non-server peer {1}.", envelope.RpcName, senderPeerId); BreakoutCoreHookRegistry.PublishRpcRejected(senderPeerId, envelope.RpcName, "Client-side RPC came from a non-server peer.", "unauthorized"); return; } if (!string.Equals(value.MessageTypeName, envelope.MessageTypeName, StringComparison.Ordinal)) { BreakoutLog.Malformed(senderPeerId, "Rejected RPC '{0}' from peer {1}; expected message type '{2}' but received '{3}'.", envelope.RpcName, senderPeerId, value.MessageTypeName, envelope.MessageTypeName); BreakoutCoreHookRegistry.PublishRpcRejected(senderPeerId, envelope.RpcName, "Message type mismatch.", "type-mismatch"); return; } if (isServerSide && !isFromServer) { BreakoutRpcRateLimit breakoutRpcRateLimit = value.RateLimit ?? BreakoutRpcRateLimit.Default; if (breakoutRpcRateLimit.Enabled) { string key = senderPeerId + ":" + envelope.RpcName; if (!InboundClientLimiter.Allow(key, Time.realtimeSinceStartup, breakoutRpcRateLimit.Capacity, breakoutRpcRateLimit.RefillPerSecond)) { BreakoutLog.Malformed(senderPeerId, "Rate-limited inbound RPC '{0}' from peer {1}; capacity={2:0.##}, refill={3:0.##}/s.", envelope.RpcName, senderPeerId, breakoutRpcRateLimit.Capacity, breakoutRpcRateLimit.RefillPerSecond); BreakoutCoreHookRegistry.PublishRpcRejected(senderPeerId, envelope.RpcName, "Rate-limited inbound RPC.", "rate-limit"); return; } } } BreakoutRpcContext context = new BreakoutRpcContext(senderPeerId, isFromServer, isServerSide, envelope.RpcName, envelope.SenderModGuid, envelope.Sequence); value.Invoke(context, package); } private static void Register(Dictionary handlers, string rpcName, IHandler handler) { if (string.IsNullOrWhiteSpace(rpcName)) { throw new ArgumentException("RPC name cannot be empty.", "rpcName"); } if (handler == null) { throw new ArgumentNullException("handler"); } handlers[rpcName] = handler; } private static bool CanSend(string rpcName, TMessage message) where TMessage : IBreakoutSerializable, new() { if (ZRoutedRpc.instance == null) { BreakoutLog.Warning("Cannot send RPC '{0}' because ZRoutedRpc is not ready.", rpcName); return false; } if (string.IsNullOrWhiteSpace(rpcName)) { BreakoutLog.Warning("Cannot send RPC with an empty name."); return false; } if (message == null) { BreakoutLog.Warning("Cannot send RPC '{0}' because message is null.", rpcName); return false; } return true; } private static void SendPackage(long targetPeerId, string rpcName, TMessage message, string senderModGuid) where TMessage : IBreakoutSerializable, new() { ZPackage val = CreatePackage(rpcName, message, senderModGuid); ZRoutedRpc.instance.InvokeRoutedRPC(targetPeerId, "BreakoutNet.RPC", new object[1] { val }); } private static bool DispatchLocalAsServer(string rpcName, TMessage message, string senderModGuid) where TMessage : IBreakoutSerializable, new() { ZPackage package = CreatePackage(rpcName, message, senderModGuid); Dispatch(((Object)(object)ZNet.instance != (Object)null) ? ZNet.GetUID() : 0, package); return true; } private static bool DispatchLocalAsClient(string rpcName, TMessage message, string senderModGuid) where TMessage : IBreakoutSerializable, new() { ZPackage package = CreatePackage(rpcName, message, senderModGuid); DispatchToHandlers(((Object)(object)ZNet.instance != (Object)null) ? ZNet.GetUID() : 0, package, isServerSide: false, isFromServer: true); return true; } private static ZPackage CreatePackage(string rpcName, TMessage message, string senderModGuid) where TMessage : IBreakoutSerializable, new() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); int num = Interlocked.Increment(ref sequence); BreakoutRpcEnvelope.WriteHeader(val, rpcName, senderModGuid, typeof(TMessage).FullName, num); message.Write(val); val.SetPos(0); return val; } private static string DescribeRateLimit(BreakoutRpcRateLimit rateLimit) { rateLimit = rateLimit ?? BreakoutRpcRateLimit.Default; if (!rateLimit.Enabled) { return "no inbound client rate limit"; } return $"inbound client rate limit capacity={rateLimit.Capacity:0.##}, refill={rateLimit.RefillPerSecond:0.##}/s"; } private static bool IsSenderServer(long senderPeerId) { if ((Object)(object)ZNet.instance == (Object)null) { return false; } if (ZNet.instance.IsServer()) { return senderPeerId == ZNet.GetUID(); } ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer != null) { return serverPeer.m_uid == senderPeerId; } return false; } } internal static class BreakoutSettingsSyncRegistry { private interface ISettingsProvider { void Broadcast(); } private sealed class SettingsProvider : ISettingsProvider where TSettings : IBreakoutSerializable, new() { private readonly string settingsName; private readonly Func getSettings; public SettingsProvider(string settingsName, Func getSettings) { this.settingsName = settingsName; this.getSettings = getSettings; } public void Broadcast() { try { TSettings val = getSettings(); if (val == null) { BreakoutLog.Warning("Settings sync '{0}' returned null and was not broadcast.", settingsName); } else { BreakoutRpc.Server.Broadcast(BuildRpcName(settingsName), val, "com.breakoutmods.valheim.breakoutnet"); } } catch (Exception ex) { BreakoutLog.Error("Settings sync '{0}' failed: {1}", settingsName, ex); } } } public const float BroadcastIntervalSeconds = 10f; private const string RpcPrefix = "breakoutnet.settings."; private static readonly Dictionary ServerProviders = new Dictionary(); public static void RegisterServerSettings(string settingsName, Func getSettings) where TSettings : IBreakoutSerializable, new() { if (string.IsNullOrWhiteSpace(settingsName)) { throw new ArgumentException("Settings name cannot be empty.", "settingsName"); } if (getSettings == null) { throw new ArgumentNullException("getSettings"); } ServerProviders[settingsName] = new SettingsProvider(settingsName, getSettings); BreakoutLog.Info("Registered server settings sync '{0}'.", settingsName); } public static void RegisterClientSettings(string settingsName, Action applySettings) where TSettings : IBreakoutSerializable, new() { if (string.IsNullOrWhiteSpace(settingsName)) { throw new ArgumentException("Settings name cannot be empty.", "settingsName"); } if (applySettings == null) { throw new ArgumentNullException("applySettings"); } BreakoutRpc.Client.Register(BuildRpcName(settingsName), delegate(BreakoutRpcContext context, TSettings settings) { if (!context.IsFromServer) { context.Reject("Settings sync packets are only accepted from the server."); } else { applySettings(settings); } }); BreakoutLog.Info("Registered client settings sync '{0}'.", settingsName); } public static void BroadcastServerSettings() { if (!BreakoutSide.IsServer || ZRoutedRpc.instance == null) { return; } foreach (ISettingsProvider value in ServerProviders.Values) { value.Broadcast(); } } private static string BuildRpcName(string settingsName) { return "breakoutnet.settings." + settingsName; } } internal static class BreakoutValidationHarness { private sealed class EmptyMessage : IBreakoutSerializable { public void Write(ZPackage package) { } public void Read(ZPackage package) { } } public static bool UnknownProtocolVersionFails() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001b: 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_003b: 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) //IL_0052: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(999); val.Write("test.rpc"); val.Write("com.breakoutmods.valheim.breakoutnet"); val.Write(typeof(EmptyMessage).FullName); val.Write(1); val.SetPos(0); if (!BreakoutRpcEnvelope.TryReadHeader(val, out var _, out var reason)) { return reason.Contains("Unsupported protocol version"); } return false; } public static bool MalformedPackageFails() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: 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_001c: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(1); val.SetPos(0); BreakoutRpcEnvelope envelope; string reason; return !BreakoutRpcEnvelope.TryReadHeader(val, out envelope, out reason); } public static bool ClientSideNonServerMessageFails() { string reason; return !BreakoutRpcRegistry.TryCreateDispatchPlanForTest(isServerSide: false, isFromServer: false, "test.rpc", out reason); } public static bool RateLimiterDropsExcessiveMessages() { BreakoutRateLimiter breakoutRateLimiter = new BreakoutRateLimiter(1f, 0f); if (breakoutRateLimiter.Allow("peer:rpc", 0f)) { return !breakoutRateLimiter.Allow("peer:rpc", 0.01f); } return false; } public static bool RateLimiterUsesCustomPolicy() { BreakoutRateLimiter breakoutRateLimiter = new BreakoutRateLimiter(1f, 0f); BreakoutRpcRateLimit breakoutRpcRateLimit = BreakoutRpcRateLimit.ForMessagesPerSecond(60f, 3f); if (breakoutRateLimiter.Allow("peer:voice", 0f, breakoutRpcRateLimit.Capacity, breakoutRpcRateLimit.RefillPerSecond)) { return breakoutRateLimiter.Allow("peer:voice", 0.01f, breakoutRpcRateLimit.Capacity, breakoutRpcRateLimit.RefillPerSecond); } return false; } public static bool EventWithMultipleSubscribersIsDelivered() { return BreakoutEventRegistry.EventWithMultipleSubscribersIsDeliveredForTest(); } public static bool DisposedSubscriptionStopsDelivery() { return BreakoutEventRegistry.DisposedSubscriptionStopsDeliveryForTest(); } public static bool SubscriberExceptionDoesNotStopDispatch() { return BreakoutEventRegistry.SubscriberExceptionDoesNotStopDispatchForTest(); } } }