Decompiled source of BetterNetworking Valheim v1.2.0

plugins\BetterNetworking_Valheim\DIT.BetterNetworking10.dll

Decompiled 2 days ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using Costura;
using HarmonyLib;
using Ionic.Zlib;
using Microsoft.CodeAnalysis;
using PlayFab.Party;
using Steamworks;
using UnityEngine;
using ZstdSharp;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyMetadata("PluginVersion", "1.2.0")]
[assembly: AssemblyCompany("DIT.BetterNetworking10")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.2.0.0")]
[assembly: AssemblyInformationalVersion("1.2.0+b695dc4fc4ae2241d59884838224eee3c1046cbc")]
[assembly: AssemblyProduct("DIT.BetterNetworking10")]
[assembly: AssemblyTitle("DIT.BetterNetworking10")]
[assembly: AssemblyVersion("1.2.0.0")]
[module: RefSafetyRules(11)]
internal class <Module>
{
	static <Module>()
	{
		AssemblyLoader.Attach();
	}
}
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace DIT.BetterNetworking10
{
	[BepInPlugin("DIT.BetterNetworking10", "Better Networking 1.0 Safe", "1.2.0")]
	[BepInIncompatibility("CW_Jesse.BetterNetworking")]
	[BepInIncompatibility("org.bepinex.plugins.network")]
	[BepInIncompatibility("be.sebastienvercammen.valheim.netcompression")]
	[BepInIncompatibility("com.github.dalayeth.Networkfix")]
	[BepInIncompatibility("Steel.ValheimMod")]
	public sealed class BetterNetworking10 : BaseUnityPlugin
	{
		public const string PluginGuid = "DIT.BetterNetworking10";

		public const string PluginName = "Better Networking 1.0 Safe";

		public const string PluginVersion = "1.2.0";

		internal static ConfigEntry<CompatibilityMode> Mode;

		internal static ConfigEntry<bool> EnableCompression;

		internal static ConfigEntry<bool> EnableQueueSize;

		internal static ConfigEntry<bool> EnableUpdateRate;

		internal static ConfigEntry<bool> EnableSendRate;

		internal static ConfigEntry<bool> EnableConnectionBuffer;

		internal static ConfigEntry<bool> EnablePortalGhostFix;

		internal static ConfigEntry<QueueSizeOption> QueueSize;

		internal static ConfigEntry<UpdateRateOption> UpdateRate;

		internal static ConfigEntry<SendRateOption> MinimumSendRate;

		internal static ConfigEntry<SendRateOption> MaximumSendRate;

		internal static ConfigEntry<ForceCrossplayOption> ForceCrossplay;

		internal static ConfigEntry<int> PlayerLimit;

		private Harmony _harmony;

		internal static BetterNetworking10 Instance { get; private set; }

		internal static BNLog Log { get; private set; }

		internal static bool SafeMode => Mode.Value == CompatibilityMode.VanillaSafe;

		internal static bool CompressionActive => EnableCompression.Value;

		internal static bool QueueActive => !SafeMode && EnableQueueSize.Value && QueueSize.Value != QueueSizeOption.Vanilla;

		internal static bool UpdateRateActive => !SafeMode && EnableUpdateRate.Value && UpdateRate.Value != UpdateRateOption.Percent100;

		internal static bool SendRateActive => !SafeMode && EnableSendRate.Value;

		internal static bool ConnectionBufferActive => !SafeMode && EnableConnectionBuffer.Value;

		internal static bool PortalGhostFixActive => EnablePortalGhostFix.Value;

		internal static int QueueBudgetBytes => QueueSize.Value switch
		{
			QueueSizeOption.KB80 => 81920, 
			QueueSizeOption.KB64 => 65536, 
			QueueSizeOption.KB48 => 49152, 
			QueueSizeOption.KB32 => 32768, 
			_ => 10240, 
		};

		internal static float UpdateRateMultiplier => UpdateRate.Value switch
		{
			UpdateRateOption.Percent75 => 0.75f, 
			UpdateRateOption.Percent50 => 0.5f, 
			_ => 1f, 
		};

		internal static bool IsDedicated => ((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.IsDedicated() : Application.isBatchMode;

		private void Awake()
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Expected O, but got Unknown
			Instance = this;
			bool flag = !File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath);
			BindConfig();
			Log = new BNLog(((BaseUnityPlugin)this).Logger, ((BaseUnityPlugin)this).Config);
			if (flag)
			{
				LegacyConfigMigration.TryImport(((BaseUnityPlugin)this).Config, Log);
			}
			GameVersion val = default(GameVersion);
			((GameVersion)(ref val))..ctor(1, 0, 12);
			GameVersion val2 = default(GameVersion);
			((GameVersion)(ref val2))..ctor(1, 0, 15);
			if (Version.CurrentVersion < val || Version.CurrentVersion > val2)
			{
				Log.Warning($"This build supports the audited Valheim range {val}-{val2} with network version 40; running {Version.CurrentVersion} / network {40u}. Check PATCH FAILED messages and retest networking after game updates.");
			}
			else
			{
				Log.Message($"Valheim compatibility verified: {Version.CurrentVersion}, network version {40u}");
			}
			_harmony = new Harmony("DIT.BetterNetworking10");
			PatchInstaller.Install(_harmony);
			PortalGhostRefresh.Initialize();
		}

		private void Update()
		{
			PortalGhostRefresh.Tick();
		}

		private void OnDestroy()
		{
			CompressionState.Clear();
			ConnectionBufferPatch.Clear();
			PortalGhostRefresh.Clear();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void BindConfig()
		{
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b9: Expected O, but got Unknown
			Mode = ((BaseUnityPlugin)this).Config.Bind<CompatibilityMode>("00 - Compatibility", "Mode", CompatibilityMode.Balanced, "Balanced: the enabled feature switches below are applied. VanillaSafe: only compression is allowed; ZDO scheduling, queue, and Steam send-rate changes are disabled.");
			EnableCompression = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - Features", "Compression", true, "Compress traffic only when both peers run this compatible build.");
			EnableQueueSize = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - Features", "Queue Size", true, "Changes the ZDO send budget directly. Disabled in VanillaSafe mode.");
			EnableUpdateRate = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - Features", "Update Rate", true, "Allows a lower ZDO update frequency. Disabled in VanillaSafe mode.");
			EnableSendRate = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - Features", "Steam Send Rate", true, "Changes Steamworks send-rate limits. Has no effect with PlayFab/crossplay. Disabled in VanillaSafe mode.");
			EnableConnectionBuffer = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - Features", "New Connection ZDO Buffer", false, "Experimental. Buffers early ZDOData packets per connection until ZDOMan registers the peer. OFF by default because stale player/ZDO state is being diagnosed.");
			EnablePortalGhostFix = ((BaseUnityPlugin)this).Config.Bind<bool>("01 - Features", "Portal Ghost Fix", false, "Optional server-side portal observer refresh. Re-sends a teleported player's ZDO and related networked status-effect ZDOs; vanilla clients are supported. Disable the standalone Sfantul mod when using this.");
			QueueSize = ((BaseUnityPlugin)this).Config.Bind<QueueSizeOption>("02 - Networking", "Queue Size", QueueSizeOption.KB32, "ZDO send budget based on Valheim's reported queue size. Vanilla leaves the game's 10 KB budget unchanged.");
			UpdateRate = ((BaseUnityPlugin)this).Config.Bind<UpdateRateOption>("02 - Networking", "Update Rate", UpdateRateOption.Percent100, "100 = vanilla 20 Hz timer; 75 = 15 Hz; 50 = 10 Hz.");
			MinimumSendRate = ((BaseUnityPlugin)this).Config.Bind<SendRateOption>("03 - Steamworks", "Minimum Send Rate", SendRateOption.KB256, "Steamworks minimum send rate in KB/s. Keep this below the real upload capacity.");
			MaximumSendRate = ((BaseUnityPlugin)this).Config.Bind<SendRateOption>("03 - Steamworks", "Maximum Send Rate", SendRateOption.KB1024, "Steamworks maximum send rate in KB/s.");
			ForceCrossplay = ((BaseUnityPlugin)this).Config.Bind<ForceCrossplayOption>("04 - Dedicated Server", "Force Crossplay", ForceCrossplayOption.Vanilla, "Vanilla follows the server command line; PlayFab forces crossplay; Steamworks disables crossplay.");
			PlayerLimit = ((BaseUnityPlugin)this).Config.Bind<int>("04 - Dedicated Server", "Player Limit", 10, new ConfigDescription("Dedicated server player limit. Requires restart.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 127), Array.Empty<object>()));
		}

		internal static int RateBytesPerSecond(SendRateOption option)
		{
			return (int)option * 1024;
		}
	}
	public enum CompatibilityMode
	{
		Balanced,
		VanillaSafe
	}
	public enum QueueSizeOption
	{
		Vanilla = 10,
		KB32 = 32,
		KB48 = 48,
		KB64 = 64,
		KB80 = 80
	}
	public enum UpdateRateOption
	{
		Percent100 = 100,
		Percent75 = 75,
		Percent50 = 50
	}
	public enum SendRateOption
	{
		KB150 = 150,
		KB256 = 256,
		KB512 = 512,
		KB768 = 768,
		KB1024 = 1024
	}
	public enum ForceCrossplayOption
	{
		Vanilla,
		PlayFab,
		Steamworks
	}
	internal static class PatchInstaller
	{
		private static int _applied;

		private static int _skipped;

		private static int _failed;

		internal static void Install(Harmony harmony)
		{
			if (BetterNetworking10.CompressionActive)
			{
				try
				{
					CompressionCodec.Initialize();
					CompressionFrame.SelfTest();
					InstallCompression(harmony);
				}
				catch (Exception ex)
				{
					Skip("Compression", "self-test failed: " + ex.GetType().Name + ": " + ex.Message);
					BetterNetworking10.Log.Error("Compression was left disabled because its startup self-test failed");
				}
			}
			else
			{
				Skip("Compression", "disabled by configuration");
			}
			if (BetterNetworking10.QueueActive)
			{
				Patch(harmony, "ZDO queue budget", AccessTools.DeclaredMethod(typeof(ZDOMan), "SendZDOs", (Type[])null, (Type[])null), null, null, "Transpiler", typeof(QueueBudgetPatch));
			}
			else
			{
				Skip("ZDO queue budget", BetterNetworking10.SafeMode ? "VanillaSafe mode" : "vanilla/disabled setting");
			}
			if (BetterNetworking10.UpdateRateActive)
			{
				Patch(harmony, "ZDO update rate", AccessTools.DeclaredMethod(typeof(ZDOMan), "SendZDOToPeers2", (Type[])null, (Type[])null), "Prefix", null, null, typeof(UpdateRatePatch));
			}
			else
			{
				Skip("ZDO update rate", BetterNetworking10.SafeMode ? "VanillaSafe mode" : "100%/disabled setting");
			}
			if (BetterNetworking10.SendRateActive)
			{
				Patch(harmony, "Steam send rate", AccessTools.DeclaredMethod(typeof(ZSteamSocket), "RegisterGlobalCallbacks", (Type[])null, (Type[])null), null, "Postfix", null, typeof(SteamSendRatePatch));
			}
			else
			{
				Skip("Steam send rate", BetterNetworking10.SafeMode ? "VanillaSafe mode" : "disabled by configuration");
			}
			if (BetterNetworking10.ConnectionBufferActive)
			{
				InstallConnectionBuffer(harmony);
			}
			else
			{
				Skip("New connection ZDO buffer", BetterNetworking10.SafeMode ? "VanillaSafe mode" : "disabled by default/configuration");
			}
			InstallDedicatedTweaks(harmony);
			BetterNetworking10.Log.Message($"Patch audit complete: {_applied} applied, {_skipped} skipped, {_failed} failed. Mode={BetterNetworking10.Mode.Value}");
		}

		private static void InstallCompression(Harmony harmony)
		{
			Patch(harmony, "Compression connect", AccessTools.DeclaredMethod(typeof(ZNet), "OnNewConnection", (Type[])null, (Type[])null), null, "OnConnectPostfix", null, typeof(CompressionPatch));
			Patch(harmony, "Compression disconnect pre", AccessTools.DeclaredMethod(typeof(ZNet), "Disconnect", (Type[])null, (Type[])null), "OnDisconnectPrefix", null, null, typeof(CompressionPatch));
			Patch(harmony, "Compression disconnect post", AccessTools.DeclaredMethod(typeof(ZNet), "Disconnect", (Type[])null, (Type[])null), null, "OnDisconnectPostfix", null, typeof(CompressionPatch));
			Patch(harmony, "Steam compress-once send", AccessTools.DeclaredMethod(typeof(ZSteamSocket), "Send", new Type[1] { typeof(ZPackage) }, (Type[])null), "SendPrefix", null, null, typeof(SteamCompressionPatch));
			Patch(harmony, "Steam receive", AccessTools.DeclaredMethod(typeof(ZSteamSocket), "Recv", (Type[])null, (Type[])null), null, "ReceivePostfix", null, typeof(SteamCompressionPatch));
			Patch(harmony, "PlayFab socket ctor()", AccessTools.DeclaredConstructor(typeof(ZPlayFabSocket), Type.EmptyTypes, false), null, "SocketOpenPostfix", null, typeof(PlayFabCompressionPatch));
			Patch(harmony, "PlayFab socket ctor(string,callback)", AccessTools.DeclaredConstructor(typeof(ZPlayFabSocket), new Type[2]
			{
				typeof(string),
				typeof(Action<PlayFabMatchmakingServerData>)
			}, false), null, "SocketOpenPostfix", null, typeof(PlayFabCompressionPatch));
			Patch(harmony, "PlayFab socket ctor(player)", AccessTools.DeclaredConstructor(typeof(ZPlayFabSocket), new Type[1] { typeof(PlayFabPlayer) }, false), null, "SocketOpenPostfix", null, typeof(PlayFabCompressionPatch));
			Patch(harmony, "PlayFab socket dispose", AccessTools.DeclaredMethod(typeof(ZPlayFabSocket), "Dispose", (Type[])null, (Type[])null), null, "SocketClosePostfix", null, typeof(PlayFabCompressionPatch));
			Patch(harmony, "PlayFab compress", AccessTools.DeclaredMethod(typeof(PlayFabZLibWorkQueue), "DoCompress", (Type[])null, (Type[])null), "CompressPrefix", null, null, typeof(PlayFabCompressionPatch));
			Patch(harmony, "PlayFab decompress", AccessTools.DeclaredMethod(typeof(PlayFabZLibWorkQueue), "DoUncompress", (Type[])null, (Type[])null), "DecompressPrefix", null, null, typeof(PlayFabCompressionPatch));
			Patch(harmony, "PlayFab direct compress", AccessTools.DeclaredMethod(typeof(PlayFabZLibWorkQueue), "CompressOnThisThread", (Type[])null, (Type[])null), "DirectCompressPrefix", null, null, typeof(PlayFabCompressionPatch));
			Patch(harmony, "PlayFab direct decompress", AccessTools.DeclaredMethod(typeof(PlayFabZLibWorkQueue), "UncompressOnThisThread", (Type[])null, (Type[])null), "DirectDecompressPrefix", null, null, typeof(PlayFabCompressionPatch));
		}

		private static void InstallConnectionBuffer(Harmony harmony)
		{
			Patch(harmony, "Connection buffer start", AccessTools.DeclaredMethod(typeof(ZNet), "OnNewConnection", (Type[])null, (Type[])null), null, "OnNewConnectionPostfix", null, typeof(ConnectionBufferPatch));
			Patch(harmony, "Connection buffer replay", AccessTools.DeclaredMethod(typeof(ZDOMan), "AddPeer", (Type[])null, (Type[])null), null, "AddPeerPostfix", null, typeof(ConnectionBufferPatch));
			Patch(harmony, "Connection buffer clear", AccessTools.DeclaredMethod(typeof(ZNet), "Shutdown", (Type[])null, (Type[])null), null, "ShutdownPostfix", null, typeof(ConnectionBufferPatch));
		}

		private static void InstallDedicatedTweaks(Harmony harmony)
		{
			if (BetterNetworking10.ForceCrossplay.Value != ForceCrossplayOption.Vanilla)
			{
				Patch(harmony, "Dedicated force crossplay", AccessTools.DeclaredMethod(typeof(FejdStartup), "ParseServerArguments", (Type[])null, (Type[])null), null, "ParseArgumentsPostfix", null, typeof(DedicatedPatch));
			}
			else
			{
				Skip("Dedicated force crossplay", "vanilla setting");
			}
			if (BetterNetworking10.PlayerLimit.Value != 10)
			{
				Patch(harmony, "Dedicated player limit", AccessTools.DeclaredMethod(typeof(ZNet), "RPC_PeerInfo", (Type[])null, (Type[])null), null, null, "PlayerLimitTranspiler", typeof(DedicatedPatch));
			}
			else
			{
				Skip("Dedicated player limit", "vanilla value 10");
			}
		}

		private static void Patch(Harmony harmony, string label, MethodBase target, string prefix = null, string postfix = null, string transpiler = null, Type patchType = null)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			if (target == null)
			{
				_failed++;
				BetterNetworking10.Log.Error("PATCH FAILED " + label + ": target method not found; feature left vanilla");
				return;
			}
			try
			{
				HarmonyMethod val = ((prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(AccessTools.DeclaredMethod(patchType, prefix, (Type[])null, (Type[])null)));
				HarmonyMethod val2 = ((postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(AccessTools.DeclaredMethod(patchType, postfix, (Type[])null, (Type[])null)));
				HarmonyMethod val3 = ((transpiler == null) ? ((HarmonyMethod)null) : new HarmonyMethod(AccessTools.DeclaredMethod(patchType, transpiler, (Type[])null, (Type[])null)));
				if ((prefix != null && val.method == null) || (postfix != null && val2.method == null) || (transpiler != null && val3.method == null))
				{
					throw new MissingMethodException("patch method not found");
				}
				harmony.Patch(target, val, val2, val3, (HarmonyMethod)null, (HarmonyMethod)null);
				_applied++;
				BetterNetworking10.Log.Message("PATCH OK " + label + ": " + target.DeclaringType?.Name + "." + target.Name);
			}
			catch (Exception ex)
			{
				_failed++;
				BetterNetworking10.Log.Error("PATCH FAILED " + label + ": " + ex.GetType().Name + ": " + ex.Message + "; feature left vanilla");
			}
		}

		private static void Skip(string label, string reason)
		{
			_skipped++;
			BetterNetworking10.Log.Message("PATCH SKIP " + label + ": " + reason);
		}
	}
	internal sealed class BNLog
	{
		internal enum LogLevel
		{
			Warning,
			Message,
			Info
		}

		private readonly ManualLogSource _logger;

		private readonly ConfigEntry<LogLevel> _level;

		internal bool IsInfo => _level.Value >= LogLevel.Info;

		internal BNLog(ManualLogSource logger, ConfigFile config)
		{
			_logger = logger;
			_level = config.Bind<LogLevel>("99 - Logging", "Log Level", LogLevel.Message, "Warning, Message, or Info (verbose packet compression logging). ");
		}

		internal void Error(object value)
		{
			_logger.LogError(value);
		}

		internal void Warning(object value)
		{
			_logger.LogWarning(value);
		}

		internal void Message(object value)
		{
			if (_level.Value >= LogLevel.Message)
			{
				_logger.LogMessage(value);
			}
		}

		internal void Info(object value)
		{
			if (_level.Value >= LogLevel.Info)
			{
				_logger.LogInfo(value);
			}
		}
	}
	internal static class CompressionCodec
	{
		private const string DictionaryResource = "DIT.BetterNetworking10.dict.small";

		private static Compressor _compressor;

		private static Decompressor _decompressor;

		private static readonly object Sync = new object();

		internal static void Initialize()
		{
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Expected O, but got Unknown
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Expected O, but got Unknown
			using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("DIT.BetterNetworking10.dict.small");
			if (stream == null)
			{
				throw new InvalidOperationException("Embedded Zstd dictionary is missing");
			}
			byte[] array = new byte[stream.Length];
			int num;
			for (int i = 0; i < array.Length; i += num)
			{
				num = stream.Read(array, i, array.Length - i);
				if (num == 0)
				{
					throw new EndOfStreamException("Could not read embedded Zstd dictionary");
				}
			}
			_compressor = new Compressor(1);
			_compressor.LoadDictionary(array);
			_decompressor = new Decompressor();
			_decompressor.LoadDictionary(array);
		}

		internal static byte[] Compress(byte[] input)
		{
			lock (Sync)
			{
				return _compressor.Wrap((ReadOnlySpan<byte>)input).ToArray();
			}
		}

		internal static byte[] Decompress(byte[] input)
		{
			lock (Sync)
			{
				return _decompressor.Unwrap((ReadOnlySpan<byte>)input, int.MaxValue).ToArray();
			}
		}
	}
	internal static class CompressionFrame
	{
		private static readonly byte[] Magic = new byte[4] { 66, 78, 49, 48 };

		private const int HeaderSize = 5;

		private const byte RawPayload = 0;

		private const byte CompressedPayload = 1;

		internal static byte[] Encode(byte[] input)
		{
			return Encode(input, diagnostics: true);
		}

		private static byte[] Encode(byte[] input, bool diagnostics)
		{
			if (HasHeader(input))
			{
				if (diagnostics)
				{
					BetterNetworking10.Log.Warning("Prevented duplicate Better Networking compression frame");
				}
				return input;
			}
			byte[] array = CompressionCodec.Compress(input);
			bool flag = array.Length < input.Length;
			byte[] array2 = (flag ? array : input);
			byte[] array3 = new byte[5 + array2.Length];
			Buffer.BlockCopy(Magic, 0, array3, 0, Magic.Length);
			array3[Magic.Length] = (byte)(flag ? 1 : 0);
			Buffer.BlockCopy(array2, 0, array3, 5, array2.Length);
			if (diagnostics && BetterNetworking10.Log.IsInfo && input.Length > 256)
			{
				if (flag)
				{
					BetterNetworking10.Log.Info($"Compression sent {input.Length} B -> {array3.Length} B ({100f * (float)array3.Length / (float)input.Length:0}%, framed)");
				}
				else
				{
					BetterNetworking10.Log.Info($"Compression bypassed for {input.Length} B packet because it would grow ({array3.Length} B framed)");
				}
			}
			return array3;
		}

		internal static bool TryDecode(byte[] input, out byte[] output)
		{
			output = null;
			if (!HasHeader(input))
			{
				return false;
			}
			int num = input.Length - 5;
			byte[] array = new byte[num];
			Buffer.BlockCopy(input, 5, array, 0, num);
			byte b = input[Magic.Length];
			switch (b)
			{
			case 0:
				output = array;
				break;
			case 1:
				output = CompressionCodec.Decompress(array);
				break;
			default:
				throw new InvalidDataException($"Unknown Better Networking frame type {b}");
			}
			if (BetterNetworking10.Log.IsInfo && output.Length > 256)
			{
				BetterNetworking10.Log.Info($"Compression received {input.Length} B -> {output.Length} B (framed)");
			}
			return true;
		}

		internal static bool HasHeader(byte[] input)
		{
			if (input == null || input.Length < 5)
			{
				return false;
			}
			for (int i = 0; i < Magic.Length; i++)
			{
				if (input[i] != Magic[i])
				{
					return false;
				}
			}
			return true;
		}

		internal static void SelfTest()
		{
			byte[] array = Enumerable.Repeat((byte)90, 1024).ToArray();
			byte[] array2 = Encode(array);
			if (!TryDecode(array2, out var output) || !array.SequenceEqual(output))
			{
				throw new InvalidDataException("Compression frame round-trip failed");
			}
			if (array2 != Encode(array2, diagnostics: false))
			{
				throw new InvalidDataException("Compression frame duplicate guard failed");
			}
		}
	}
	internal static class CompressionState
	{
		internal sealed class PeerState
		{
			internal int Version;

			internal bool Enabled;

			internal volatile bool Sending;

			internal volatile bool Receiving;

			internal volatile bool WarnedUnframed;
		}

		internal const int ProtocolVersion = 8;

		private static readonly ConcurrentDictionary<ISocket, PeerState> Peers = new ConcurrentDictionary<ISocket, PeerState>();

		internal static void Add(ISocket socket)
		{
			if (socket != null)
			{
				Peers[socket] = new PeerState();
			}
		}

		internal static void Remove(ISocket socket)
		{
			if (socket != null)
			{
				Peers.TryRemove(socket, out var _);
			}
		}

		internal static bool TryGet(ISocket socket, out PeerState state)
		{
			state = null;
			return socket != null && Peers.TryGetValue(socket, out state);
		}

		internal static void Clear()
		{
			Peers.Clear();
		}
	}
	internal static class PeerLookup
	{
		internal static ZNetPeer ByRpc(ZRpc rpc)
		{
			if (rpc == null || (Object)(object)ZNet.instance == (Object)null)
			{
				return null;
			}
			return ((IEnumerable<ZNetPeer>)ZNet.instance.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer peer) => peer.m_rpc == rpc));
		}

		internal static string Name(ZNetPeer peer)
		{
			if (peer == null)
			{
				return "[unknown]";
			}
			if (peer.m_server)
			{
				return "[server]";
			}
			string text = ((peer.m_socket == null) ? "?" : peer.m_socket.GetHostName());
			return peer.m_playerName + "[" + text + "]";
		}
	}
	internal static class CompressionPatch
	{
		private const string RpcVersion = "CW_Jesse.BetterNetworking.CompressionVersion";

		private const string RpcEnabled = "CW_Jesse.BetterNetworking.CompressionEnabled";

		private const string RpcStarted = "CW_Jesse.BetterNetworking.CompressedStarted";

		internal static void OnConnectPostfix(ZNetPeer peer)
		{
			if (peer?.m_socket != null && peer.m_rpc != null)
			{
				CompressionState.Add(peer.m_socket);
				peer.m_rpc.Register<int>("CW_Jesse.BetterNetworking.CompressionVersion", (Action<ZRpc, int>)ReceiveVersion);
				peer.m_rpc.Register<bool>("CW_Jesse.BetterNetworking.CompressionEnabled", (Action<ZRpc, bool>)ReceiveEnabled);
				peer.m_rpc.Register<bool>("CW_Jesse.BetterNetworking.CompressedStarted", (Action<ZRpc, bool>)ReceiveStarted);
				peer.m_rpc.Invoke("CW_Jesse.BetterNetworking.CompressionVersion", new object[1] { 8 });
				BetterNetworking10.Log.Message("Compression peer connected: " + PeerLookup.Name(peer));
			}
		}

		internal static void OnDisconnectPrefix(ZNetPeer peer)
		{
			BetterNetworking10.Log.Message("Compression peer disconnected: " + PeerLookup.Name(peer));
		}

		internal static void OnDisconnectPostfix(ZNetPeer peer)
		{
			CompressionState.Remove(peer?.m_socket);
		}

		private static void ReceiveVersion(ZRpc rpc, int version)
		{
			ZNetPeer val = PeerLookup.ByRpc(rpc);
			if (val != null && CompressionState.TryGet(val.m_socket, out var state))
			{
				state.Version = version;
				if (version == 8)
				{
					BetterNetworking10.Log.Message($"Compression compatible with {PeerLookup.Name(val)} (protocol {version})");
					SendEnabled(val);
				}
				else if (version > 0)
				{
					BetterNetworking10.Log.Warning($"Compression disabled with {PeerLookup.Name(val)}: protocol {version}, expected {8}");
				}
			}
		}

		private static void ReceiveEnabled(ZRpc rpc, bool enabled)
		{
			ZNetPeer val = PeerLookup.ByRpc(rpc);
			if (val != null && CompressionState.TryGet(val.m_socket, out var state))
			{
				state.Enabled = enabled;
				SendStarted(val, BetterNetworking10.CompressionActive && enabled && state.Version == 8);
			}
		}

		private static void ReceiveStarted(ZRpc rpc, bool started)
		{
			ZNetPeer val = PeerLookup.ByRpc(rpc);
			if (val != null && CompressionState.TryGet(val.m_socket, out var state))
			{
				state.Receiving = started;
				state.WarnedUnframed = false;
				BetterNetworking10.Log.Message($"Compression from {PeerLookup.Name(val)}: {started}");
			}
		}

		private static void SendEnabled(ZNetPeer peer)
		{
			peer.m_rpc.Invoke("CW_Jesse.BetterNetworking.CompressionEnabled", new object[1] { BetterNetworking10.CompressionActive });
			if (CompressionState.TryGet(peer.m_socket, out var state))
			{
				SendStarted(peer, BetterNetworking10.CompressionActive && state.Enabled && state.Version == 8);
			}
		}

		private static void SendStarted(ZNetPeer peer, bool started)
		{
			if (CompressionState.TryGet(peer.m_socket, out var state) && state.Sending != started)
			{
				peer.m_rpc.Invoke("CW_Jesse.BetterNetworking.CompressedStarted", new object[1] { started });
				Flush(peer.m_socket);
				state.Sending = started;
				BetterNetworking10.Log.Message($"Compression to {PeerLookup.Name(peer)}: {started}");
			}
		}

		private static void Flush(ISocket socket)
		{
			if (socket is ZSteamSocket)
			{
				socket.Flush();
				return;
			}
			ZPlayFabSocket val = (ZPlayFabSocket)(object)((socket is ZPlayFabSocket) ? socket : null);
			if (val != null)
			{
				PlayFabCompressionPatch.Flush(val);
			}
		}
	}
	internal static class SteamCompressionPatch
	{
		internal static void SendPrefix(ZSteamSocket __instance, ref ZPackage pkg)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			if (pkg != null && CompressionState.TryGet((ISocket)(object)__instance, out var state) && state.Sending)
			{
				pkg = new ZPackage(CompressionFrame.Encode(pkg.GetArray()));
			}
		}

		internal static void ReceivePostfix(ZSteamSocket __instance, ref ZPackage __result)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Expected O, but got Unknown
			if (__result == null || !CompressionState.TryGet((ISocket)(object)__instance, out var state))
			{
				return;
			}
			byte[] array = __result.GetArray();
			try
			{
				if (CompressionFrame.TryDecode(array, out var output))
				{
					__result = new ZPackage(output);
					state.WarnedUnframed = false;
					if (!state.Receiving)
					{
						state.Receiving = true;
						BetterNetworking10.Log.Warning("Received framed Steamworks packet before compression-start state; recovered automatically");
					}
				}
				else if (state.Receiving && !state.WarnedUnframed)
				{
					state.WarnedUnframed = true;
					BetterNetworking10.Log.Warning("Received an unframed Steamworks packet while compression was active; packet preserved");
				}
			}
			catch (Exception ex)
			{
				BetterNetworking10.Log.Error("Invalid Better Networking Steamworks frame dropped: " + ex.Message);
				__result = null;
			}
		}
	}
	internal static class PlayFabCompressionPatch
	{
		private static readonly ConcurrentDictionary<PlayFabZLibWorkQueue, ZPlayFabSocket> Sockets = new ConcurrentDictionary<PlayFabZLibWorkQueue, ZPlayFabSocket>();

		private static readonly FieldInfo WorkQueueField = AccessTools.Field(typeof(ZPlayFabSocket), "m_zlibWorkQueue");

		private static readonly FieldInfo WorkLockField = AccessTools.Field(typeof(PlayFabZLibWorkQueue), "m_lock");

		private static readonly FieldInfo WorkersField = AccessTools.Field(typeof(PlayFabZLibWorkQueue), "s_workers");

		private static readonly MethodInfo ExecuteMethod = AccessTools.Method(typeof(PlayFabZLibWorkQueue), "Execute", (Type[])null, (Type[])null);

		private static readonly MethodInfo LateUpdateMethod = AccessTools.Method(typeof(ZPlayFabSocket), "LateUpdate", (Type[])null, (Type[])null);

		internal static void SocketOpenPostfix(ZPlayFabSocket __instance, PlayFabZLibWorkQueue ___m_zlibWorkQueue)
		{
			if (__instance != null && ___m_zlibWorkQueue != null)
			{
				Sockets[___m_zlibWorkQueue] = __instance;
			}
		}

		internal static void SocketClosePostfix(PlayFabZLibWorkQueue ___m_zlibWorkQueue)
		{
			if (___m_zlibWorkQueue != null)
			{
				Sockets.TryRemove(___m_zlibWorkQueue, out var _);
			}
		}

		internal static bool CompressPrefix(PlayFabZLibWorkQueue __instance, Queue<byte[]> ___m_inCompress, Queue<byte[]> ___m_outCompress)
		{
			if (!Sockets.TryGetValue(__instance, out var value))
			{
				return true;
			}
			if (!CompressionState.TryGet((ISocket)(object)value, out var state) || !state.Sending)
			{
				return true;
			}
			while (___m_inCompress.Count > 0)
			{
				byte[] input = ___m_inCompress.Dequeue();
				try
				{
					___m_outCompress.Enqueue(CompressionFrame.Encode(input));
				}
				catch (Exception ex)
				{
					BetterNetworking10.Log.Error("PlayFab compression failed: " + ex.Message);
				}
			}
			return false;
		}

		internal static bool DecompressPrefix(PlayFabZLibWorkQueue __instance, Queue<byte[]> ___m_inDecompress, Queue<byte[]> ___m_outDecompress)
		{
			if (!Sockets.TryGetValue(__instance, out var value))
			{
				return true;
			}
			CompressionState.TryGet((ISocket)(object)value, out var state);
			while (___m_inDecompress.Count > 0)
			{
				byte[] array = ___m_inDecompress.Dequeue();
				bool flag = CompressionFrame.HasHeader(array);
				try
				{
					if (CompressionFrame.TryDecode(array, out var output))
					{
						___m_outDecompress.Enqueue(output);
						if (state != null && !state.Receiving)
						{
							state.Receiving = true;
							BetterNetworking10.Log.Warning("Received framed PlayFab packet before compression-start state; recovered automatically");
						}
						continue;
					}
				}
				catch (Exception ex)
				{
					BetterNetworking10.Log.Error("Invalid Better Networking PlayFab frame dropped: " + ex.Message);
					continue;
				}
				if (!flag)
				{
					try
					{
						byte[] item = ZlibStream.UncompressBuffer(array);
						___m_outDecompress.Enqueue(item);
					}
					catch
					{
						BetterNetworking10.Log.Warning("PlayFab packet was neither BN-framed nor vanilla-zlib; preserving raw data");
						___m_outDecompress.Enqueue(array);
					}
				}
			}
			return false;
		}

		internal static bool DirectCompressPrefix(PlayFabZLibWorkQueue __instance, byte[] payload, ref byte[] __result)
		{
			if (!Sockets.TryGetValue(__instance, out var value))
			{
				return true;
			}
			if (!CompressionState.TryGet((ISocket)(object)value, out var state) || !state.Sending)
			{
				return true;
			}
			__result = CompressionFrame.Encode(payload);
			return false;
		}

		internal static bool DirectDecompressPrefix(PlayFabZLibWorkQueue __instance, byte[] payload, ref byte[] __result)
		{
			if (!Sockets.TryGetValue(__instance, out var value))
			{
				return true;
			}
			if (!CompressionFrame.HasHeader(payload))
			{
				return true;
			}
			try
			{
				if (!CompressionFrame.TryDecode(payload, out __result))
				{
					return true;
				}
				if (CompressionState.TryGet((ISocket)(object)value, out var state) && !state.Receiving)
				{
					state.Receiving = true;
					BetterNetworking10.Log.Warning("Recovered an early/direct BN-compressed PlayFab packet");
				}
				return false;
			}
			catch
			{
				return true;
			}
		}

		internal static void Flush(ZPlayFabSocket socket)
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			if (socket == null || WorkQueueField == null || WorkLockField == null || WorkersField == null || ExecuteMethod == null || LateUpdateMethod == null)
			{
				return;
			}
			_ = (PlayFabZLibWorkQueue)WorkQueueField.GetValue(socket);
			object value = WorkLockField.GetValue(null);
			List<PlayFabZLibWorkQueue> list = (List<PlayFabZLibWorkQueue>)WorkersField.GetValue(null);
			lock (value)
			{
				PlayFabZLibWorkQueue[] array = list.ToArray();
				foreach (PlayFabZLibWorkQueue obj in array)
				{
					ExecuteMethod.Invoke(obj, Array.Empty<object>());
				}
			}
			LateUpdateMethod.Invoke(socket, null);
		}
	}
	internal static class ConnectionBufferPatch
	{
		private const int MaximumPacketsPerConnection = 256;

		private static readonly ConcurrentDictionary<ZRpc, List<ZPackage>> Buffers = new ConcurrentDictionary<ZRpc, List<ZPackage>>();

		private static readonly MethodInfo ReceiveZdoData = AccessTools.DeclaredMethod(typeof(ZDOMan), "RPC_ZDOData", (Type[])null, (Type[])null);

		internal static void OnNewConnectionPostfix(ZNet __instance, ZNetPeer peer)
		{
			if ((Object)(object)__instance == (Object)null || peer?.m_rpc == null || __instance.IsServer())
			{
				return;
			}
			List<ZPackage> buffer = Buffers.GetOrAdd(peer.m_rpc, (ZRpc _) => new List<ZPackage>());
			peer.m_rpc.Register<ZPackage>("ZDOData", (Action<ZRpc, ZPackage>)delegate(ZRpc rpc, ZPackage package)
			{
				//IL_004c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0056: Expected O, but got Unknown
				lock (buffer)
				{
					if (buffer.Count >= 256)
					{
						BetterNetworking10.Log.Error("Connection ZDO buffer reached 256 packets; dropping additional early packets");
					}
					else
					{
						buffer.Add(new ZPackage(package.GetArray()));
					}
				}
			});
		}

		internal static void AddPeerPostfix(ZDOMan __instance, ZNetPeer netPeer)
		{
			if (__instance == null || netPeer?.m_rpc == null || ReceiveZdoData == null || !Buffers.TryRemove(netPeer.m_rpc, out var value))
			{
				return;
			}
			lock (value)
			{
				BetterNetworking10.Log.Warning($"Replaying {value.Count} early ZDOData packet(s) for one connection");
				foreach (ZPackage item in value)
				{
					ReceiveZdoData.Invoke(__instance, new object[2] { netPeer.m_rpc, item });
				}
			}
		}

		internal static void ShutdownPostfix()
		{
			Clear();
		}

		internal static void Clear()
		{
			Buffers.Clear();
		}
	}
	internal static class DedicatedPatch
	{
		internal static void ParseArgumentsPostfix()
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			if (BetterNetworking10.IsDedicated)
			{
				switch (BetterNetworking10.ForceCrossplay.Value)
				{
				case ForceCrossplayOption.PlayFab:
					ZNet.m_onlineBackend = (OnlineBackendType)1;
					ZPlayFabMatchmaking.LookupPublicIP();
					break;
				case ForceCrossplayOption.Steamworks:
					ZNet.m_onlineBackend = (OnlineBackendType)0;
					break;
				}
			}
		}

		internal static int GetPlayerLimit()
		{
			return BetterNetworking10.PlayerLimit.Value;
		}

		internal static IEnumerable<CodeInstruction> PlayerLimitTranspiler(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> list = instructions.ToList();
			MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "GetNrOfPlayers", (Type[])null, (Type[])null);
			MethodInfo operand = AccessTools.DeclaredMethod(typeof(DedicatedPatch), "GetPlayerLimit", (Type[])null, (Type[])null);
			int num = -1;
			for (int i = 1; i < list.Count; i++)
			{
				if ((CodeInstructionExtensions.Is(list[i], OpCodes.Ldc_I4_S, (object)(sbyte)10) || CodeInstructionExtensions.Is(list[i], OpCodes.Ldc_I4, (object)10)) && CodeInstructionExtensions.Calls(list[i - 1], methodInfo))
				{
					if (num != -1)
					{
						BetterNetworking10.Log.Error("Player-limit transpiler found multiple guard constants; leaving RPC_PeerInfo unmodified");
						return list;
					}
					num = i;
				}
			}
			if (num == -1)
			{
				BetterNetworking10.Log.Error("Player-limit transpiler could not identify the 1.0 server-full guard; leaving RPC_PeerInfo unmodified");
				return list;
			}
			list[num].opcode = OpCodes.Call;
			list[num].operand = operand;
			BetterNetworking10.Log.Message($"Player-limit transpiler verified 1/1 IL match; limit={BetterNetworking10.PlayerLimit.Value}");
			return list;
		}
	}
	internal static class LegacyConfigMigration
	{
		private const string LegacyFileName = "CW_Jesse.BetterNetworking.cfg";

		internal static void TryImport(ConfigFile config, BNLog log)
		{
			string path = Path.Combine(Paths.ConfigPath, "CW_Jesse.BetterNetworking.cfg");
			if (!File.Exists(path))
			{
				return;
			}
			try
			{
				Dictionary<string, string> values = ReadValues(path);
				int num = 0;
				if (TryGet(values, "Networking", "Compression Enabled", out var value) && TryParseBoolean(value, out var result))
				{
					BetterNetworking10.EnableCompression.Value = result;
					num++;
				}
				if (TryGet(values, "Networking", "Queue Size", out var value2) && TryParseQueueSize(value2, out var result2))
				{
					BetterNetworking10.EnableQueueSize.Value = true;
					BetterNetworking10.QueueSize.Value = result2;
					num++;
				}
				if (TryGet(values, "Networking", "Update Rate", out var value3) && TryParseUpdateRate(value3, out var result3))
				{
					BetterNetworking10.EnableUpdateRate.Value = true;
					BetterNetworking10.UpdateRate.Value = result3;
					num++;
				}
				if (TryGet(values, "Networking (Steamworks)", "Minimum Send Rate", out var value4) && TryParseSendRate(value4, out var result4))
				{
					BetterNetworking10.EnableSendRate.Value = true;
					BetterNetworking10.MinimumSendRate.Value = result4;
					num++;
				}
				if (TryGet(values, "Networking (Steamworks)", "Maximum Send Rate", out var value5) && TryParseSendRate(value5, out var result5))
				{
					BetterNetworking10.EnableSendRate.Value = true;
					BetterNetworking10.MaximumSendRate.Value = result5;
					num++;
				}
				if (TryGet(values, "Dedicated Server", "Force Crossplay", out var value6) && TryParseCrossplay(value6, out var result6))
				{
					BetterNetworking10.ForceCrossplay.Value = result6;
					num++;
				}
				if (TryGet(values, "Dedicated Server", "Player Limit", out var value7) && int.TryParse(value7.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result7))
				{
					BetterNetworking10.PlayerLimit.Value = Math.Max(1, Math.Min(127, result7));
					num++;
				}
				if (TryGet(values, "Logging", "Log Level", out var value8) && TryParseLogLevel(value8, out var result8))
				{
					config.Bind<BNLog.LogLevel>("99 - Logging", "Log Level", BNLog.LogLevel.Message, (ConfigDescription)null).Value = result8;
					num++;
				}
				BetterNetworking10.EnableConnectionBuffer.Value = false;
				config.Save();
				log.Message(string.Format("Imported {0} setting(s) from legacy config {1}. The old file was left unchanged.", num, "CW_Jesse.BetterNetworking.cfg"));
			}
			catch (Exception ex)
			{
				log.Warning("Legacy config import skipped: " + ex.GetType().Name + ": " + ex.Message);
			}
		}

		private static Dictionary<string, string> ReadValues(string path)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			string section = string.Empty;
			string[] array = File.ReadAllLines(path);
			foreach (string text in array)
			{
				string text2 = text.Trim();
				if (text2.Length == 0 || text2.StartsWith("#") || text2.StartsWith(";"))
				{
					continue;
				}
				if (text2.StartsWith("[") && text2.EndsWith("]"))
				{
					section = text2.Substring(1, text2.Length - 2).Trim();
					continue;
				}
				int num = text2.IndexOf('=');
				if (num > 0)
				{
					string key = text2.Substring(0, num).Trim();
					string value = text2.Substring(num + 1).Trim();
					dictionary[MakeKey(section, key)] = value;
				}
			}
			return dictionary;
		}

		private static bool TryGet(Dictionary<string, string> values, string section, string key, out string value)
		{
			return values.TryGetValue(MakeKey(section, key), out value);
		}

		private static string MakeKey(string section, string key)
		{
			return section + "\n" + key;
		}

		private static string Normalize(string value)
		{
			return new string(value.Where(char.IsLetterOrDigit).Select(char.ToLowerInvariant).ToArray());
		}

		private static bool TryParseBoolean(string value, out bool result)
		{
			string text = Normalize(value);
			if (text == "true" || text == "enabled")
			{
				result = true;
				return true;
			}
			if (text == "false" || text == "disabled")
			{
				result = false;
				return true;
			}
			result = false;
			return false;
		}

		private static bool TryParseQueueSize(string value, out QueueSizeOption result)
		{
			string text = Normalize(value);
			if (text.Contains("80"))
			{
				result = QueueSizeOption.KB80;
			}
			else if (text.Contains("64"))
			{
				result = QueueSizeOption.KB64;
			}
			else if (text.Contains("48"))
			{
				result = QueueSizeOption.KB48;
			}
			else if (text.Contains("32"))
			{
				result = QueueSizeOption.KB32;
			}
			else
			{
				if (!text.Contains("vanilla") && !text.Contains("10"))
				{
					result = (QueueSizeOption)0;
					return false;
				}
				result = QueueSizeOption.Vanilla;
			}
			return true;
		}

		private static bool TryParseUpdateRate(string value, out UpdateRateOption result)
		{
			string text = Normalize(value);
			if (text.Contains("100"))
			{
				result = UpdateRateOption.Percent100;
			}
			else if (text.Contains("75"))
			{
				result = UpdateRateOption.Percent75;
			}
			else
			{
				if (!text.Contains("50"))
				{
					result = (UpdateRateOption)0;
					return false;
				}
				result = UpdateRateOption.Percent50;
			}
			return true;
		}

		private static bool TryParseSendRate(string value, out SendRateOption result)
		{
			string text = Normalize(value);
			if (text.Contains("1024"))
			{
				result = SendRateOption.KB1024;
			}
			else if (text.Contains("768"))
			{
				result = SendRateOption.KB768;
			}
			else if (text.Contains("512"))
			{
				result = SendRateOption.KB512;
			}
			else if (text.Contains("256"))
			{
				result = SendRateOption.KB256;
			}
			else
			{
				if (!text.Contains("150"))
				{
					result = (SendRateOption)0;
					return false;
				}
				result = SendRateOption.KB150;
			}
			return true;
		}

		private static bool TryParseCrossplay(string value, out ForceCrossplayOption result)
		{
			string text = Normalize(value);
			if (text.Contains("playfab") || text.Contains("enabled"))
			{
				result = ForceCrossplayOption.PlayFab;
			}
			else if (text.Contains("steamworks") || text.Contains("disabled"))
			{
				result = ForceCrossplayOption.Steamworks;
			}
			else
			{
				if (!text.Contains("vanilla"))
				{
					result = ForceCrossplayOption.Vanilla;
					return false;
				}
				result = ForceCrossplayOption.Vanilla;
			}
			return true;
		}

		private static bool TryParseLogLevel(string value, out BNLog.LogLevel result)
		{
			string text = Normalize(value);
			if (text.Contains("info"))
			{
				result = BNLog.LogLevel.Info;
			}
			else if (text.Contains("message"))
			{
				result = BNLog.LogLevel.Message;
			}
			else
			{
				if (!text.Contains("warning"))
				{
					result = BNLog.LogLevel.Warning;
					return false;
				}
				result = BNLog.LogLevel.Warning;
			}
			return true;
		}
	}
	internal static class QueueBudgetPatch
	{
		internal static int GetBudget()
		{
			return BetterNetworking10.QueueBudgetBytes;
		}

		internal static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> list = instructions.ToList();
			int num = list.Count((CodeInstruction i) => CodeInstructionExtensions.Is(i, OpCodes.Ldc_I4, (object)10240));
			if (num != 2)
			{
				BetterNetworking10.Log.Error($"Queue transpiler expected exactly 2 vanilla 10240 constants but found {num}; leaving SendZDOs unmodified");
				return list;
			}
			MethodInfo operand = AccessTools.DeclaredMethod(typeof(QueueBudgetPatch), "GetBudget", (Type[])null, (Type[])null);
			foreach (CodeInstruction item in list)
			{
				if (CodeInstructionExtensions.Is(item, OpCodes.Ldc_I4, (object)10240))
				{
					item.opcode = OpCodes.Call;
					item.operand = operand;
				}
			}
			BetterNetworking10.Log.Message($"Queue transpiler verified 2/2 IL matches; budget={BetterNetworking10.QueueBudgetBytes / 1024} KB");
			return list;
		}
	}
	internal static class UpdateRatePatch
	{
		internal static void Prefix(ref float dt)
		{
			dt *= BetterNetworking10.UpdateRateMultiplier;
		}
	}
	internal static class SteamSendRatePatch
	{
		internal static void Postfix()
		{
			int num = BetterNetworking10.RateBytesPerSecond(BetterNetworking10.MinimumSendRate.Value);
			int num2 = BetterNetworking10.RateBytesPerSecond(BetterNetworking10.MaximumSendRate.Value);
			if (num > num2)
			{
				int num3 = num;
				num = num2;
				num2 = num3;
				BetterNetworking10.Log.Warning("Steam send-rate minimum exceeded maximum; values were swapped at runtime");
			}
			Set((ESteamNetworkingConfigValue)10, num);
			Set((ESteamNetworkingConfigValue)11, num2);
		}

		private static void Set(ESteamNetworkingConfigValue key, int value)
		{
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			GCHandle gCHandle = GCHandle.Alloc(value, GCHandleType.Pinned);
			try
			{
				if (BetterNetworking10.IsDedicated)
				{
					SteamGameServerNetworkingUtils.SetConfigValue(key, (ESteamNetworkingConfigScope)1, IntPtr.Zero, (ESteamNetworkingConfigDataType)1, gCHandle.AddrOfPinnedObject());
				}
				else
				{
					SteamNetworkingUtils.SetConfigValue(key, (ESteamNetworkingConfigScope)1, IntPtr.Zero, (ESteamNetworkingConfigDataType)1, gCHandle.AddrOfPinnedObject());
				}
				BetterNetworking10.Log.Message($"Steamworks {key} set to {value / 1024} KB/s");
			}
			catch (Exception ex)
			{
				BetterNetworking10.Log.Error($"Could not set Steamworks {key}: {ex.Message}");
			}
			finally
			{
				gCHandle.Free();
			}
		}
	}
	internal static class PortalGhostRefresh
	{
		private const string StandalonePluginGuid = "sfantul.portalobserverfix";

		private const float CheckIntervalSeconds = 0.25f;

		private const float DelayedRefreshSeconds = 0.75f;

		private const float MinimumJumpDistance = 64f;

		private const float MinimumJumpDistanceSquared = 4096f;

		private static readonly Dictionary<ZDOID, Vector3> LastPositions = new Dictionary<ZDOID, Vector3>();

		private static readonly Dictionary<ZDOID, float> DelayedRefreshes = new Dictionary<ZDOID, float>();

		private static readonly HashSet<ZDOID> ActivePlayers = new HashSet<ZDOID>();

		private static readonly List<ZDOID> TemporaryIds = new List<ZDOID>();

		private static ZDOMan _manager;

		private static float _nextCheck;

		private static bool _externalModInstalled;

		private static bool _runtimeDisabled;

		internal static void Initialize()
		{
			_externalModInstalled = Chainloader.PluginInfos.ContainsKey("sfantul.portalobserverfix");
			_runtimeDisabled = false;
			if (!BetterNetworking10.PortalGhostFixActive)
			{
				BetterNetworking10.Log.Message("FEATURE SKIP Portal ghost fix: disabled by configuration");
			}
			else if (_externalModInstalled)
			{
				BetterNetworking10.Log.Warning("FEATURE SKIP Portal ghost fix: standalone Sfantul-PortalGhostFix detected; built-in refresh disabled to avoid duplicate sends");
			}
			else
			{
				BetterNetworking10.Log.Message("FEATURE ON Portal ghost fix: server-side player and linked status-effect refresh; vanilla clients supported");
			}
		}

		internal static void Tick()
		{
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_026b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0270: Unknown result type (might be due to invalid IL or missing references)
			//IL_0278: Unknown result type (might be due to invalid IL or missing references)
			//IL_028d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0222: Unknown result type (might be due to invalid IL or missing references)
			//IL_0224: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Unknown result type (might be due to invalid IL or missing references)
			//IL_0214: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_02db: Unknown result type (might be due to invalid IL or missing references)
			if (!BetterNetworking10.PortalGhostFixActive || _externalModInstalled || _runtimeDisabled)
			{
				return;
			}
			ZNet instance = ZNet.instance;
			ZDOMan instance2 = ZDOMan.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer() || instance2 == null)
			{
				if (_manager != null)
				{
					ClearRuntimeState();
				}
				return;
			}
			if (_manager != instance2)
			{
				ClearRuntimeState();
				_manager = instance2;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (realtimeSinceStartup < _nextCheck)
			{
				return;
			}
			_nextCheck = realtimeSinceStartup + 0.25f;
			try
			{
				ActivePlayers.Clear();
				foreach (PlayerInfo player in instance.GetPlayerList())
				{
					ZDOID characterID = player.m_characterID;
					if (characterID == ZDOID.None)
					{
						continue;
					}
					ActivePlayers.Add(characterID);
					ZDO zDO = instance2.GetZDO(characterID);
					if (zDO == null)
					{
						LastPositions.Remove(characterID);
						DelayedRefreshes.Remove(characterID);
						continue;
					}
					Vector3 position = zDO.GetPosition();
					if (LastPositions.TryGetValue(characterID, out var value))
					{
						Vector3 val = position - value;
						if (((Vector3)(ref val)).sqrMagnitude >= 4096f)
						{
							int num = ForceRefresh(instance2, characterID);
							DelayedRefreshes[characterID] = realtimeSinceStartup + 0.75f;
							BetterNetworking10.Log.Message($"Portal ghost refresh queued for {SafePlayerName(player.m_name)} ({characterID}) after {((Vector3)(ref val)).magnitude:F1} m movement; linked effects={num}");
						}
					}
					if (DelayedRefreshes.TryGetValue(characterID, out var value2) && realtimeSinceStartup >= value2)
					{
						ForceRefresh(instance2, characterID);
						DelayedRefreshes.Remove(characterID);
					}
					LastPositions[characterID] = position;
				}
				TemporaryIds.Clear();
				foreach (ZDOID key in LastPositions.Keys)
				{
					if (!ActivePlayers.Contains(key))
					{
						TemporaryIds.Add(key);
					}
				}
				foreach (ZDOID temporaryId in TemporaryIds)
				{
					LastPositions.Remove(temporaryId);
					DelayedRefreshes.Remove(temporaryId);
				}
			}
			catch (Exception ex)
			{
				_runtimeDisabled = true;
				ClearRuntimeState();
				BetterNetworking10.Log.Error("FEATURE FAILED Portal ghost fix: " + ex.GetType().Name + ": " + ex.Message + ". Disabled until restart; networking remains otherwise unchanged");
			}
		}

		internal static void Clear()
		{
			ClearRuntimeState();
			_externalModInstalled = false;
			_runtimeDisabled = false;
		}

		private static int ForceRefresh(ZDOMan manager, ZDOID characterId)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			manager.ForceSendZDO(characterId);
			int num = 0;
			List<ZDOID> allZDOIDsWithHash = ZDOExtraData.GetAllZDOIDsWithHash((Type)4, ZDOVars.s_ownerZDOUserId);
			foreach (ZDOID item in allZDOIDsWithHash)
			{
				if (!(item == characterId))
				{
					ZDO zDO = manager.GetZDO(item);
					if (zDO != null && zDO.GetLong(ZDOVars.s_ownerZDOUserId, 0L) == ((ZDOID)(ref characterId)).UserID && zDO.GetLong(ZDOVars.s_ownerZDOId, 0L) == ((ZDOID)(ref characterId)).ID)
					{
						manager.ForceSendZDO(item);
						num++;
					}
				}
			}
			return num;
		}

		private static void ClearRuntimeState()
		{
			LastPositions.Clear();
			DelayedRefreshes.Clear();
			ActivePlayers.Clear();
			TemporaryIds.Clear();
			_manager = null;
			_nextCheck = 0f;
		}

		private static string SafePlayerName(string name)
		{
			return string.IsNullOrEmpty(name) ? "unknown player" : name;
		}
	}
	internal static class VersionInfo
	{
		public const string Version = "1.2.0";
	}
}
namespace Costura
{
	[CompilerGenerated]
	internal static class AssemblyLoader
	{
		private static object nullCacheLock = new object();

