Decompiled source of BreakoutNet v0.2.1

BreakoutNet.dll

Decompiled 2 weeks ago
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<TEvent>(Action<TEvent> handler) where TEvent : class, IBreakoutEvent
		{
			return Track(BreakoutEventRegistry.SubscribeScoped(modGuid, handler));
		}

		public BreakoutSubscription Subscribe<TEvent>(string eventName, Action<TEvent> handler) where TEvent : class, IBreakoutEvent
		{
			return Track(BreakoutEventRegistry.SubscribeNamed(modGuid, eventName, handler));
		}

		public void Publish<TEvent>(TEvent evt) where TEvent : class, IBreakoutEvent
		{
			BreakoutEventRegistry.PublishScoped(modGuid, evt);
		}

		public void Publish<TEvent>(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<BreakoutNetworkReadyEvent> handler)
		{
			return Track(BreakoutCoreHookRegistry.Subscribe(handler));
		}

		public BreakoutSubscription OnWorldLeft(Action<BreakoutWorldLeftEvent> handler)
		{
			return Track(BreakoutCoreHookRegistry.Subscribe(handler));
		}

		public BreakoutSubscription OnPeerJoined(Action<BreakoutPeerChangedEvent> handler)
		{
			return Track(BreakoutCoreHookRegistry.Subscribe("breakoutnet.core.peer.joined", handler));
		}

		public BreakoutSubscription OnPeerLeft(Action<BreakoutPeerChangedEvent> handler)
		{
			return Track(BreakoutCoreHookRegistry.Subscribe("breakoutnet.core.peer.left", handler));
		}

		public BreakoutSubscription OnRpcReceived(Action<BreakoutRpcObservedEvent> handler)
		{
			return Track(BreakoutCoreHookRegistry.Subscribe(handler));
		}

		public BreakoutSubscription OnRpcRejected(Action<BreakoutRpcRejectedEvent> 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<IBreakoutModule> modules = new List<IBreakoutModule>();

		private readonly List<BreakoutSubscription> subscriptions = new List<BreakoutSubscription>();

		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<Type> sharedModules = new List<Type>();

		private readonly List<Type> serverModules = new List<Type>();

		private readonly List<Type> clientModules = new List<Type>();

		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<TModule>() where TModule : IBreakoutModule, new()
		{
			sharedModules.Add(typeof(TModule));
			return this;
		}

		public BreakoutModBuilder AddServer<TModule>() where TModule : IBreakoutModule, new()
		{
			serverModules.Add(typeof(TModule));
			return this;
		}

		public BreakoutModBuilder AddClient<TModule>() 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<BreakoutModAppOwner>();
			if ((Object)(object)breakoutModAppOwner == (Object)null)
			{
				breakoutModAppOwner = ((Component)plugin).gameObject.AddComponent<BreakoutModAppOwner>();
			}
			breakoutModAppOwner.Add(breakoutModApp);
			return breakoutModApp;
		}

		private static void CreateModules(IEnumerable<Type> 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<ZNetPeer> EmptyPeers = new List<ZNetPeer>();

		public static ZNetPeer ServerPeer
		{
			get
			{
				if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer())
				{
					return null;
				}
				return ZNet.instance.GetServerPeer();
			}
		}

		public static IReadOnlyList<ZNetPeer> 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<TRequest>(string rpcName, BreakoutRpcHandler<TRequest> handler) where TRequest : IBreakoutSerializable, new()
			{
				BreakoutRpcRegistry.RegisterServer(rpcName, handler, BreakoutRpcRateLimit.Default);
			}

			public static void Register<TRequest>(string rpcName, BreakoutRpcHandler<TRequest> handler, BreakoutRpcRateLimit rateLimit) where TRequest : IBreakoutSerializable, new()
			{
				BreakoutRpcRegistry.RegisterServer(rpcName, handler, rateLimit);
			}

			public static bool SendToClient<TMessage>(long peerId, string rpcName, TMessage message, string senderModGuid = null) where TMessage : IBreakoutSerializable, new()
			{
				return BreakoutRpcRegistry.SendToPeer(peerId, rpcName, message, senderModGuid);
			}

			public static bool Broadcast<TMessage>(string rpcName, TMessage message, string senderModGuid = null) where TMessage : IBreakoutSerializable, new()
			{
				return BreakoutRpcRegistry.Broadcast(rpcName, message, senderModGuid);
			}

			public static bool BroadcastToAll<TMessage>(string rpcName, TMessage message, string senderModGuid = null) where TMessage : IBreakoutSerializable, new()
			{
				return Broadcast(rpcName, message, senderModGuid);
			}

			public static int BroadcastExcept<TMessage>(long senderPeerId, string rpcName, TMessage message, string senderModGuid = null) where TMessage : IBreakoutSerializable, new()
			{
				return BreakoutRpcRegistry.BroadcastExcept(senderPeerId, rpcName, message, senderModGuid);
			}

			public static int BroadcastNear<TMessage>(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<TMessage>(string rpcName, BreakoutRpcHandler<TMessage> handler) where TMessage : IBreakoutSerializable, new()
			{
				BreakoutRpcRegistry.RegisterClient(rpcName, handler);
			}

			public static bool SendToServer<TRequest>(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<TMessage>(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<TSettings>(string settingsName, Action<TSettings> applySettings) where TSettings : IBreakoutSerializable, new()
			{
				BreakoutSettingsSyncRegistry.RegisterClientSettings(settingsName, applySettings);
			}
		}

		public static void RegisterServerSettings<TSettings>(string settingsName, Func<TSettings> 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<TEvent>(Action<TEvent> handler) where TEvent : class, IBreakoutEvent
		{
			return BreakoutEventRegistry.SubscribeNamed("com.breakoutmods.valheim.breakoutnet", typeof(TEvent).FullName, handler);
		}

		public static BreakoutSubscription Subscribe<TEvent>(string hookName, Action<TEvent> 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<TEvent> : ISubscription where TEvent : class, IBreakoutEvent
		{
			private readonly string subscriberModGuid;

			private readonly string eventName;

			private readonly Action<TEvent> handler;

			public Subscription(string subscriberModGuid, string eventName, Action<TEvent> 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<EventKey, List<ISubscription>> Subscriptions = new Dictionary<EventKey, List<ISubscription>>();

		public static BreakoutSubscription SubscribeScoped<TEvent>(string modGuid, Action<TEvent> handler) where TEvent : class, IBreakoutEvent
		{
			if (handler == null)
			{
				throw new ArgumentNullException("handler");
			}
			EventKey key = EventKey.Scoped(modGuid, typeof(TEvent));
			return AddSubscription(key, new Subscription<TEvent>(modGuid, key.ToString(), handler));
		}

		public static BreakoutSubscription SubscribeNamed<TEvent>(string modGuid, string eventName, Action<TEvent> 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<TEvent>(modGuid, eventName, handler));
		}

		public static void PublishScoped<TEvent>(string modGuid, TEvent evt) where TEvent : class, IBreakoutEvent
		{
			Publish(EventKey.Scoped(modGuid, typeof(TEvent)), modGuid, evt);
		}

		public static void PublishNamed<TEvent>(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<ISubscription>();
				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<TEvent>(EventKey key, string publisherModGuid, TEvent evt) where TEvent : class, IBreakoutEvent
		{
			List<ISubscription> 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<TestEvent>("test.mod", delegate
			{
				count++;
			});
			BreakoutSubscription breakoutSubscription2 = SubscribeScoped<TestEvent>("test.mod", delegate
			{
				count++;
			});
			PublishScoped("test.mod", new TestEvent());
			breakoutSubscription.Dispose();
			breakoutSubscription2.Dispose();
			return count == 2;
		}

		internal static bool DisposedSubscriptionStopsDeliveryForTest()
		{
			int count = 0;
			SubscribeScoped<TestEvent>("test.mod", delegate
			{
				count++;
			}).Dispose();
			PublishScoped("test.mod", new TestEvent());
			return count == 0;
		}

		internal static bool SubscriberExceptionDoesNotStopDispatchForTest()
		{
			int count = 0;
			BreakoutSubscription breakoutSubscription = SubscribeScoped<TestEvent>("test.mod", delegate
			{
				throw new InvalidOperationException("test");
			});
			BreakoutSubscription breakoutSubscription2 = SubscribeScoped<TestEvent>("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<BreakoutModApp> apps = new List<BreakoutModApp>();

		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<BreakoutNetRunner>();
			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<long> knownPeers = new HashSet<long>();

		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<ZPackage>("BreakoutNet.RPC", (Action<long, ZPackage>)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<long> hashSet = new HashSet<long>();
			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<string, Bucket> buckets = new Dictionary<string, Bucket>();

		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<TMessage> : IHandler where TMessage : IBreakoutSerializable, new()
		{
			private readonly BreakoutRpcHandler<TMessage> handler;

			private readonly BreakoutRpcRateLimit rateLimit;

			public string MessageTypeName => typeof(TMessage).FullName;

			public BreakoutRpcRateLimit RateLimit => rateLimit;

			public Handler(BreakoutRpcHandler<TMessage> 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<string, IHandler> ServerHandlers = new Dictionary<string, IHandler>();

		private static readonly Dictionary<string, IHandler> ClientHandlers = new Dictionary<string, IHandler>();

		private static readonly BreakoutRateLimiter InboundClientLimiter = new BreakoutRateLimiter(12f, 8f);

		private static int sequence;

		public static void RegisterServer<TMessage>(string rpcName, BreakoutRpcHandler<TMessage> handler, BreakoutRpcRateLimit rateLimit) where TMessage : IBreakoutSerializable, new()
		{
			Register(ServerHandlers, rpcName, new Handler<TMessage>(handler, rateLimit));
			BreakoutLog.Info("Registered server RPC '{0}' for {1} with {2}.", rpcName, typeof(TMessage).FullName, DescribeRateLimit(rateLimit));
		}

		public static void RegisterClient<TMessage>(string rpcName, BreakoutRpcHandler<TMessage> handler) where TMessage : IBreakoutSerializable, new()
		{
			Register(ClientHandlers, rpcName, new Handler<TMessage>(handler, BreakoutRpcRateLimit.Unlimited));
			BreakoutLog.Info("Registered client RPC '{0}' for {1}.", rpcName, typeof(TMessage).FullName);
		}

		public static bool SendToServer<TMessage>(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<TMessage>(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<TMessage>(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<TMessage>(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<TMessage>(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<string, IHandler> 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<TMessage>(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<TMessage>(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<TMessage>(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<TMessage>(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<TMessage>(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<TSettings> : ISettingsProvider where TSettings : IBreakoutSerializable, new()
		{
			private readonly string settingsName;

			private readonly Func<TSettings> getSettings;

			public SettingsProvider(string settingsName, Func<TSettings> 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<string, ISettingsProvider> ServerProviders = new Dictionary<string, ISettingsProvider>();

		public static void RegisterServerSettings<TSettings>(string settingsName, Func<TSettings> 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<TSettings>(settingsName, getSettings);
			BreakoutLog.Info("Registered server settings sync '{0}'.", settingsName);
		}

		public static void RegisterClientSettings<TSettings>(string settingsName, Action<TSettings> 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();
		}
	}
}