		private static Dictionary<string, bool> nullCache = new Dictionary<string, bool>();

		private static Dictionary<string, string> assemblyNames = new Dictionary<string, string>();

		private static Dictionary<string, string> symbolNames = new Dictionary<string, string>();

		private static int isAttached;

		private static string CultureToString(CultureInfo culture)
		{
			if (culture == null)
			{
				return "";
			}
			return culture.Name;
		}

		private static Assembly ReadExistingAssembly(AssemblyName name)
		{
			AppDomain currentDomain = AppDomain.CurrentDomain;
			Assembly[] assemblies = currentDomain.GetAssemblies();
			Assembly[] array = assemblies;
			foreach (Assembly assembly in array)
			{
				AssemblyName name2 = assembly.GetName();
				if (string.Equals(name2.Name, name.Name, StringComparison.InvariantCultureIgnoreCase) && string.Equals(CultureToString(name2.CultureInfo), CultureToString(name.CultureInfo), StringComparison.InvariantCultureIgnoreCase))
				{
					return assembly;
				}
			}
			return null;
		}

		private static void CopyTo(Stream source, Stream destination)
		{
			byte[] array = new byte[81920];
			int count;
			while ((count = source.Read(array, 0, array.Length)) != 0)
			{
				destination.Write(array, 0, count);
			}
		}

		private static Stream LoadStream(string fullName)
		{
			Assembly executingAssembly = Assembly.GetExecutingAssembly();
			if (fullName.EndsWith(".compressed"))
			{
				using (Stream stream = executingAssembly.GetManifestResourceStream(fullName))
				{
					using DeflateStream source = new DeflateStream(stream, CompressionMode.Decompress);
					MemoryStream memoryStream = new MemoryStream();
					CopyTo(source, memoryStream);
					memoryStream.Position = 0L;
					return memoryStream;
				}
			}
			return executingAssembly.GetManifestResourceStream(fullName);
		}

		private static Stream LoadStream(Dictionary<string, string> resourceNames, string name)
		{
			if (resourceNames.TryGetValue(name, out var value))
			{
				return LoadStream(value);
			}
			return null;
		}

		private static byte[] ReadStream(Stream stream)
		{
			byte[] array = new byte[stream.Length];
			stream.Read(array, 0, array.Length);
			return array;
		}

		private static Assembly ReadFromEmbeddedResources(Dictionary<string, string> assemblyNames, Dictionary<string, string> symbolNames, AssemblyName requestedAssemblyName)
		{
			string text = requestedAssemblyName.Name.ToLowerInvariant();
			if (requestedAssemblyName.CultureInfo != null && !string.IsNullOrEmpty(requestedAssemblyName.CultureInfo.Name))
			{
				text = requestedAssemblyName.CultureInfo.Name + "." + text;
			}
			byte[] rawAssembly;
			using (Stream stream = LoadStream(assemblyNames, text))
			{
				if (stream == null)
				{
					return null;
				}
				rawAssembly = ReadStream(stream);
			}
			using (Stream stream2 = LoadStream(symbolNames, text))
			{
				if (stream2 != null)
				{
					byte[] rawSymbolStore = ReadStream(stream2);
					return Assembly.Load(rawAssembly, rawSymbolStore);
				}
			}
			return Assembly.Load(rawAssembly);
		}

		public static Assembly ResolveAssembly(object sender, ResolveEventArgs e)
		{
			lock (nullCacheLock)
			{
				if (nullCache.ContainsKey(e.Name))
				{
					return null;
				}
			}
			AssemblyName assemblyName = new AssemblyName(e.Name);
			Assembly assembly = ReadExistingAssembly(assemblyName);
			if ((object)assembly != null)
			{
				return assembly;
			}
			assembly = ReadFromEmbeddedResources(assemblyNames, symbolNames, assemblyName);
			if ((object)assembly == null)
			{
				lock (nullCacheLock)
				{
					nullCache[e.Name] = true;
				}
				if ((assemblyName.Flags & AssemblyNameFlags.Retargetable) != AssemblyNameFlags.None)
				{
					assembly = Assembly.Load(assemblyName);
				}
			}
			return assembly;
		}

		static AssemblyLoader()
		{
			assemblyNames.Add("costura", "costura.costura.dll.compressed");
			symbolNames.Add("costura", "costura.costura.pdb.compressed");
			assemblyNames.Add("system.buffers", "costura.system.buffers.dll.compressed");
			assemblyNames.Add("system.diagnostics.diagnosticsource", "costura.system.diagnostics.diagnosticsource.dll.compressed");
			assemblyNames.Add("system.memory", "costura.system.memory.dll.compressed");
			assemblyNames.Add("system.numerics.vectors", "costura.system.numerics.vectors.dll.compressed");
			assemblyNames.Add("system.runtime.compilerservices.unsafe", "costura.system.runtime.compilerservices.unsafe.dll.compressed");
			assemblyNames.Add("system.threading.tasks.extensions", "costura.system.threading.tasks.extensions.dll.compressed");
			assemblyNames.Add("zstdsharp", "costura.zstdsharp.dll.compressed");
		}

		public static void Attach()
		{
			if (Interlocked.Exchange(ref isAttached, 1) == 1)
			{
				return;
			}
			AppDomain currentDomain = AppDomain.CurrentDomain;
			currentDomain.AssemblyResolve += delegate(object sender, ResolveEventArgs e)
			{
				lock (nullCacheLock)
				{
					if (nullCache.ContainsKey(e.Name))
					{
						return (Assembly)null;
					}
				}
				AssemblyName assemblyName = new AssemblyName(e.Name);
				Assembly assembly = ReadExistingAssembly(assemblyName);
				if ((object)assembly != null)
				{
					return assembly;
				}
				assembly = ReadFromEmbeddedResources(assemblyNames, symbolNames, assemblyName);
				if ((object)assembly == null)
				{
					lock (nullCacheLock)
					{
						nullCache[e.Name] = true;
					}
					if ((assemblyName.Flags & AssemblyNameFlags.Retargetable) != AssemblyNameFlags.None)
					{
						assembly = Assembly.Load(assemblyName);
					}
				}
				return assembly;
			};
		}
	}
}
internal class DITBetterNetworking10_ProcessedByFody
{
	internal const string FodyVersion = "6.9.3.0";

	internal const string Costura = "5.7.0";
}