Decompiled source of Hermod v0.1.3

plugins/Hermod.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using GBV.Shared;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Steamworks;
using UnityEngine;
using ZstdSharp;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Guys Being Vikings")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.3.0")]
[assembly: AssemblyInformationalVersion("0.1.3+0240d42be45db71c129cce2fb4c016a0e9ff31a4")]
[assembly: AssemblyProduct("Hermod")]
[assembly: AssemblyTitle("Hermod")]
[assembly: AssemblyVersion("0.1.3.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace GBV.Shared
{
	public class JsonException : Exception
	{
		public JsonException(string message)
			: base(message)
		{
		}
	}
	public sealed class JsonValue
	{
		public enum Kind
		{
			Null,
			Bool,
			Number,
			String,
			Array,
			Object
		}

		private bool _bool;

		private double _number;

		private bool _isFloat;

		private string _string;

		private List<JsonValue> _array;

		private Dictionary<string, JsonValue> _object;

		public static readonly JsonValue Null = new JsonValue
		{
			ValueKind = Kind.Null
		};

		private const int MaxDepth = 64;

		public Kind ValueKind { get; private set; }

		public bool IsNull => ValueKind == Kind.Null;

		public JsonValue this[string key]
		{
			get
			{
				if (ValueKind != Kind.Object)
				{
					return Null;
				}
				if (!_object.TryGetValue(key, out var value))
				{
					return Null;
				}
				return value;
			}
		}

		public JsonValue this[int index]
		{
			get
			{
				if (ValueKind != Kind.Array || index < 0 || index >= _array.Count)
				{
					return Null;
				}
				return _array[index];
			}
		}

		public int Count
		{
			get
			{
				if (ValueKind == Kind.Array)
				{
					return _array.Count;
				}
				if (ValueKind == Kind.Object)
				{
					return _object.Count;
				}
				return 0;
			}
		}

		public IEnumerable<KeyValuePair<string, JsonValue>> Members
		{
			get
			{
				if (ValueKind != Kind.Object)
				{
					return new KeyValuePair<string, JsonValue>[0];
				}
				return _object;
			}
		}

		public IEnumerable<JsonValue> Items
		{
			get
			{
				if (ValueKind != Kind.Array)
				{
					return new JsonValue[0];
				}
				return _array;
			}
		}

		private JsonValue()
		{
		}

		public static JsonValue Bool(bool v)
		{
			return new JsonValue
			{
				ValueKind = Kind.Bool,
				_bool = v
			};
		}

		public static JsonValue Number(double v)
		{
			return new JsonValue
			{
				ValueKind = Kind.Number,
				_number = v
			};
		}

		public static JsonValue Number(float v)
		{
			return new JsonValue
			{
				ValueKind = Kind.Number,
				_number = v,
				_isFloat = true
			};
		}

		public static JsonValue String(string v)
		{
			if (v != null)
			{
				return new JsonValue
				{
					ValueKind = Kind.String,
					_string = v
				};
			}
			return Null;
		}

		public static JsonValue NewArray()
		{
			return new JsonValue
			{
				ValueKind = Kind.Array,
				_array = new List<JsonValue>()
			};
		}

		public static JsonValue NewObject()
		{
			return new JsonValue
			{
				ValueKind = Kind.Object,
				_object = new Dictionary<string, JsonValue>(StringComparer.Ordinal)
			};
		}

		public JsonValue Add(JsonValue v)
		{
			if (ValueKind != Kind.Array)
			{
				throw new JsonException("Add called on a " + ValueKind);
			}
			_array.Add(v ?? Null);
			return this;
		}

		public JsonValue Set(string key, JsonValue v)
		{
			if (ValueKind != Kind.Object)
			{
				throw new JsonException("Set called on a " + ValueKind);
			}
			_object[key] = v ?? Null;
			return this;
		}

		public JsonValue Set(string key, string v)
		{
			return Set(key, String(v));
		}

		public JsonValue Set(string key, double v)
		{
			return Set(key, Number(v));
		}

		public JsonValue Set(string key, float v)
		{
			return Set(key, Number(v));
		}

		public JsonValue Set(string key, int v)
		{
			return Set(key, Number((double)v));
		}

		public JsonValue Set(string key, long v)
		{
			return Set(key, Number((double)v));
		}

		public JsonValue Set(string key, bool v)
		{
			return Set(key, Bool(v));
		}

		public string AsString(string fallback = null)
		{
			if (ValueKind != Kind.String)
			{
				return fallback;
			}
			return _string;
		}

		public double AsDouble(double fallback = 0.0)
		{
			if (ValueKind != Kind.Number)
			{
				return fallback;
			}
			return _number;
		}

		public float AsFloat(float fallback = 0f)
		{
			if (ValueKind != Kind.Number)
			{
				return fallback;
			}
			return (float)_number;
		}

		public int AsInt(int fallback = 0)
		{
			if (ValueKind != Kind.Number)
			{
				return fallback;
			}
			return (int)Math.Round(_number);
		}

		public long AsLong(long fallback = 0L)
		{
			if (ValueKind != Kind.Number)
			{
				return fallback;
			}
			return (long)Math.Round(_number);
		}

		public bool AsBool(bool fallback = false)
		{
			if (ValueKind != Kind.Bool)
			{
				return fallback;
			}
			return _bool;
		}

		public override string ToString()
		{
			StringBuilder stringBuilder = new StringBuilder();
			Write(stringBuilder, this, -1, 0);
			return stringBuilder.ToString();
		}

		public string ToPrettyString()
		{
			StringBuilder stringBuilder = new StringBuilder();
			Write(stringBuilder, this, 2, 0);
			return stringBuilder.ToString();
		}

		private static void Write(StringBuilder sb, JsonValue v, int indent, int depth)
		{
			bool flag = indent >= 0;
			switch (v.ValueKind)
			{
			case Kind.Null:
				sb.Append("null");
				break;
			case Kind.Bool:
				sb.Append(v._bool ? "true" : "false");
				break;
			case Kind.Number:
				WriteNumber(sb, v._number, v._isFloat);
				break;
			case Kind.String:
				WriteString(sb, v._string);
				break;
			case Kind.Array:
			{
				if (v._array.Count == 0)
				{
					sb.Append("[]");
					break;
				}
				sb.Append('[');
				for (int i = 0; i < v._array.Count; i++)
				{
					if (i > 0)
					{
						sb.Append(',');
					}
					if (flag)
					{
						NewLine(sb, indent, depth + 1);
					}
					Write(sb, v._array[i], indent, depth + 1);
				}
				if (flag)
				{
					NewLine(sb, indent, depth);
				}
				sb.Append(']');
				break;
			}
			case Kind.Object:
			{
				if (v._object.Count == 0)
				{
					sb.Append("{}");
					break;
				}
				sb.Append('{');
				bool flag2 = true;
				foreach (KeyValuePair<string, JsonValue> item in v._object)
				{
					if (!flag2)
					{
						sb.Append(',');
					}
					flag2 = false;
					if (flag)
					{
						NewLine(sb, indent, depth + 1);
					}
					WriteString(sb, item.Key);
					sb.Append(':');
					if (flag)
					{
						sb.Append(' ');
					}
					Write(sb, item.Value, indent, depth + 1);
				}
				if (flag)
				{
					NewLine(sb, indent, depth);
				}
				sb.Append('}');
				break;
			}
			}
		}

		private static void NewLine(StringBuilder sb, int indent, int depth)
		{
			sb.Append('\n');
			sb.Append(' ', indent * depth);
		}

		private static void WriteNumber(StringBuilder sb, double d, bool asFloat)
		{
			if (double.IsNaN(d) || double.IsInfinity(d))
			{
				sb.Append('0');
			}
			else
			{
				sb.Append(asFloat ? ((float)d).ToString("R", CultureInfo.InvariantCulture) : d.ToString("R", CultureInfo.InvariantCulture));
			}
		}

		private static void WriteString(StringBuilder sb, string s)
		{
			sb.Append('"');
			foreach (char c in s)
			{
				switch (c)
				{
				case '"':
					sb.Append("\\\"");
					continue;
				case '\\':
					sb.Append("\\\\");
					continue;
				case '\b':
					sb.Append("\\b");
					continue;
				case '\f':
					sb.Append("\\f");
					continue;
				case '\n':
					sb.Append("\\n");
					continue;
				case '\r':
					sb.Append("\\r");
					continue;
				case '\t':
					sb.Append("\\t");
					continue;
				}
				if (c < ' ')
				{
					StringBuilder stringBuilder = sb.Append("\\u");
					int num = c;
					stringBuilder.Append(num.ToString("x4", CultureInfo.InvariantCulture));
				}
				else
				{
					sb.Append(c);
				}
			}
			sb.Append('"');
		}

		public static JsonValue Parse(string text)
		{
			if (text == null)
			{
				throw new JsonException("Cannot parse null.");
			}
			int pos = 0;
			JsonValue result = ParseValue(text, ref pos, 0);
			SkipWhitespace(text, ref pos);
			if (pos != text.Length)
			{
				throw new JsonException("Trailing content at offset " + pos + ".");
			}
			return result;
		}

		public static JsonValue TryParse(string text)
		{
			try
			{
				return Parse(text);
			}
			catch (JsonException)
			{
				return null;
			}
		}

		private static JsonValue ParseValue(string s, ref int pos, int depth)
		{
			if (depth > 64)
			{
				throw new JsonException("Nested too deeply.");
			}
			SkipWhitespace(s, ref pos);
			if (pos >= s.Length)
			{
				throw new JsonException("Unexpected end of input.");
			}
			switch (s[pos])
			{
			case '{':
				return ParseObject(s, ref pos, depth);
			case '[':
				return ParseArray(s, ref pos, depth);
			case '"':
				return String(ParseString(s, ref pos));
			case 't':
				Expect(s, ref pos, "true");
				return Bool(v: true);
			case 'f':
				Expect(s, ref pos, "false");
				return Bool(v: false);
			case 'n':
				Expect(s, ref pos, "null");
				return Null;
			default:
				return Number(ParseNumber(s, ref pos));
			}
		}

		private static JsonValue ParseObject(string s, ref int pos, int depth)
		{
			pos++;
			JsonValue jsonValue = NewObject();
			SkipWhitespace(s, ref pos);
			if (pos < s.Length && s[pos] == '}')
			{
				pos++;
				return jsonValue;
			}
			while (true)
			{
				SkipWhitespace(s, ref pos);
				if (pos >= s.Length || s[pos] != '"')
				{
					throw new JsonException("Expected a member name at offset " + pos + ".");
				}
				string key = ParseString(s, ref pos);
				SkipWhitespace(s, ref pos);
				if (pos >= s.Length || s[pos] != ':')
				{
					throw new JsonException("Expected ':' at offset " + pos + ".");
				}
				pos++;
				jsonValue._object[key] = ParseValue(s, ref pos, depth + 1);
				SkipWhitespace(s, ref pos);
				if (pos >= s.Length)
				{
					throw new JsonException("Unterminated object.");
				}
				if (s[pos] != ',')
				{
					break;
				}
				pos++;
			}
			if (s[pos] == '}')
			{
				pos++;
				return jsonValue;
			}
			throw new JsonException("Expected ',' or '}' at offset " + pos + ".");
		}

		private static JsonValue ParseArray(string s, ref int pos, int depth)
		{
			pos++;
			JsonValue jsonValue = NewArray();
			SkipWhitespace(s, ref pos);
			if (pos < s.Length && s[pos] == ']')
			{
				pos++;
				return jsonValue;
			}
			while (true)
			{
				jsonValue._array.Add(ParseValue(s, ref pos, depth + 1));
				SkipWhitespace(s, ref pos);
				if (pos >= s.Length)
				{
					throw new JsonException("Unterminated array.");
				}
				if (s[pos] != ',')
				{
					break;
				}
				pos++;
			}
			if (s[pos] == ']')
			{
				pos++;
				return jsonValue;
			}
			throw new JsonException("Expected ',' or ']' at offset " + pos + ".");
		}

		private static string ParseString(string s, ref int pos)
		{
			pos++;
			StringBuilder stringBuilder = new StringBuilder();
			while (pos < s.Length)
			{
				char c = s[pos++];
				switch (c)
				{
				case '"':
					return stringBuilder.ToString();
				default:
					stringBuilder.Append(c);
					break;
				case '\\':
				{
					if (pos >= s.Length)
					{
						throw new JsonException("Unterminated escape.");
					}
					char c2 = s[pos++];
					switch (c2)
					{
					case '"':
						stringBuilder.Append('"');
						break;
					case '\\':
						stringBuilder.Append('\\');
						break;
					case '/':
						stringBuilder.Append('/');
						break;
					case 'b':
						stringBuilder.Append('\b');
						break;
					case 'f':
						stringBuilder.Append('\f');
						break;
					case 'n':
						stringBuilder.Append('\n');
						break;
					case 'r':
						stringBuilder.Append('\r');
						break;
					case 't':
						stringBuilder.Append('\t');
						break;
					case 'u':
					{
						if (pos + 4 > s.Length)
						{
							throw new JsonException("Truncated \\u escape.");
						}
						if (!int.TryParse(s.Substring(pos, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result))
						{
							throw new JsonException("Bad \\u escape at offset " + pos + ".");
						}
						stringBuilder.Append((char)result);
						pos += 4;
						break;
					}
					default:
						throw new JsonException("Unknown escape '\\" + c2 + "' at offset " + (pos - 1) + ".");
					}
					break;
				}
				}
			}
			throw new JsonException("Unterminated string.");
		}

		private static double ParseNumber(string s, ref int pos)
		{
			int num = pos;
			if (pos < s.Length && (s[pos] == '-' || s[pos] == '+'))
			{
				pos++;
			}
			while (pos < s.Length)
			{
				char c = s[pos];
				if ((c < '0' || c > '9') && c != '.' && c != 'e' && c != 'E' && c != '-' && c != '+')
				{
					break;
				}
				pos++;
			}
			string text = s.Substring(num, pos - num);
			if (!double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				throw new JsonException("Bad number '" + text + "' at offset " + num + ".");
			}
			return result;
		}

		private static void Expect(string s, ref int pos, string literal)
		{
			if (pos + literal.Length > s.Length || string.CompareOrdinal(s, pos, literal, 0, literal.Length) != 0)
			{
				throw new JsonException("Expected '" + literal + "' at offset " + pos + ".");
			}
			pos += literal.Length;
		}

		private static void SkipWhitespace(string s, ref int pos)
		{
			while (pos < s.Length)
			{
				switch (s[pos])
				{
				case '\t':
				case '\n':
				case '\r':
				case ' ':
					pos++;
					break;
				case '/':
					if (pos + 1 < s.Length && s[pos + 1] == '/')
					{
						pos += 2;
						while (pos < s.Length && s[pos] != '\n')
						{
							pos++;
						}
						break;
					}
					return;
				default:
					return;
				}
			}
		}
	}
}
namespace GBV.Hermod
{
	internal static class Bridge
	{
		private const string RequestRpc = "GBV_Hermod_Request";

		private const string ReplyRpc = "GBV_Hermod_Reply";

		private const int WireVersion = 1;

		private static ZRoutedRpc _registeredOn;

		internal static void EnsureRegistered()
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance == null)
			{
				_registeredOn = null;
			}
			else if (instance != _registeredOn)
			{
				try
				{
					instance.Register<ZPackage>("GBV_Hermod_Request", (Action<long, ZPackage>)OnRequest);
					instance.Register<ZPackage>("GBV_Hermod_Reply", (Action<long, ZPackage>)OnReply);
					_registeredOn = instance;
				}
				catch (Exception ex)
				{
					Safety.Noted("registering the Hermod admin channel", ex);
				}
			}
		}

		internal static bool Send(string line)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected O, but got Unknown
			try
			{
				ZRoutedRpc instance = ZRoutedRpc.instance;
				if (instance == null)
				{
					return false;
				}
				ZPackage val = new ZPackage();
				val.Write(1);
				val.Write(line ?? string.Empty);
				instance.InvokeRoutedRPC("GBV_Hermod_Request", new object[1] { val });
				return true;
			}
			catch (Exception ex)
			{
				Safety.Noted("sending a command to the server", ex);
				return false;
			}
		}

		private static void OnReply(long sender, ZPackage pkg)
		{
			try
			{
				if (pkg == null)
				{
					return;
				}
				int num = pkg.ReadInt();
				if (num != 1)
				{
					WrongWire(num);
					return;
				}
				string text = pkg.ReadString();
				if (string.IsNullOrEmpty(text))
				{
					return;
				}
				string[] array = text.Split('\n');
				foreach (string text2 in array)
				{
					Console instance = Console.instance;
					if (instance != null)
					{
						((Terminal)instance).AddString(text2);
					}
				}
			}
			catch (Exception ex)
			{
				Safety.Noted("reading the server's answer", ex);
			}
		}

		private static void OnRequest(long sender, ZPackage pkg)
		{
			try
			{
				ZNet instance = ZNet.instance;
				if ((Object)(object)instance == (Object)null || !instance.IsServer() || pkg == null)
				{
					return;
				}
				int num = pkg.ReadInt();
				if (num != 1)
				{
					Reply(sender, "Hermod: that client is running a different version of Hermod (wire " + num + ", this server speaks " + 1 + ").");
					return;
				}
				string text = pkg.ReadString();
				if (!IsAdminPeer(sender))
				{
					HermodPlugin.Log.LogWarning((object)("Hermod: refused a command from a non-admin peer (" + sender + "): " + text));
					Reply(sender, "Hermod: you need to be an admin on this server to do that.");
					return;
				}
				HermodPlugin.Log.LogInfo((object)("Hermod: admin " + sender + " ran '" + text + "'."));
				List<string> list = Commands.RunOnServer(text);
				Reply(sender, string.Join("\n", list.ToArray()));
			}
			catch (Exception ex)
			{
				Safety.Noted("handling an admin command", ex);
				try
				{
					Reply(sender, "Hermod: that command failed on the server. See the log.");
				}
				catch
				{
				}
			}
		}

		private static bool IsAdminPeer(long uid)
		{
			try
			{
				ZNet instance = ZNet.instance;
				if ((Object)(object)instance == (Object)null)
				{
					return false;
				}
				if (uid == 0L || uid == ZNet.GetUID())
				{
					return true;
				}
				ZNetPeer peer = instance.GetPeer(uid);
				object obj;
				if (peer == null)
				{
					obj = null;
				}
				else
				{
					ISocket socket = peer.m_socket;
					obj = ((socket != null) ? socket.GetHostName() : null);
				}
				string text = (string)obj;
				return !string.IsNullOrEmpty(text) && instance.IsAdmin(text);
			}
			catch (Exception ex)
			{
				Safety.Noted("checking whether a peer is an admin", ex);
				return false;
			}
		}

		internal static void Reply(long target, string text)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			try
			{
				ZRoutedRpc instance = ZRoutedRpc.instance;
				if (instance != null)
				{
					ZPackage val = new ZPackage();
					val.Write(1);
					val.Write(text ?? string.Empty);
					instance.InvokeRoutedRPC(target, "GBV_Hermod_Reply", new object[1] { val });
				}
			}
			catch (Exception ex)
			{
				Safety.Noted("replying to an admin command", ex);
			}
		}

		private static void WrongWire(int seen)
		{
			Safety.Noted("a Hermod message arrived speaking wire version " + seen + " rather than " + 1 + ", so the server and this client are on different versions", new InvalidOperationException("wire version mismatch"));
		}
	}
	internal static class Ceiling
	{
		internal const int Vanilla = 10240;

		private static bool _applied;

		private static bool _verified;

		private static bool _announced;

		internal static bool Active
		{
			get
			{
				if (_applied)
				{
					return _verified;
				}
				return false;
			}
		}

		internal static int Budget()
		{
			try
			{
				if (HermodPlugin.MeasureOnly != null && HermodPlugin.MeasureOnly.Value)
				{
					return 10240;
				}
				int inFlightCeiling = HermodPlugin.Cfg.InFlightCeiling;
				return (inFlightCeiling > 0) ? inFlightCeiling : 10240;
			}
			catch
			{
				return 10240;
			}
		}

		internal static string Apply(Harmony harmony)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected O, but got Unknown
			if (_applied)
			{
				return null;
			}
			try
			{
				harmony.Patch((MethodBase)Reflect.SendZDOs, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(Ceiling), "Transpiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
				_applied = true;
				return null;
			}
			catch (Exception ex)
			{
				return ex.Message;
			}
		}

		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> list = instructions.ToList();
			try
			{
				int num = list.Count((CodeInstruction i) => CodeInstructionExtensions.Is(i, OpCodes.Ldc_I4, (object)10240));
				if (num != 2)
				{
					HermodPlugin.Log.LogError((object)("Hermod: ZDOMan.SendZDOs was expected to contain exactly two " + 10240 + " constants and contains " + num + ". The send budget has been LEFT ALONE. This means Valheim changed that method and Hermod has not caught up; everything else still works."));
					return list;
				}
				MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(Ceiling), "Budget", (Type[])null, (Type[])null);
				if (methodInfo == null)
				{
					HermodPlugin.Log.LogError((object)"Hermod: could not resolve its own send budget getter.");
					return list;
				}
				foreach (CodeInstruction item in list)
				{
					if (CodeInstructionExtensions.Is(item, OpCodes.Ldc_I4, (object)10240))
					{
						item.opcode = OpCodes.Call;
						item.operand = methodInfo;
					}
				}
				_verified = true;
				if (!_announced)
				{
					_announced = true;
					HermodPlugin.Log.LogInfo((object)("Hermod: the ZDO send budget is now adjustable (both constants rewritten). It is currently " + Budget() + " bytes" + ((HermodPlugin.Cfg.InFlightCeiling > 0) ? (", raised from the game's " + 10240 + ".") : ", which is the game's own value - set inFlightCeiling to change it.")));
				}
				return list;
			}
			catch (Exception ex)
			{
				Safety.Failed("ZDOMan.SendZDOs transpiler", ex);
				return list;
			}
		}

		internal static string StatusLine()
		{
			if (!Active)
			{
				return "send budget: unavailable (the transpiler did not take)";
			}
			int num = Budget();
			return "send budget: " + num + " bytes" + ((num == 10240) ? " (the game's own value)" : (" (game default " + 10240 + ")"));
		}
	}
	[HarmonyPatch(typeof(Terminal), "InitTerminal")]
	public static class Commands
	{
		private static bool _registered;

		[HarmonyPostfix]
		public static void Postfix()
		{
			try
			{
				if (!_registered)
				{
					_registered = true;
					Register();
				}
			}
			catch (Exception ex)
			{
				Safety.Failed("Terminal.InitTerminal command registration", ex);
			}
		}

		private static void Register()
		{
			Cmd("hermod_status", "hermod_status - frame time, cpu, zdo counts and relay rate right now.");
			Cmd("hermod_peers", "hermod_peers - per player: ping, send queue, send rate and how often they are updated.");
			Cmd("hermod_report", "hermod_report - close the current bucket and write the JSON report now.");
			Cmd("hermod_mark", "hermod_mark <label> - stamp a labelled boundary into the logs. Use it before and after every change you want to compare.");
			Cmd("hermod_profile", "hermod_profile <vanilla|tier1|relay> - flip the whole set of settings at once, live. This is how you run an A/B.");
			Cmd("hermod_relay", "hermod_relay <on|off|status> - the ZDO send rewrite. Off by default.");
			Cmd("hermod_net", "hermod_net [apply] - show the Steam socket settings in force; apply re-asserts them on every live connection.");
			Cmd("hermod_fps", "hermod_fps <n|auto|off> - cap the server frame rate. Refuses a cap that would slow ZDO delivery down.");
			Cmd("hermod_compress", "hermod_compress <on|off|status> - packet compression. Takes effect immediately for everyone connected.");
			Cmd("hermod_queue", "hermod_queue <bytes|off|status> - the per-player ZDO send budget. The game's is 10240; raising it is what bail_ceiling in the logs is arguing for.");
			Cmd("hermod_probe", "hermod_probe <on|off> - the detailed timing patches. Leaves the rest of the measurement running.");
			Cmd("hermod_reload", "hermod_reload - re-read the config file from disk and apply what can be applied live.");
			Cmd("hermod_save", "hermod_save - write the settings currently in force back to the config file.");
			Cmd("hermod_panic", "hermod_panic - put every setting back the way the game had it, right now. Measurement keeps running.");
		}

		private static void Cmd(string name, string help)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Expected O, but got Unknown
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			new ConsoleCommand(name, help, (ConsoleEvent)delegate(ConsoleEventArgs args)
			{
				try
				{
					foreach (string item in Dispatch((name + " " + string.Join(" ", args.Args, 1, Math.Max(0, args.Args.Length - 1))).Trim(), args.Context))
					{
						Terminal context = args.Context;
						if (context != null)
						{
							context.AddString(item);
						}
					}
				}
				catch (Exception ex)
				{
					Safety.Failed(name, ex);
					Terminal context2 = args.Context;
					if (context2 != null)
					{
						context2.AddString("Hermod: " + name + " failed. See the log.");
					}
				}
			}, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}

		private static List<string> Dispatch(string line, Terminal context)
		{
			List<string> list = new List<string>();
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				list.Add("Hermod: load into a world first.");
				return list;
			}
			if (instance.IsServer())
			{
				return RunOnServer(line);
			}
			if (!instance.LocalPlayerIsAdminOrHost())
			{
				list.Add("Hermod: you need to be an admin on this server to do that.");
				return list;
			}
			list.Add(Bridge.Send(line) ? "Hermod: asked the server. The answer will appear here in a moment." : "Hermod: could not reach the server.");
			return list;
		}

		internal static List<string> RunOnServer(string line)
		{
			List<string> list = new List<string>();
			string[] array = (line ?? string.Empty).Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
			if (array.Length == 0)
			{
				list.Add("Hermod: nothing to do.");
				return list;
			}
			string text = array[0].ToLowerInvariant();
			string text2 = ((array.Length > 1) ? array[1].ToLowerInvariant() : string.Empty);
			string text3 = ((array.Length > 1) ? string.Join(" ", array, 1, array.Length - 1) : string.Empty);
			switch (text)
			{
			case "hermod_status":
				list.AddRange(Sampler.StatusLines());
				break;
			case "hermod_peers":
				list.AddRange(Sampler.PeerLines());
				break;
			case "hermod_report":
				list.Add(Sampler.FlushReport());
				break;
			case "hermod_mark":
				if (string.IsNullOrEmpty(text3))
				{
					list.Add("Hermod: give the mark a label, for example: hermod_mark everyone at base");
					break;
				}
				Sampler.Mark(text3);
				list.Add("Hermod: marked '" + text3 + "'. Everything from here is a new bucket.");
				break;
			case "hermod_profile":
				list.AddRange(Profile(text2));
				break;
			case "hermod_relay":
				list.AddRange(RelayCommand(text2));
				break;
			case "hermod_net":
				list.Add(Tuning.StatusLine());
				if (text2 == "apply")
				{
					list.Add(Tuning.ApplyToLivePeers());
				}
				else
				{
					list.Add("Hermod: add 'apply' to re-assert these on every live connection.");
				}
				break;
			case "hermod_fps":
				list.AddRange(FpsCommand(text2));
				break;
			case "hermod_compress":
				list.AddRange(CompressCommand(text2));
				break;
			case "hermod_queue":
				list.AddRange(QueueCommand(text2));
				break;
			case "hermod_probe":
				if (text2 == "on")
				{
					Probes.Enabled = true;
				}
				else
				{
					if (!(text2 == "off"))
					{
						list.Add("Hermod: hermod_probe on, or hermod_probe off.");
						break;
					}
					Probes.Enabled = false;
				}
				list.Add("Hermod: detailed timing probes " + (Probes.Enabled ? "on" : "off") + ". Frame time, cpu, zdo counts and the per-player numbers carry on either way.");
				break;
			case "hermod_reload":
				list.AddRange(Reload());
				break;
			case "hermod_save":
				list.Add(HermodPlugin.SaveConfig());
				break;
			case "hermod_panic":
				list.AddRange(Panic());
				break;
			default:
				list.Add("Hermod: no such command, " + text + ".");
				break;
			}
			return list;
		}

		private static List<string> Profile(string which)
		{
			List<string> list = new List<string>();
			switch (which)
			{
			case "vanilla":
				list.Add(Relay.Disable("switching to the vanilla profile"));
				list.Add(Tuning.RestoreVanillaSteam());
				HermodPlugin.Cfg.TargetFrameRate = -1;
				list.Add(Tuning.ApplyFrameRate("the vanilla profile"));
				Sampler.Mark("profile:vanilla");
				list.Add("Hermod: profile VANILLA. Everything is as the game shipped it, and measurement is still running. This is the baseline to compare against.");
				break;
			case "tier1":
				list.Add(Relay.Disable("switching to the tier1 profile"));
				list.Add(Tuning.ApplySteam("the tier1 profile"));
				list.Add(Tuning.ApplyToLivePeers());
				HermodPlugin.Cfg.TargetFrameRate = 0;
				list.Add(Tuning.ApplyFrameRate("the tier1 profile"));
				Sampler.Mark("profile:tier1");
				list.Add("Hermod: profile TIER1. Steam send rate and Nagle changed; the game is still driving its own ZDO sends. This is the shipped default.");
				break;
			case "relay":
				list.Add(Tuning.ApplySteam("the relay profile"));
				list.Add(Tuning.ApplyToLivePeers());
				HermodPlugin.Cfg.TargetFrameRate = 0;
				list.Add(Relay.Enable(clearLatch: true));
				list.Add(Tuning.ApplyFrameRate("the relay profile"));
				Sampler.Mark("profile:relay");
				list.Add("Hermod: profile RELAY. Watch chat. If anybody says anything got worse, hermod_panic puts it all back in one go.");
				break;
			default:
				list.Add("Hermod: hermod_profile vanilla, tier1, or relay.");
				list.Add("  vanilla - nothing changed, measurement only. The baseline.");
				list.Add("  tier1   - Steam send rate and Nagle. The shipped default.");
				list.Add("  relay   - tier1 plus the ZDO send rewrite.");
				break;
			}
			return list;
		}

		private static List<string> RelayCommand(string arg)
		{
			List<string> list = new List<string>();
			switch (arg)
			{
			default:
				if (arg.Length == 0)
				{
					goto case "status";
				}
				goto case null;
			case "on":
				list.Add(Relay.Enable(clearLatch: true));
				Sampler.Mark("relay:on");
				break;
			case "off":
				list.Add(Relay.Disable("asked to"));
				Sampler.Mark("relay:off");
				break;
			case "status":
				list.Add(Relay.StatusLine());
				list.Add("  " + Relay.DebugLine());
				break;
			case null:
				list.Add("Hermod: hermod_relay on, off, or status.");
				break;
			}
			return list;
		}

		private static List<string> CompressCommand(string arg)
		{
			List<string> list = new List<string>();
			if (!string.IsNullOrEmpty(arg))
			{
				int num;
				switch (arg)
				{
				case "status":
					break;
				case "on":
					HermodPlugin.Cfg.CompressionEnabled = true;
					if (!Compression.Ready)
					{
						Compression.Initialize();
					}
					goto IL_0095;
				case "off":
					HermodPlugin.Cfg.CompressionEnabled = false;
					goto IL_0095;
				default:
					{
						list.Add("Hermod: hermod_compress on, or off.");
						list.Add("  " + CompressionLink.StatusLine());
						return list;
					}
					IL_0095:
					num = CompressionLink.Resettle("hermod_compress " + arg);
					list.Add(CompressionLink.StatusLine());
					list.Add("Hermod: re-negotiated with " + num + " connected peer(s). Nothing was written to disk - hermod_save keeps it.");
					Sampler.Mark("compress:" + arg);
					return list;
				}
			}
			list.Add(CompressionLink.StatusLine());
			list.Add("hermod_compress on, or off. Takes effect immediately for everyone connected.");
			return list;
		}

		private static List<string> QueueCommand(string arg)
		{
			List<string> list = new List<string>();
			if (!string.IsNullOrEmpty(arg))
			{
				switch (arg)
				{
				case "status":
					break;
				case "off":
				case "vanilla":
					HermodPlugin.Cfg.InFlightCeiling = 0;
					goto IL_00a3;
				default:
					{
						if (int.TryParse(arg, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
						{
							HermodPlugin.Cfg.InFlightCeiling = result;
							HermodPlugin.Cfg.Clamp();
							goto IL_00a3;
						}
						list.Add("Hermod: hermod_queue <bytes>, or off. For example 32768.");
						list.Add("  " + Ceiling.StatusLine());
						return list;
					}
					IL_00a3:
					list.Add(Ceiling.StatusLine());
					list.Add("Hermod: this takes effect on the next send, no restart needed. Watch bail_ceiling drop - and watch ping, because every message shares one ordered stream and a bigger backlog delays chat and player positions too.");
					Sampler.Mark("queue:" + arg);
					return list;
				}
			}
			list.Add(Ceiling.StatusLine());
			list.Add("hermod_queue <bytes> to raise it, or hermod_queue off for the game's own value.");
			return list;
		}

		private static List<string> FpsCommand(string arg)
		{
			List<string> list = new List<string>();
			switch (arg)
			{
			case "auto":
				HermodPlugin.Cfg.TargetFrameRate = 0;
				break;
			case "off":
			case "uncapped":
				HermodPlugin.Cfg.TargetFrameRate = -1;
				break;
			default:
			{
				if (int.TryParse(arg, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
				{
					HermodPlugin.Cfg.TargetFrameRate = result;
					HermodPlugin.Cfg.Clamp();
					break;
				}
				list.Add("Hermod: hermod_fps <number>, or auto, or off.");
				list.Add("  " + Tuning.StatusLine());
				return list;
			}
			}
			list.Add(Tuning.ApplyFrameRate("asked to"));
			Sampler.Mark("fps:" + arg);
			return list;
		}

		private static List<string> Reload()
		{
			List<string> list = new List<string>();
			string text = HermodPlugin.ReloadConfig();
			if (text != null)
			{
				list.Add("Hermod: " + text);
				return list;
			}
			list.Add("Hermod: re-read gbv.valheim.hermod.json.");
			list.Add(Tuning.ApplySteam("the config was reloaded"));
			list.Add(Tuning.ApplyFrameRate("the config was reloaded"));
			list.Add(Relay.Enabled ? "Hermod: the relay is still running; hermod_relay off stops it." : "Hermod: the relay is still off. A reload never starts it - use hermod_relay on.");
			Sampler.Mark("config reloaded");
			return list;
		}

		private static List<string> Panic()
		{
			List<string> obj = new List<string>
			{
				Relay.Disable("hermod_panic"),
				Tuning.RestoreVanillaSteam()
			};
			HermodPlugin.Cfg.CompressionEnabled = false;
			obj.Add("Hermod: compression off for " + CompressionLink.Resettle("hermod_panic") + " peer(s).");
			HermodPlugin.Cfg.InFlightCeiling = 0;
			obj.Add(Ceiling.StatusLine());
			HermodPlugin.Cfg.ZoneGenBudgetMs = 0;
			obj.Add(ZoneGuard.StatusLine());
			HermodPlugin.Cfg.TargetFrameRate = -1;
			obj.Add(Tuning.ApplyFrameRate("hermod_panic"));
			Sampler.Mark("PANIC");
			obj.Add("Hermod: everything is back the way the game had it. Measurement is still running, so whatever went wrong is in the session log. Nothing was written to disk, so a restart comes back to the configured settings.");
			HermodPlugin.Log.LogWarning((object)"Hermod: hermod_panic was run. Every setting is back to the game's own values and the relay is off. Measurement continues.");
			return obj;
		}
	}
	internal static class Compression
	{
		internal static class Stats
		{
			internal static long PacketsSent;

			internal static long BytesBefore;

			internal static long BytesAfter;

			internal static long PacketsGrown;

			internal static long PacketsReceived;

			internal static long BytesReceivedFramed;

			internal static long BytesReceivedPlain;

			internal static double SentRatio
			{
				get
				{
					if (BytesBefore > 0)
					{
						return 100.0 * (double)BytesAfter / (double)BytesBefore;
					}
					return 0.0;
				}
			}

			internal static void NoteSent(int before, int after, bool compressed)
			{
				PacketsSent++;
				BytesBefore += before;
				BytesAfter += after;
				if (!compressed)
				{
					PacketsGrown++;
				}
			}

			internal static void NoteReceived(int framed, int plain)
			{
				PacketsReceived++;
				BytesReceivedFramed += framed;
				BytesReceivedPlain += plain;
			}

			internal static void Reset()
			{
				PacketsSent = 0L;
				BytesBefore = 0L;
				BytesAfter = 0L;
				PacketsGrown = 0L;
				PacketsReceived = 0L;
				BytesReceivedFramed = 0L;
				BytesReceivedPlain = 0L;
			}
		}

		private const string DictionaryResource = "GBV.Hermod.dict.small";

		private static readonly byte[] Magic = new byte[4] { 72, 82, 77, 49 };

		private const int HeaderSize = 5;

		private const byte RawPayload = 0;

		private const byte CompressedPayload = 1;

		internal const int ProtocolVersion = 2;

		private static Compressor _compressor;

		private static Decompressor _decompressor;

		private static readonly object Sync = new object();

		internal static bool Ready { get; private set; }

		internal static string Unavailable { get; private set; } = "not initialised yet";

		internal static void Initialize()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			if (Ready)
			{
				return;
			}
			try
			{
				byte[] array = ReadDictionary();
				Compressor val = new Compressor(HermodPlugin.Cfg.CompressionLevel);
				val.LoadDictionary(array);
				Decompressor val2 = new Decompressor();
				val2.LoadDictionary(array);
				lock (Sync)
				{
					_compressor = val;
					_decompressor = val2;
				}
				Ready = true;
				Unavailable = null;
				SelfTest();
				HermodPlugin.Log.LogInfo((object)("Hermod: compression ready - Zstandard level " + HermodPlugin.Cfg.CompressionLevel + " with a " + array.Length / 1024 + " KB trained dictionary."));
			}
			catch (Exception ex)
			{
				Ready = false;
				Unavailable = ex.Message;
				_compressor = null;
				_decompressor = null;
				HermodPlugin.Log.LogError((object)("Hermod: compression could not start (" + ex.Message + "), so it stays OFF for this session and nothing is framed. Everything else works. If ZstdSharp.dll is missing from the plugin folder, that is the cause.\n" + ex));
			}
		}

		private static byte[] ReadDictionary()
		{
			using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("GBV.Hermod.dict.small");
			if (stream == null)
			{
				throw new InvalidOperationException("the embedded Zstandard dictionary (GBV.Hermod.dict.small) is missing from Hermod.dll");
			}
			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("the embedded Zstandard dictionary is truncated");
				}
			}
			return array;
		}

		private static void SelfTest()
		{
			byte[] array = new byte[1024];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = (byte)(i * 31);
			}
			byte[] array2 = Encode(array);
			if (!TryDecode(array2, out var output))
			{
				throw new InvalidDataException("the compression self test could not decode its own frame");
			}
			if (output.Length != array.Length)
			{
				throw new InvalidDataException("the compression self test round-tripped the wrong length");
			}
			for (int j = 0; j < array.Length; j++)
			{
				if (output[j] != array[j])
				{
					throw new InvalidDataException("the compression self test round-tripped the wrong bytes");
				}
			}
			if (array2 != Encode(array2))
			{
				throw new InvalidDataException("the compression duplicate-frame guard did not hold");
			}
		}

		internal static byte[] Encode(byte[] input)
		{
			if (input == null || !Ready)
			{
				return input;
			}
			if (HasHeader(input))
			{
				return input;
			}
			byte[] array;
			lock (Sync)
			{
				if (_compressor == null)
				{
					return input;
				}
				array = _compressor.Wrap((ReadOnlySpan<byte>)input).ToArray();
			}
			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] = (flag ? ((byte)1) : ((byte)0));
			Buffer.BlockCopy(array2, 0, array3, 5, array2.Length);
			Stats.NoteSent(input.Length, array3.Length, flag);
			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:
				if (!Ready)
				{
					throw new InvalidDataException("a compressed frame arrived but compression is not available here");
				}
				lock (Sync)
				{
					if (_decompressor == null)
					{
						throw new InvalidDataException("the decompressor is gone");
					}
					output = _decompressor.Unwrap((ReadOnlySpan<byte>)array, int.MaxValue).ToArray();
				}
				break;
			default:
				throw new InvalidDataException("unknown Hermod frame type " + b);
			}
			Stats.NoteReceived(input.Length, output.Length);
			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 string StatusLine()
		{
			if (!Ready)
			{
				return "compression: off (" + (Unavailable ?? "unknown") + ")";
			}
			if (Stats.PacketsSent == 0L)
			{
				return "compression: ready, nothing framed yet";
			}
			return "compression: " + Stats.PacketsSent + " packets, " + Writer.Num((double)Stats.BytesBefore / 1024.0, 0) + " KB -> " + Writer.Num((double)Stats.BytesAfter / 1024.0, 0) + " KB (" + Writer.Num(Stats.SentRatio, 0) + "%), " + Stats.PacketsGrown + " left uncompressed";
		}
	}
	internal static class CompressionLink
	{
		internal sealed class PeerState
		{
			internal int Version;

			internal bool WantsIt;

			internal volatile bool Sending;

			internal volatile bool Receiving;

			internal volatile bool WarnedUnframed;

			internal string Name = "?";
		}

		private const string RpcVersion = "GBV_Hermod_CompVersion";

		private const string RpcEnabled = "GBV_Hermod_CompEnabled";

		private const string RpcStarted = "GBV_Hermod_CompStarted";

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

		private static bool _applied;

		internal static bool Active
		{
			get
			{
				if (Compression.Ready && HermodPlugin.Cfg.CompressionEnabled && !Interop.SuppressNetworkChanges)
				{
					if (HermodPlugin.MeasureOnly != null)
					{
						return !HermodPlugin.MeasureOnly.Value;
					}
					return true;
				}
				return false;
			}
		}

		internal static int LinkedPeers
		{
			get
			{
				int num = 0;
				foreach (KeyValuePair<ISocket, PeerState> peer in Peers)
				{
					if (peer.Value.Sending)
					{
						num++;
					}
				}
				return num;
			}
		}

		internal static string Apply(Harmony harmony)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Expected O, but got Unknown
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Expected O, but got Unknown
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Expected O, but got Unknown
			if (_applied)
			{
				return null;
			}
			try
			{
				Type typeFromHandle = typeof(CompressionLink);
				harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(ZNet), "OnNewConnection", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "OnNewConnectionPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(ZNet), "Disconnect", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "OnDisconnectPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(ZSteamSocket), "Send", new Type[1] { typeof(ZPackage) }, (Type[])null), new HarmonyMethod(typeFromHandle, "SendPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(ZSteamSocket), "Recv", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "RecvPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				_applied = true;
				return null;
			}
			catch (Exception ex)
			{
				return "compression hooks (" + ex.Message + ")";
			}
		}

		internal static void Clear()
		{
			Peers.Clear();
			Compression.Stats.Reset();
		}

		internal static int Resettle(string why)
		{
			int num = 0;
			try
			{
				ZNet instance = ZNet.instance;
				if ((Object)(object)instance == (Object)null)
				{
					return 0;
				}
				List<ZNetPeer> peers = instance.GetPeers();
				if (peers == null)
				{
					return 0;
				}
				foreach (ZNetPeer item in peers)
				{
					if (item?.m_rpc == null)
					{
						continue;
					}
					ZSteamSocket val = Reflect.UnwrapSteamSocket(item.m_socket);
					if (val != null && Peers.TryGetValue((ISocket)(object)val, out var value))
					{
						bool sending = value.Sending;
						Settle(item, value);
						if (value.Sending != sending)
						{
							num++;
						}
					}
				}
				if (num > 0)
				{
					HermodPlugin.Log.LogInfo((object)("Hermod: compression re-negotiated with " + num + " peer(s) - " + why + "."));
				}
			}
			catch (Exception ex)
			{
				Safety.Noted("re-negotiating compression", ex);
			}
			return num;
		}

		private static void OnNewConnectionPostfix(ZNetPeer peer)
		{
			try
			{
				if (peer?.m_rpc != null)
				{
					ZSteamSocket val = Reflect.UnwrapSteamSocket(peer.m_socket);
					if (val != null)
					{
						PeerState value = new PeerState
						{
							Name = DescribePeer(peer)
						};
						Peers[(ISocket)(object)val] = value;
						peer.m_rpc.Register<int>("GBV_Hermod_CompVersion", (Action<ZRpc, int>)ReceiveVersion);
						peer.m_rpc.Register<bool>("GBV_Hermod_CompEnabled", (Action<ZRpc, bool>)ReceiveEnabled);
						peer.m_rpc.Register<bool>("GBV_Hermod_CompStarted", (Action<ZRpc, bool>)ReceiveStarted);
						peer.m_rpc.Invoke("GBV_Hermod_CompVersion", new object[1] { 2 });
					}
				}
			}
			catch (Exception ex)
			{
				Safety.Failed("ZNet.OnNewConnection compression handshake", ex);
			}
		}

		private static void OnDisconnectPostfix(ZNetPeer peer)
		{
			try
			{
				ZSteamSocket val = Reflect.UnwrapSteamSocket(peer?.m_socket);
				if (val != null)
				{
					Peers.TryRemove((ISocket)(object)val, out var _);
				}
			}
			catch (Exception ex)
			{
				Safety.Failed("ZNet.Disconnect compression cleanup", ex);
			}
		}

		private static void ReceiveVersion(ZRpc rpc, int version)
		{
			try
			{
				if (TryFind(rpc, out var peer, out var state))
				{
					state.Version = version;
					if (version != 2)
					{
						HermodPlugin.Log.LogWarning((object)("Hermod: " + state.Name + " speaks compression protocol " + version + " and this end speaks " + 2 + ", so traffic with them stays uncompressed. Same Hermod version on both ends fixes it."));
					}
					else
					{
						peer.m_rpc.Invoke("GBV_Hermod_CompEnabled", new object[1] { Active });
						Settle(peer, state);
					}
				}
			}
			catch (Exception ex)
			{
				Safety.Failed("compression version exchange", ex);
			}
		}

		private static void ReceiveEnabled(ZRpc rpc, bool enabled)
		{
			try
			{
				if (TryFind(rpc, out var peer, out var state))
				{
					state.WantsIt = enabled;
					Settle(peer, state);
				}
			}
			catch (Exception ex)
			{
				Safety.Failed("compression enable exchange", ex);
			}
		}

		private static void ReceiveStarted(ZRpc rpc, bool started)
		{
			try
			{
				if (TryFind(rpc, out var _, out var state))
				{
					state.Receiving = started;
					state.WarnedUnframed = false;
					HermodPlugin.Log.LogInfo((object)("Hermod: " + state.Name + " is " + (started ? "now" : "no longer") + " compressing what it sends here."));
				}
			}
			catch (Exception ex)
			{
				Safety.Failed("compression start exchange", ex);
			}
		}

		private static void Settle(ZNetPeer peer, PeerState state)
		{
			bool flag = Active && state.WantsIt && state.Version == 2;
			if (state.Sending == flag)
			{
				return;
			}
			peer.m_rpc.Invoke("GBV_Hermod_CompStarted", new object[1] { flag });
			try
			{
				ISocket socket = peer.m_socket;
				if (socket != null)
				{
					socket.Flush();
				}
			}
			catch (Exception ex)
			{
				Safety.Noted("flushing a socket before switching compression", ex);
			}
			state.Sending = flag;
			HermodPlugin.Log.LogInfo((object)("Hermod: compression to " + state.Name + " is " + (flag ? "ON" : "off") + "."));
		}

		private 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
			try
			{
				if (pkg != null && Peers.TryGetValue((ISocket)(object)__instance, out var value) && value.Sending)
				{
					byte[] array = Compression.Encode(pkg.GetArray());
					pkg = new ZPackage(array);
				}
			}
			catch (Exception ex)
			{
				Safety.Failed("ZSteamSocket.Send compression", ex);
			}
		}

		private static void RecvPostfix(ZSteamSocket __instance, ref ZPackage __result)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			try
			{
				if (__result == null || !Peers.TryGetValue((ISocket)(object)__instance, out var value))
				{
					return;
				}
				if (Compression.TryDecode(__result.GetArray(), out var output))
				{
					__result = new ZPackage(output);
					value.WarnedUnframed = false;
					if (!value.Receiving)
					{
						value.Receiving = true;
						HermodPlugin.Log.LogWarning((object)("Hermod: a compressed packet from " + value.Name + " arrived before the handshake finished. It was decoded correctly; no data was lost."));
					}
				}
				else if (value.Receiving && !value.WarnedUnframed)
				{
					value.WarnedUnframed = true;
					HermodPlugin.Log.LogInfo((object)("Hermod: an uncompressed packet arrived from " + value.Name + " while compression was on. Passed through untouched."));
				}
			}
			catch (Exception ex)
			{
				Safety.Failed("ZSteamSocket.Recv decompression", ex);
				__result = null;
			}
		}

		private static bool TryFind(ZRpc rpc, out ZNetPeer peer, out PeerState state)
		{
			peer = null;
			state = null;
			if (rpc == null || (Object)(object)ZNet.instance == (Object)null)
			{
				return false;
			}
			List<ZNetPeer> peers = ZNet.instance.GetPeers();
			if (peers == null)
			{
				return false;
			}
			foreach (ZNetPeer item in peers)
			{
				if (item?.m_rpc == rpc)
				{
					ZSteamSocket val = Reflect.UnwrapSteamSocket(item.m_socket);
					if (val == null || !Peers.TryGetValue((ISocket)(object)val, out state))
					{
						return false;
					}
					peer = item;
					return true;
				}
			}
			return false;
		}

		private static string DescribePeer(ZNetPeer peer)
		{
			try
			{
				if (peer == null)
				{
					return "?";
				}
				if (peer.m_server)
				{
					return "the server";
				}
				string obj = (string.IsNullOrEmpty(peer.m_playerName) ? "a player" : peer.m_playerName);
				ISocket socket = peer.m_socket;
				return obj + " (" + (((socket != null) ? socket.GetHostName() : null) ?? "?") + ")";
			}
			catch
			{
				return "?";
			}
		}

		internal static string StatusLine()
		{
			if (!Active)
			{
				if (!HermodPlugin.Cfg.CompressionEnabled)
				{
					return "compression: disabled by config";
				}
				if (Interop.SuppressNetworkChanges)
				{
					return "compression: standing down, " + Interop.Summary();
				}
				if (!Compression.Ready)
				{
					return "compression: off (" + (Compression.Unavailable ?? "unknown") + ")";
				}
				return "compression: off";
			}
			return Compression.StatusLine() + "; linked to " + LinkedPeers + " of " + Peers.Count + " peer(s)";
		}
	}
	internal static class Ghosts
	{
		private const float TeleportMetres = 64f;

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

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

		private static readonly HashSet<long> Reported = new HashSet<long>();

		private static bool _warnedUnavailable;

		internal static long Teleports { get; private set; }

		internal static int WorstStranded { get; private set; }

		internal static long SessionTeleports { get; private set; }

		internal static int SessionWorstStranded { get; private set; }

		internal static int Tracked { get; private set; }

		internal static float SessionFurthestMove { get; private set; }

		private static long Pair(ZNetPeer observer, ZNetPeer subject)
		{
			return (observer.m_uid * 31) ^ subject.m_uid;
		}

		internal static void ResetMinute()
		{
			Teleports = 0L;
			WorstStranded = 0;
		}

		internal static void Clear()
		{
			LastSeen.Clear();
			Reported.Clear();
			SessionTeleports = 0L;
			SessionWorstStranded = 0;
			ResetMinute();
		}

		internal static void Tick()
		{
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: 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)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				ZNet instance = ZNet.instance;
				ZDOMan instance2 = ZDOMan.instance;
				if ((Object)(object)instance == (Object)null || instance2 == null || !instance.IsServer())
				{
					return;
				}
				IList list = Reflect.ZdoPeers(instance2);
				if (list == null)
				{
					return;
				}
				List<ZNetPeer> peers = instance.GetPeers();
				if (peers == null || peers.Count == 0)
				{
					LastSeen.Clear();
					return;
				}
				int num = 0;
				int num2 = 0;
				foreach (ZNetPeer item in peers)
				{
					if (item == null || item.m_characterID == ZDOID.None)
					{
						continue;
					}
					ZDO zDO = instance2.GetZDO(item.m_characterID);
					if (zDO == null)
					{
						continue;
					}
					Vector3 position = zDO.GetPosition();
					num2++;
					if (LastSeen.TryGetValue(item.m_characterID, out var value))
					{
						float num3 = Vector3.Distance(value, position);
						if (num3 > SessionFurthestMove)
						{
							SessionFurthestMove = num3;
						}
						if (num3 >= 64f)
						{
							Teleports++;
							SessionTeleports++;
							NoteTeleport(item, value, position);
						}
					}
					LastSeen[item.m_characterID] = position;
					num += CountStrandedRecords(list, item, position);
				}
				Tracked = num2;
				if (num > WorstStranded)
				{
					WorstStranded = num;
				}
				if (num > SessionWorstStranded)
				{
					SessionWorstStranded = num;
				}
				DropDepartedPlayers(peers);
			}
			catch (Exception ex)
			{
				Safety.Noted("looking for portal ghosts", ex);
			}
		}

		private static int CountStrandedRecords(IList zdoPeers, ZNetPeer subject, Vector3 pos)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: 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)
			int num = 0;
			foreach (object zdoPeer in zdoPeers)
			{
				ZNetPeer val = Reflect.PeerOf(zdoPeer);
				if (val == null || val.m_uid == subject.m_uid)
				{
					continue;
				}
				if (ZNetScene.InActiveArea(pos, val.GetRefPos()))
				{
					Reported.Remove(Pair(val, subject));
					continue;
				}
				IDictionary dictionary = Reflect.PeerKnownZdos(zdoPeer);
				if (dictionary == null)
				{
					if (!_warnedUnavailable)
					{
						_warnedUnavailable = true;
						HermodPlugin.Log.LogWarning((object)"Hermod: ZDOMan.ZDOPeer.m_zdos could not be read in this build of the game, so the portal ghost detector is off. Everything else in the session log is unaffected.");
					}
					return 0;
				}
				long item = Pair(val, subject);
				if (!dictionary.Contains(subject.m_characterID))
				{
					Reported.Remove(item);
					continue;
				}
				num++;
				if (Reported.Add(item))
				{
					HermodPlugin.Log.LogWarning((object)("Hermod: GHOST - " + Describe(val) + " still holds " + Describe(subject) + "'s character record, but that player is " + Writer.Num(Vector3.Distance(pos, val.GetRefPos()), 0) + " m away and outside their active area. Their client was never told to drop the object, so it is standing there."));
				}
			}
			return num;
		}

		private static void NoteTeleport(ZNetPeer peer, Vector3 from, Vector3 to)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			HermodPlugin.Log.LogInfo((object)("Hermod: " + Describe(peer) + " moved " + Writer.Num(Vector3.Distance(from, to), 0) + " m in a quarter second - a teleport. Watching for stranded records on the other clients."));
		}

		private static void DropDepartedPlayers(List<ZNetPeer> peers)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			if (LastSeen.Count <= peers.Count)
			{
				return;
			}
			Forget.Clear();
			foreach (KeyValuePair<ZDOID, Vector3> item in LastSeen)
			{
				bool flag = false;
				foreach (ZNetPeer peer in peers)
				{
					if (peer != null && peer.m_characterID == item.Key)
					{
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					Forget.Add(item.Key);
				}
			}
			foreach (ZDOID item2 in Forget)
			{
				LastSeen.Remove(item2);
			}
		}

		private static string Describe(ZNetPeer peer)
		{
			if (peer == null)
			{
				return "?";
			}
			if (!string.IsNullOrEmpty(peer.m_playerName))
			{
				return peer.m_playerName;
			}
			return "a player";
		}

		internal static string StatusLine()
		{
			return "ghosts: " + Teleports + " teleport(s) this minute, " + WorstStranded + " stranded record(s) at worst";
		}

		internal static string SessionLine()
		{
			string text = "tracking " + Tracked + " player(s), furthest move " + Writer.Num(SessionFurthestMove, 0) + " m, teleport threshold " + 64 + " m";
			if (SessionWorstStranded > 0)
			{
				return "PORTAL GHOSTS ARE REAL HERE - " + SessionTeleports + " teleport(s) so far and up to " + SessionWorstStranded + " client(s) left holding a player who had gone. Search the log for GHOST. (" + text + ")";
			}
			return "ghosts: none, over " + SessionTeleports + " teleport(s) (" + text + ")";
		}
	}
	internal sealed class HermodConfig
	{
		internal const int CurrentVersion = 1;

		internal const string FileName = "gbv.valheim.hermod.json";

		internal const int VanillaSendRate = 153600;

		internal const int VanillaInFlightCeiling = 10240;

		internal int Version = 1;

		internal bool ApplySteamSettings = true;

		internal int NagleTimeMicros;

		internal int SendRateMin = 153600;

		internal int SendRateMax = 1048576;

		internal int SendBufferSize;

		internal int MaxTotalUploadKiBPerSec;

		internal int TargetFrameRate;

		internal int RelayFrameRateCap = 60;

		internal bool RelayEnabled;

		internal double RelayTargetHz = 20.0;

		internal double RelayAbortFrameMs = 250.0;

		internal int InFlightCeiling;

		internal int ZoneGenBudgetMs = 50;

		internal bool CompressionEnabled = true;

		internal int CompressionLevel = 1;

		internal bool FixZdoExtraDataLeak;

		internal bool ProbesEnabled = true;

		internal bool WriteCsv = true;

		internal bool WriteJson = true;

		internal int ReportMinutes = 5;

		internal double SlowFrameMs = 100.0;

		internal double SlowZoneGenMs = 50.0;

		internal long CsvMaxBytes = 8388608L;

		internal bool DeferToOtherNetworkMods = true;

		internal const string DefaultBetterNetworkingGuid = "DIT.BetterNetworking10";

		internal string BetterNetworkingGuid = "DIT.BetterNetworking10";

		private JsonValue _raw;

		internal static string PathIn(string configDir)
		{
			return Path.Combine(configDir, "gbv.valheim.hermod.json");
		}

		internal static HermodConfig Defaults()
		{
			return new HermodConfig();
		}

		internal string ToJson()
		{
			JsonValue obj = ((_raw != null && _raw.ValueKind == JsonValue.Kind.Object) ? Clone(_raw) : JsonValue.NewObject());
			obj.Set("version", Version);
			obj.Set("applySteamSettings", ApplySteamSettings);
			obj.Set("nagleTimeMicros", NagleTimeMicros);
			obj.Set("sendRateMin", SendRateMin);
			obj.Set("sendRateMax", SendRateMax);
			obj.Set("sendBufferSize", SendBufferSize);
			obj.Set("maxTotalUploadKiBPerSec", MaxTotalUploadKiBPerSec);
			obj.Set("targetFrameRate", TargetFrameRate);
			obj.Set("relayFrameRateCap", RelayFrameRateCap);
			obj.Set("relayEnabled", RelayEnabled);
			obj.Set("relayTargetHz", RelayTargetHz);
			obj.Set("relayAbortFrameMs", RelayAbortFrameMs);
			obj.Set("inFlightCeiling", InFlightCeiling);
			obj.Set("zoneGenBudgetMs", ZoneGenBudgetMs);
			obj.Set("compressionEnabled", CompressionEnabled);
			obj.Set("compressionLevel", CompressionLevel);
			obj.Set("fixZdoExtraDataLeak", FixZdoExtraDataLeak);
			obj.Set("probesEnabled", ProbesEnabled);
			obj.Set("writeCsv", WriteCsv);
			obj.Set("writeJson", WriteJson);
			obj.Set("reportMinutes", ReportMinutes);
			obj.Set("slowFrameMs", SlowFrameMs);
			obj.Set("slowZoneGenMs", SlowZoneGenMs);
			obj.Set("csvMaxBytes", (double)CsvMaxBytes);
			obj.Set("deferToOtherNetworkMods", DeferToOtherNetworkMods);
			obj.Set("betterNetworkingGuid", BetterNetworkingGuid);
			return obj.ToPrettyString();
		}

		internal static HermodConfig FromJson(string text)
		{
			JsonValue jsonValue = JsonValue.Parse(text);
			if (jsonValue.ValueKind != JsonValue.Kind.Object)
			{
				throw new JsonException("The config file must be a JSON object.");
			}
			HermodConfig hermodConfig = new HermodConfig
			{
				_raw = jsonValue
			};
			hermodConfig.Version = jsonValue["version"].AsInt(1);
			hermodConfig.ApplySteamSettings = jsonValue["applySteamSettings"].AsBool(hermodConfig.ApplySteamSettings);
			hermodConfig.NagleTimeMicros = jsonValue["nagleTimeMicros"].AsInt(hermodConfig.NagleTimeMicros);
			hermodConfig.SendRateMin = jsonValue["sendRateMin"].AsInt(hermodConfig.SendRateMin);
			hermodConfig.SendRateMax = jsonValue["sendRateMax"].AsInt(hermodConfig.SendRateMax);
			hermodConfig.SendBufferSize = jsonValue["sendBufferSize"].AsInt(hermodConfig.SendBufferSize);
			hermodConfig.MaxTotalUploadKiBPerSec = jsonValue["maxTotalUploadKiBPerSec"].AsInt(hermodConfig.MaxTotalUploadKiBPerSec);
			hermodConfig.TargetFrameRate = jsonValue["targetFrameRate"].AsInt(hermodConfig.TargetFrameRate);
			hermodConfig.RelayFrameRateCap = jsonValue["relayFrameRateCap"].AsInt(hermodConfig.RelayFrameRateCap);
			hermodConfig.RelayEnabled = jsonValue["relayEnabled"].AsBool(hermodConfig.RelayEnabled);
			hermodConfig.RelayTargetHz = jsonValue["relayTargetHz"].AsDouble(hermodConfig.RelayTargetHz);
			hermodConfig.RelayAbortFrameMs = jsonValue["relayAbortFrameMs"].AsDouble(hermodConfig.RelayAbortFrameMs);
			hermodConfig.InFlightCeiling = jsonValue["inFlightCeiling"].AsInt(hermodConfig.InFlightCeiling);
			hermodConfig.ZoneGenBudgetMs = jsonValue["zoneGenBudgetMs"].AsInt(hermodConfig.ZoneGenBudgetMs);
			hermodConfig.CompressionEnabled = jsonValue["compressionEnabled"].AsBool(hermodConfig.CompressionEnabled);
			hermodConfig.CompressionLevel = jsonValue["compressionLevel"].AsInt(hermodConfig.CompressionLevel);
			hermodConfig.FixZdoExtraDataLeak = jsonValue["fixZdoExtraDataLeak"].AsBool(hermodConfig.FixZdoExtraDataLeak);
			hermodConfig.ProbesEnabled = jsonValue["probesEnabled"].AsBool(hermodConfig.ProbesEnabled);
			hermodConfig.WriteCsv = jsonValue["writeCsv"].AsBool(hermodConfig.WriteCsv);
			hermodConfig.WriteJson = jsonValue["writeJson"].AsBool(hermodConfig.WriteJson);
			hermodConfig.ReportMinutes = jsonValue["reportMinutes"].AsInt(hermodConfig.ReportMinutes);
			hermodConfig.SlowFrameMs = jsonValue["slowFrameMs"].AsDouble(hermodConfig.SlowFrameMs);
			hermodConfig.SlowZoneGenMs = jsonValue["slowZoneGenMs"].AsDouble(hermodConfig.SlowZoneGenMs);
			hermodConfig.CsvMaxBytes = jsonValue["csvMaxBytes"].AsLong(hermodConfig.CsvMaxBytes);
			hermodConfig.DeferToOtherNetworkMods = jsonValue["deferToOtherNetworkMods"].AsBool(hermodConfig.DeferToOtherNetworkMods);
			hermodConfig.BetterNetworkingGuid = jsonValue["betterNetworkingGuid"].AsString(hermodConfig.BetterNetworkingGuid);
			hermodConfig.Clamp();
			return hermodConfig;
		}

		internal void Clamp()
		{
			if (Version < 1)
			{
				Version = 1;
			}
			if (NagleTimeMicros < -1)
			{
				NagleTimeMicros = -1;
			}
			if (NagleTimeMicros > 100000)
			{
				NagleTimeMicros = 100000;
			}
			if (SendRateMin < 1024)
			{
				SendRateMin = 1024;
			}
			if (SendRateMax < SendRateMin)
			{
				SendRateMax = SendRateMin;
			}
			if (SendBufferSize < 0)
			{
				SendBufferSize = 0;
			}
			if (MaxTotalUploadKiBPerSec < 0)
			{
				MaxTotalUploadKiBPerSec = 0;
			}
			if (TargetFrameRate < -1)
			{
				TargetFrameRate = -1;
			}
			if (TargetFrameRate > 1000)
			{
				TargetFrameRate = 1000;
			}
			if (RelayFrameRateCap < 30)
			{
				RelayFrameRateCap = 30;
			}
			if (RelayFrameRateCap > 1000)
			{
				RelayFrameRateCap = 1000;
			}
			if (RelayTargetHz < 1.0)
			{
				RelayTargetHz = 1.0;
			}
			if (RelayTargetHz > 60.0)
			{
				RelayTargetHz = 60.0;
			}
			if (RelayAbortFrameMs < 50.0)
			{
				RelayAbortFrameMs = 50.0;
			}
			if (InFlightCeiling != 0 && InFlightCeiling < 4096)
			{
				InFlightCeiling = 4096;
			}
			if (InFlightCeiling > 262144)
			{
				InFlightCeiling = 262144;
			}
			if (ZoneGenBudgetMs < 0)
			{
				ZoneGenBudgetMs = 0;
			}
			if (ZoneGenBudgetMs > 90)
			{
				ZoneGenBudgetMs = 90;
			}
			if (CompressionLevel < 1)
			{
				CompressionLevel = 1;
			}
			if (CompressionLevel > 9)
			{
				CompressionLevel = 9;
			}
			if (ReportMinutes < 1)
			{
				ReportMinutes = 1;
			}
			if (ReportMinutes > 1440)
			{
				ReportMinutes = 1440;
			}
			if (SlowFrameMs < 5.0)
			{
				SlowFrameMs = 5.0;
			}
			if (SlowZoneGenMs < 1.0)
			{
				SlowZoneGenMs = 1.0;
			}
			if (CsvMaxBytes < 65536)
			{
				CsvMaxBytes = 65536L;
			}
			if (string.IsNullOrEmpty(BetterNetworkingGuid))
			{
				BetterNetworkingGuid = "DIT.BetterNetworking10";
			}
		}

		internal int EffectiveSendRateMax(int playerLimit)
		{
			if (MaxTotalUploadKiBPerSec <= 0 || playerLimit < 1)
			{
				return SendRateMax;
			}
			int num = (int)((long)MaxTotalUploadKiBPerSec * 1024L / playerLimit);
			if (num < SendRateMin)
			{
				num = SendRateMin;
			}
			if (num >= SendRateMax)
			{
				return SendRateMax;
			}
			return num;
		}

		internal static string Load(string configDir, out HermodConfig loaded)
		{
			loaded = null;
			string path = PathIn(configDir);
			try
			{
				if (!File.Exists(path))
				{
					Directory.CreateDirectory(configDir);
					File.WriteAllText(path, Defaults().ToJson());
					loaded = Defaults();
					return null;
				}
				string text = File.ReadAllText(path);
				loaded = FromJson(text);
				return null;
			}
			catch (JsonException ex)
			{
				return "gbv.valheim.hermod.json could not be parsed (" + ex.Message + "). The settings already in force were kept.";
			}
			catch (Exception ex2)
			{
				return "gbv.valheim.hermod.json could not be read (" + ex2.Message + "). The settings already in force were kept.";
			}
		}

		internal string Save(string configDir)
		{
			string text = PathIn(configDir);
			try
			{
				Directory.CreateDirectory(configDir);
				if (File.Exists(text))
				{
					File.Copy(text, text + ".bak", overwrite: true);
				}
				File.WriteAllText(text, ToJson());
				return null;
			}
			catch (Exception ex)
			{
				return "gbv.valheim.hermod.json could not be written (" + ex.Message + ").";
			}
		}

		private static JsonValue Clone(JsonValue source)
		{
			JsonValue jsonValue = JsonValue.NewObject();
			foreach (KeyValuePair<string, JsonValue> member in source.Members)
			{
				jsonValue.Set(member.Key, member.Value);
			}
			return jsonValue;
		}
	}
	internal static class Interop
	{
		private static string _found;

		private static bool _checked;

		internal static bool SuppressNetworkChanges { get; private set; }

		internal static void Detect()
		{
			if (_checked)
			{
				return;
			}
			_checked = true;
			try
			{
				if (!HermodPlugin.Cfg.DeferToOtherNetworkMods)
				{
					SuppressNetworkChanges = false;
					return;
				}
				string betterNetworkingGuid = HermodPlugin.Cfg.BetterNetworkingGuid;
				IDictionary<string, PluginInfo> pluginInfos = Chainloader.PluginInfos;
				if (pluginInfos == null)
				{
					return;
				}
				if (!string.IsNullOrEmpty(betterNetworkingGuid) && pluginInfos.ContainsKey(betterNetworkingGuid))
				{
					Flag(betterNetworkingGuid);
					return;
				}
				foreach (KeyValuePair<string, PluginInfo> item in pluginInfos)
				{
					if (item.Key != null && item.Key.IndexOf("betternetworking", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						Flag(item.Key);
						break;
					}
				}
			}
			catch (Exception ex)
			{
				Safety.Noted("looking for other networking mods", ex);
			}
		}

		private static void Flag(string guid)
		{
			SuppressNetworkChanges = true;
			_found = guid;
			HermodPlugin.Log.LogWarning((object)("Hermod: BetterNetworking (" + guid + ") is installed here. Hermod is leaving the Steam send rate, the Nagle delay, the ZDO relay AND COMPRESSION alone so the two mods do not fight. Measurement is unaffected and the session log is still written."));
			HermodPlugin.Log.LogWarning((object)"Hermod: running both is not recommended. Hermod's compression is adapted from BetterNetworking but uses its own frame marker and its own handshake, so the two are deliberately NOT wire-compatible - if both ended up framing the same packet the result would be corrupt traffic rather than a clean failure. Pick one. Setting deferToOtherNetworkMods to false in gbv.valheim.hermod.json overrides this, and is not advised while both are installed.");
		}

		internal static string Summary()
		{
			if (!_checked)
			{
				return "not checked yet";
			}
			if (!HermodPlugin.Cfg.DeferToOtherNetworkMods)
			{
				return "deferral disabled by config";
			}
			if (!SuppressNetworkChanges)
			{
				return "no other networking mod found";
			}
			return "standing down for " + _found;
		}
	}
	internal sealed class Stat
	{
		internal long N;

		internal double Sum;

		internal double Min = double.MaxValue;

		internal double Max = double.MinValue;

		internal double Mean
		{
			get
			{
				if (N != 0L)
				{
					return Sum / (double)N;
				}
				return 0.0;
			}
		}

		internal double MinOrZero
		{
			get
			{
				if (N != 0L)
				{
					return Min;
				}
				return 0.0;
			}
		}

		internal double MaxOrZero
		{
			get
			{
				if (N != 0L)
				{
					return Max;
				}
				return 0.0;
			}
		}

		internal void Add(double v)
		{
			N++;
			Sum += v;
			if (v < Min)
			{
				Min = v;
			}
			if (v > Max)
			{
				Max = v;
			}
		}

		internal void Reset()
		{
			N = 0L;
			Sum = 0.0;
			Min = double.MaxValue;
			Max = double.MinValue;
		}
	}
	internal sealed class Histogram
	{
		internal static readonly double[] MilliEdges = new double[21]
		{
			0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 6.7, 8.3, 10.0, 12.5,
			16.7, 20.0, 25.0, 33.0, 50.0, 75.0, 100.0, 150.0, 250.0, 500.0,
			1000.0
		};

		internal static readonly double[] MicroEdges = new double[13]
		{
			0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1.0, 2.0, 5.0, 10.0,
			25.0, 50.0, 100.0
		};

		internal static readonly double[] QueueEdges = new double[12]
		{
			0.0, 256.0, 512.0, 1024.0, 2048.0, 4096.0, 8192.0, 10240.0, 16384.0, 32768.0,
			65536.0, 131072.0
		};

		internal static readonly double[] IntervalEdges = new double[13]
		{
			10.0, 25.0, 50.0, 75.0, 100.0, 150.0, 200.0, 300.0, 500.0, 750.0,
			1000.0, 2000.0, 5000.0
		};

		private readonly double[] _edges;

		private readonly long[] _counts;

		private long _n;

		private double _max = double.MinValue;

		private double _min = double.MaxValue;

		private double _sum;

		internal long N => _n;

		internal double Max
		{
			get
			{
				if (_n != 0L)
				{
					return _max;
				}
				return 0.0;
			}
		}

		internal double Min
		{
			get
			{
				if (_n != 0L)
				{
					return _min;
				}
				return 0.0;
			}
		}

		internal double Mean
		{
			get
			{
				if (_n != 0L)
				{
					return _sum / (double)_n;
				}
				return 0.0;
			}
		}

		internal Histogram(double[] edges)
		{
			_edges = edges ?? throw new ArgumentNullException("edges");
			_counts = new long[edges.Length + 1];
		}

		internal void Add(double v)
		{
			_n++;
			_sum += v;
			if (v > _max)
			{
				_max = v;
			}
			if (v < _min)
			{
				_min = v;
			}
			for (int i = 0; i < _edges.Length; i++)
			{
				if (v <= _edges[i])
				{
					_counts[i]++;
					return;
				}
			}
			_counts[_edges.Length]++;
		}

		internal double Quantile(double q)
		{
			if (_n == 0L)
			{
				return 0.0;
			}
			if (q <= 0.0)
			{
				return _min;
			}
			if (q >= 1.0)
			{
				return _max;
			}
			long num = (long)Math.Ceiling(q * (double)_n);
			if (num < 1)
			{
				num = 1L;
			}
			long num2 = 0L;
			for (int i = 0; i < _counts.Length; i++)
			{
				num2 += _counts[i];
				if (num2 >= num)
				{
					if (i >= _edges.Length)
					{
						return _max;
					}
					return _edges[i];
				}
			}
			return _max;
		}

		internal long CountAbove(double threshold)
		{
			long num = 0L;
			for (int i = 0; i < _counts.Length; i++)
			{
				if (((i == 0) ? double.MinValue : _edges[i - 1]) >= threshold)
				{
					num += _counts[i];
				}
			}
			return num;
		}

		internal void Reset()
		{
			Array.Clear(_counts, 0, _counts.Length);
			_n = 0L;
			_sum = 0.0;
			_max = double.MinValue;
			_min = double.MaxValue;
		}

		internal JsonValue ToJson()
		{
			JsonValue jsonValue = JsonValue.NewArray();
			double[] edges = _edges;
			foreach (double v in edges)
			{
				jsonValue.Add(JsonValue.Number(v));
			}
			JsonValue jsonValue2 = JsonValue.NewArray();
			long[] counts = _counts;
			foreach (long num in counts)
			{
				jsonValue2.Add(JsonValue.Number((double)num));
			}
			return JsonValue.NewObject().Set("n", (double)_n).Set("mean", Mean)
				.Set("min", Min)
				.Set("max", Max)
				.Set("edges", jsonValue)
				.Set("counts", jsonValue2);
		}
	}
	internal sealed class Ring<T>
	{
		private readonly T[] _buf;

		private int _next;

		private int _count;

		internal int Capacity => _buf.Length;

		internal int Count => _count;

		internal long Total { get; private set; }

		internal Ring(int capacity)
		{
			if (capacity < 1)
			{
				throw new ArgumentOutOfRangeException("capacity");
			}
			_buf = new T[capacity];
		}

		internal void Add(T item)
		{
			_buf[_next] = item;
			_next = (_next + 1) % _buf.Length;
			if (_count < _buf.Length)
			{
				_count++;
			}
			Total++;
		}

		internal IEnumerable<T> Items()
		{
			int start = ((_count >= _buf.Length) ? _next : 0);
			for (int i = 0; i < _count; i++)
			{
				yield return _buf[(start + i) % _buf.Length];
			}
		}

		internal void Clear()
		{
			Array.Clear(_buf, 0, _buf.Length);
			_next = 0;
			_count = 0;
			Total = 0L;
		}
	}
	[BepInPlugin("gbv.valheim.hermod", "Hermod", "0.1.3")]
	public class HermodPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "gbv.valheim.hermod";

		public const string PluginName = "Hermod";

		public const string PluginVersion = "0.1.3";

		internal static ManualLogSource Log;

		internal static HermodConfig Cfg = HermodConfig.Defaults();

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<bool> MeasureOnly;

		internal static string MissingTarget;

		private Harmony _harmony;

		private bool _worldWasLoaded;

		private bool _appliedForThisWorld;

		private static string ConfigDir => Paths.ConfigPath;

		private static Assembly ResolveEmbedded(object sender, ResolveEventArgs args)
		{
			try
			{
				if (!string.Equals(new AssemblyName(args.Name).Name, "ZstdSharp", StringComparison.OrdinalIgnoreCase))
				{
					return null;
				}
				using Stream stream = typeof(HermodPlugin).Assembly.GetManifestResourceStream("GBV.Hermod.ZstdSharp.dll");
				if (stream == null)
				{
					return null;
				}
				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)
					{
						break;
					}
				}
				return Assembly.Load(array);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogError((object)("Hermod: could not load its embedded copy of ZstdSharp (" + ex.Message + "). Compression will be unavailable."));
				}
				return null;
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static void StartCompression()
		{
			Compression.Initialize();
		}

		private void Awake()
		{
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			AppDomain.CurrentDomain.AssemblyResolve += ResolveEmbedded;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Master switch. Turning it off stops the measurement and puts every setting back the way the game has them, without uninstalling. Has no effect on a client: nothing here runs anywhere but the server.");
			MeasureOnly = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "MeasureOnly", false, "Watch and change nothing at all. Overrides every setting in gbv.valheim.hermod.json, including the Steam socket and the frame rate. This is the setting to use if you want a clean before-picture of your server, or if you suspect Hermod of causing a problem and want to keep the session log while ruling it out.");
			LoadConfig("startup");
			_harmony = new Harmony("gbv.valheim.hermod");
			_harmony.PatchAll(typeof(HermodPlugin).Assembly);
			MissingTarget = Reflect.VerifyTargets() ?? ZoneGuard.VerifyTargets();
			if (MissingTarget == null)
			{
				MissingTarget = Relay.Apply(_harmony) ?? Probes.Apply(_harmony) ?? Ceiling.Apply(_harmony) ?? ZoneGuard.Apply(_harmony) ?? Tuning.Apply(_harmony) ?? CompressionLink.Apply(_harmony);
			}
			if (MissingTarget != null)
			{
				Log.LogError((object)("Hermod: " + MissingTarget + " could not be hooked in this build of the game, so the detailed timings, the Steam settings and the ZDO relay are all DISABLED. This almost always means Valheim updated and Hermod has not caught up. Frame time, cpu, memory, zdo counts, per-player ping and bandwidth, zone generation and world saves are all still measured - none of that needs a patch."));
			}
			Probes.Enabled = Cfg.ProbesEnabled;
			if (Cfg.CompressionEnabled && !MeasureOnly.Value)
			{
				StartCompression();
			}
			Log.LogInfo((object)"Hermod 0.1.3 loaded.");
		}

		private void Update()
		{
			try
			{
				Pump();
			}
			catch (Exception ex)
			{
				Safety.Noted("the Hermod update pump", ex);
			}
		}

		private void Pump()
		{
			Bridge.EnsureRegistered();
			bool flag = (Object)(object)ZNet.instance != (Object)null && (Object)(object)ZNetScene.instance != (Object)null && ZDOMan.instance != null;
			if (_worldWasLoaded && !flag)
			{
				if (Relay.Enabled)
				{
					Relay.Disable("the world unloaded");
				}
				Sampler.Stop("the world unloaded");
				_appliedForThisWorld = false;
			}
			_worldWasLoaded = flag;
			if (flag && ZNet.instance.IsServer() && Enabled.Value)
			{
				Interop.Detect();
				if (!_appliedForThisWorld)
				{
					_appliedForThisWorld = true;
					StartForWorld();
				}
				Sampler.Tick();
				Relay.Tick();
				Tuning.MaybeReapplyForPeerCount();
			}
		}

		private void StartForWorld()
		{
			Sampler.Start(ConfigDir);
			if (MeasureOnly.Value)
			{
				Log.LogInfo((object)"Hermod: MeasureOnly is on, so nothing at all is being changed - not the Steam socket, not the frame rate, not the ZDO relay. The session log is still written, which makes this run a clean baseline.");
				return;
			}
			Log.LogInfo((object)Tuning.ApplySteam("the world loaded"));
			Log.LogInfo((object)Tuning.ApplyFrameRate("the world loaded"));
			if (Cfg.RelayEnabled)
			{
				Log.LogWarning((object)"Hermod: relayEnabled is set in gbv.valheim.hermod.json. The ZDO relay is the one thing here that changes how the game behaves, and it cannot be tested properly without real players on real connections. Watch the first session closely.");
				Log.LogInfo((object)Relay.Enable(clearLatch: false));
			}
		}

		private void OnDestroy()
		{
			try
			{
				if (Relay.Enabled)
				{
					Relay.Disable("the plugin is shutting down");
				}
				Sampler.Stop("the plugin is shutting down");
			}
			catch (Exception ex)
			{
				Safety.Noted("shutting Hermod down", ex);
			}
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			AppDomain.CurrentDomain.AssemblyResolve -= ResolveEmbedded;
		}

		private static void LoadConfig(string why)
		{
			HermodConfig loaded;
			string text = HermodConfig.Load(ConfigDir, out loaded);
			if (text != null)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogError((object)("Hermod: " + text));
				}
				return;
			}
			Cfg = loaded;
			Cfg.Clamp();
			if (Log != null && why != null)
			{
				Log.LogInfo((object)("Hermod: read gbv.valheim.hermod.json (" + why + ")."));
			}
		}

		internal static string ReloadConfig()
		{
			HermodConfig loaded;
			string text = HermodConfig.Load(ConfigDir, out loaded);
			if (text != null)
			{
				return text;
			}
			Cfg = loaded;
			Cfg.Clamp();
			Probes.Enabled = Cfg.ProbesEnabled;
			return null;
		}

		internal static string SaveConfig()
		{
			Cfg.RelayEnabled = Relay.Enabled;
			Cfg.ProbesEnabled = Probes.Enabled;
			string text = Cfg.Save(ConfigDir);
			if (text != null)
			{
				return "Hermod: " + text;
			}
			return "Hermod: wrote gbv.valheim.hermod.json (the previous one is beside it as .bak).";
		}
	}
	internal static class Probes
	{
		private static readonly double TicksToMs = 1000.0 / (double)Stopwatch.Frequency;

		internal static bool Enabled = true;

		private static bool _applied;

		private static long _sendT0;

		private static long _syncListT0;

		private static long _sortT0;

		private static int _sendZdosBefore;

		private static int _sendQueueBefore;

		internal static string Apply(Harmony harmony)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			//IL_003b: Expected O, but got Unknown
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Expected O, but got Unknown
			//IL_0062: Expected O, but got Unknown
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Expected O, but got Unknown
			//IL_0089: Expected O, but got Unknown
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Expected O, but got Unknown
			if (_applied)
			{
				return null;
			}
			try
			{
				Type typeFromHandle = typeof(Probes);
				harmony.Patch((MethodBase)Reflect.SendZDOs, new HarmonyMethod(typeFromHandle, "SendZDOsPrefix", (Type[])null), new HarmonyMethod(typeFromHandle, "SendZDOsPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)Reflect.CreateSyncList, new HarmonyMethod(typeFromHandle, "CreateSyncListPrefix", (Type[])null), new HarmonyMethod(typeFromHandle, "CreateSyncListPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)Reflect.ServerSortSendZDOS, new HarmonyMethod(typeFromHandle, "SortPrefix", (Type[])null), new HarmonyMethod(typeFromHandle, "SortPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)Reflect.ZRpcInvoke, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "ZRpcInvokePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				_applied = true;
				return null;
			}
			catch (Exception ex)
			{
				return ex.Message;
			}
		}

		private static void SendZDOsPrefix(ZDOMan __instance, bool flush)
		{
			try
			{
				if (!Enabled || !Sampler.Running)
				{
					return;
				}
				_sendT0 = Stopwatch.GetTimestamp();
				_sendZdosBefore = Reflect.ZdosSent.Invoke(__instance);
				_sendQueueBefore = -1;
				if (!flush)
				{
					ZNetPeer currentPeer = Relay.CurrentPeer;
					if (currentPeer?.m_socket != null)
					{
						_sendQueueBefore = currentPeer.m_socket.GetSendQueueSize();
					}
				}
			}
			catch (Exception ex)
			{
				Safety.Failed("ZDOMan.SendZDOs prefix", ex);
			}
		}

		private static void SendZDOsPostfix(ZDOMan __instance, bool flush, bool __result)
		{
			try
			{
				if (!Enabled || !Sampler.Running || _sendT0 == 0L)
				{
					return;
				}
				double v = (double)(Stopwatch.GetTimestamp() - _sendT0) * TicksToMs;
				_sendT0 = 0L;
				MinuteAccumulator current = Sampler.Current;
				if (flush)
				{
					current.FlushCalls++;
					return;
				}
				current.SendCalls++;
				current.SendMs.Add(v);
				int num = Reflect.ZdosSent.Invoke(__instance) - _sendZdosBefore;
				if (num > 0)
				{
					current.ZdosPerSend.Add(num);
				}
				PeerAccumulator peerAccumulator = null;
				ZNetPeer currentPeer = Relay.CurrentPeer;
				if (currentPeer != null)
				{
					peerAccumulator = Sampler.PeerFor(currentPeer.m_uid);
				}
				if (__result)
				{
					current.SendOk++;
					if (peerAccumulator != null)
					{
						peerAccumulator.Sends++;
						long timestamp = Stopwatch.GetTimestamp();
						if (peerAccumulator.LastSendTicks != 0L)
						{
							peerAccumulator.SendInterval.Add((double)(timestamp - peerAccumulator.LastSendTicks) * TicksToMs);
						}
						peerAccumulator.LastSendTicks = timestamp;
					}
				}
				else if (_sendQueueBefore >= 0)
				{
					if (_sendQueueBefore > 10240)
					{
						current.BailCeiling++;
						if (peerAccumulator != null)
						{
							peerAccumulator.BailCeiling++;
						}
					}
					else if (10240 - _sendQueueBefore < 2048)
					{
						current.BailHeadroom++;
						if (peerAccumulator != null)
						{
							peerAccumulator.BailHeadroom++;
						}
					}
					else
					{
						current.BailEmpty++;
					}
				}
				else
				{
					current.BailEmpty++;
				}
			}
			catch (Exception ex)
			{
				Safety.Failed("ZDOMan.SendZDOs postfix", ex);
			}
		}

		private static void CreateSyncListPrefix()
		{
			try
			{
				if (Enabled && Sampler.Running)
				{
					_syncListT0 = Stopwatch.GetTimestamp();
				}
			}
			catch (Exception ex)
			{
				Safety.Failed("ZDOMan.CreateSyncList prefix", ex);
			}
		}

		private static void CreateSyncListPostfix(List<ZDO> toSync)
		{
			try
			{
				if (Enabled && Sampler.Running && _syncListT0 != 0L)
				{
					Sampler.Current.SyncListMs.Add((double)(Stopwatch.GetTimestamp() - _syncListT0) * TicksToMs);
					_syncListT0 = 0L;
					if (toSync != null)
					{
						Sampler.Current.SortN.Add(toSync.Count);
					}
				}
			}
			catch (Exception ex)
			{
				Safety.Failed("ZDOMan.CreateSyncList postfix", ex);
			}
		}

		private static void SortPrefix()
		{
			try
			{
				if (Enabled && Sampler.Running)
				{
					_sortT0 = Stopwatch.GetTimestamp();
				}
			}
			catch (Exception ex)
			{
				Safety.Failed("ZDOMan.ServerSortSendZDOS prefix", ex);
			}
		}

		private static void SortPostfix(List<ZDO> objects)
		{
			try
			{
				ClearSortValueLeak(objects);
				if (Enabled && Sampler.Running && _sortT0 != 0L)
				{
					Sampler.Current.SortMs.Add((double)(Stopwatch.GetTimestamp() - _sortT0) * TicksToMs);
					_sortT0 = 0L;
				}
			}
			catch (Exception ex)
			{
				Safety.Failed("ZDOMan.ServerSortSendZDOS postfix", ex);
			}
		}

		private static void ClearSortValueLeak(List<ZDO> objects)
		{
			if (!HermodPlugin.Cfg.FixZdoExtraDataLeak || objects == null)
			{
				return;
			}
			for (int i = 0; i < objects.Count; i++)
			{
				ZDO val = objects[i];
				if (val != null && val.m_tempSortValue < 0f)
				{
					val.m_tempSortValue = 0f;
				}
			}
		}

		private static void ZRpcInvokePostfix(string method, object[] parameters)
		{
			try
			{
				if (Enabled && Sampler.Running && string.Equals(method, "ZDOData", StringComparison.Ordinal) && parameters != null && parameters.Length != 0)
				{
					object obj = parameters[0];
					ZPackage val = (ZPackage)((obj is ZPackage) ? obj : null);
					if (val != null)
					{
						Sampler.Current.ZdoDataBytes.Add(val.Size());
					}
				}
			}
			catch (Exception ex)
			{
				Safety.Failed("ZRpc.Invoke postfix", ex);
			}
		}
	}
	internal static class Reflect
	{
		internal static MethodInfo SendZDOToPeers2;

		internal static MethodInfo SendZDOs;

		internal static MethodInfo CreateSyncList;

		internal static MethodInfo ServerSortSendZDOS;

		internal static MethodInfo SpawnZone;

		internal static MethodInfo RegisterGlobalCallbacks;

		internal static MethodInfo ZRpcInvoke;

		internal static Action<ZDOMan, float> SendZDOToPeers2Call;

		internal static FieldRef<ZDOMan, int> NextSendPeer;

		internal static FieldRef<ZDOMan, float> SendTimer;

		internal static FieldRef<ZDOMan, int> ZdosSent;

		private static FieldInfo _zdoManPeers;

		private static FieldInfo _zdoPeerPeer;

		private static FieldInfo _zdoPeerZdos;

		private static FieldInfo _deadZdos;

		private static FieldInfo _generatedZones;

		private static FieldInfo _extraDataFloats;

		private static FieldInfo _steamConnection;

		private static FieldInfo _steamSocketsField;

		private static bool _steamSocketsResolved;

		private static readonly Dictionary<Type, FieldInfo> InnerSocketField = new Dictionary<Type, FieldInfo>();

		internal static readonly string[] NeverPatch = new string[9] { "ZDO.GetPosition", "ZDO.Type", "ZDOMan.ZDOPeer.ShouldSend", "ZDOMan.ServerSendCompare", "ZDOMan.FindObjects", "ZoneSystem.IsZoneGenerated", "ZoneSystem.ZonesWithinRadius", "ZPackage.Write*", "ZDOExtraData.*" };

		internal static bool Usable { get; private set; }

		internal static string VerifyTargets()
		{
			Usable = false;
			SendZDOToPeers2 = AccessTools.Method(typeof(ZDOMan), "SendZDOToPeers2", (Type[])null, (Type[])null);
			if (SendZDOToPeers2 == null)
			{
				return "ZDOMan.SendZDOToPeers2";
			}
			SendZDOs = AccessTools.Method(typeof(ZDOMan), "SendZDOs", (Type[])null, (Type[])null);
			if (SendZDOs == null)
			{
				return "ZDOMan.SendZDOs";
			}
			CreateSyncList = AccessTools.Method(typeof(ZDOMan), "CreateSyncList", (Type[])null, (Type[])null);
			if (CreateSyncList == null)
			{
				return "ZDOMan.CreateSyncList";
			}
			ServerSortSendZDOS = AccessTools.Method(typeof(ZDOMan), "ServerSortSendZDOS", (Type[])null, (Type[])null);
			if (ServerSortSendZDOS == null)
			{
				return "ZDOMan.ServerSortSendZDOS";
			}
			SpawnZone = AccessTools.Method(typeof(ZoneSystem), "SpawnZone", (Type[])null, (Type[])null);
			if (SpawnZone == null)
			{
				return "ZoneSystem.SpawnZone";
			}
			RegisterGlobalCallbacks = AccessTools.Method(typeof(ZSteamSocket), "RegisterGlobalCallbacks", (Type[])null, (Type[])null);
			if (RegisterGlobalCallbacks == null)
			{
				return "ZSteamSocket.RegisterGlobalCallbacks";
			}
			ZRpcInvoke = AccessTools.Method(typeof(ZRpc), "Invoke", new Type[2]
			{
				typeof(string),
				typeof(object[])
			}, (Type[])null);
			if (ZRpcInvoke == null)
			{
				return "ZRpc.Invoke(string, object[])";
			}
			_zdoManPeers = AccessTools.Field(typeof(ZDOMan), "m_peers");
			if (_zdoManPeers == null)
			{
				return "ZDOMan.m_peers";
			}
			Type type = AccessTools.Inner(typeof(ZDOMan), "ZDOPeer");
			if (type == null)
			{
				return "ZDOMan.ZDOPeer";
			}
			_zdoPeerPeer = AccessTools.Field(type, "m_peer");
			if (_zdoPeerPeer == null)
			{
				return "ZDOMan.ZDOPeer.m_peer";
			}
			_zdoPeerZdos = AccessTools.Field(type, "m_zdos");
			_deadZdos = AccessTools.Field(typeof(ZDOMan), "m_deadZDOs");
			if (_deadZdos == null)
			{
				return "ZDOMan.m_deadZDOs";
			}
			_generatedZones = AccessTools.Field(typeof(ZoneSystem), "m_generatedZones");
			if (_generatedZones == null)
			{
				return "ZoneSystem.m_generatedZones";
			}
			_extraDataFloats = AccessTools.Field(typeof(ZDOExtraData), "s_floats");
			if (_extraDataFloats == null)
			{
				return "ZDOExtraData.s_floats";
			}
			_steamConnection = AccessTools.Field(typeof(ZSteamSocket), "m_con");
			if (_steamConnection == null)
			{
				return "ZSteamSocket.m_con";
			}
			try
			{
				NextSendPeer = AccessTools.FieldRefAccess<ZDOMan, int>("m_nextSendPeer");
				SendTimer = AccessTools.FieldRefAccess<ZDOMan, float>("m_sendTimer");
				ZdosSent = AccessTools.FieldRefAccess<ZDOMan, int>("m_zdosSent");
				SendZDOToPeers2Call = AccessTools.MethodDelegate<Action<ZDOMan, float>>(SendZDOToPeers2, (object)null, true);
			}
			catch (Exception ex)
			{
				return "ZDOMan field or delegate access (" + ex.Message + ")";
			}
			if (NextSendPeer == null)
			{
				return "ZDOMan.m_nextSendPeer";
			}
			if (SendTimer == null)
			{
				return "ZDOMan.m_sendTimer";
			}
			if (ZdosSent == null)
			{
				return "ZDOMan.m_zdosSent";
			}
			if (SendZDOToPeers2Call == null)
			{
				return "a callable ZDOMan.SendZDOToPeers2";
			}
			Usable = true;
			return null;
		}

		internal static IList ZdoPeers(ZDOMan man)
		{
			if (man == null || _zdoManPeers == null)
			{
				return null;
			}
			return _zdoManPeers.GetValue(man) as IList;
		}

		internal static ZNetPeer PeerOf(object zdoPeer)
		{
			if (zdoPeer == null || _zdoPeerPeer == null)
			{
				return null;
			}
			object? value = _zdoPeerPeer.GetValue(zdoPeer);
			return (ZNetPeer)((value is ZNetPeer) ? value : null);
		}

		internal static IDictionary PeerKnownZdos(object zdoPeer)
		{
			if (zdoPeer == null || _zdoPeerZdos == null)
			{
				return null;
			}
			try
			{
				return _zdoPeerZdos.GetValue(zdoPeer) as IDictionary;
			}
			catch (Exception ex)
			{
				Safety.Noted("reading ZDOPeer.m_zdos", ex);
				return null;
			}
		}

		internal static int DeadZdoCount(ZDOMan man)
		{
			return CountOf(_deadZdos, man);
		}

		internal static int GeneratedZoneCount(ZoneSystem zs)
		{
			return CountOf(_generatedZones, zs);
		}

		internal static int ExtraDataFloatCount()
		{
			return CountOf(_extraDataFloats, null);
		}

		internal static object SteamConnection(ZSteamSocket socket)
		{
			if (socket == null || _steamConnection == null)
			{
				return null;
			}
			try
			{
				return _steamConnection.GetValue(socket);
			}
			catch (Exception ex)
			{
				Safety.Noted("reading ZSteamSocket.m_con", ex);
				return null;
			}
		}

		internal static ZSteamSocket UnwrapSteamSocket(ISocket socket)
		{
			if (socket == null)
			{
				return null;
			}
			ZSteamSocket val = (ZSteamSocket)(object)((socket is ZSteamSocket) ? socket : null);
			if (val != null)
			{
				return val;
			}
			try
			{
				string hostName = socket.GetHostName();
				if (!string.IsNullOrEmpty(hostName))
				{
					IList list = SteamSockets();
					if (list != null)
					{
						for (int i = 0; i < list.Count; i++)
						{
							object? obj = list[i];
							ZSteamSocket val2 = (ZSteamSocket)((obj is ZSteamSocket) ? obj : null);
							if (val2 != null && string.Equals(val2.GetHostName(), hostName, StringComparison.Ordinal))
							{
								return val2;
							}
						}
					}
				}
			}
			catch (Exception ex)
			{
				Safety.Noted("matching a peer to its Steam socket", ex);
			}
			try
			{
				int num = 0;
				while (socket != null && num < 16)
				{
					ZSteamSocket val3 = (ZSteamSocket)(object)((socket is ZSteamSocket) ? socket : null);
					if (val3 != null)
					{
						return val3;
					}
					ISocket val4 = InnerSocketOf(socket);
					if (val4 == null || val4 == socket)
					{
						return null;
					}
					socket = val4;
					num++;
				}
			}
			catch (Exception ex2)
			{
				Safety.Noted("unwrapping a peer's socket", ex2);
			}
			return null;
		}

		private static IList SteamSockets()
		{
			if (!_steamSocketsResolved)
			{
				_steamSocketsResolved = true;
				_steamSocketsField = AccessTools.Field(typeof(ZSteamSocket), "m_sockets");
			}
			return _steamSocketsField?.GetValue(null) as IList;
		}

		private static ISocket InnerSocketOf(ISocket socket)
		{
			Type type = ((object)socket).GetType();
			if (!InnerSocketField.TryGetValue(type, out var value))
			{
				value = null;
				Type type2 = type;
				while (type2 != null && value == null)
				{
					FieldInfo[] fields = type2.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (FieldInfo fieldInfo in fields)
					{
						if (typeof(ISocket).IsAssignableFrom(fieldInfo.FieldType))
						{
							value = fieldInfo;
							break;
						}
					}
					type2 = type2.BaseType;
				}
				InnerSocketField[type] = value;
			}
			object? obj = value?.GetValue(socket);
			return (ISocket)((obj is ISocket) ? obj : null);
		}

		private static int CountOf(FieldInfo field, object instance)
		{
			if (field == null)
			{
				return -1;
			}
			try
			{
				return (field.GetValue(instance) is ICollection collection) ? collection.Count : (-1);
			}
			catch (Exception ex)
			{
				Safety.Noted("reading " + field.DeclaringType?.Name + "." + field.Name, ex);
				return -1;
			}
		}
	}
	internal static class Relay
	{
		private static readonly double TicksToMs = 1000.0 / (double)Stopwatch.Frequency;

		private static bool _applied;

		private static bool _driving;

		private static bool _relayRanThisCall;

		private static long _intervalTicks;

		private static long _lastSweepTicks;

		private static long _stepT0;

		private static double _passMs;

		private static bool _passInProgress;

		private static string _lastAbort;

		private static bool _abortLatched;

		private static long _lastWatchdogTicks;

		internal static bool Enabled { get; private set; }

		internal static ZNetPeer CurrentPeer { get; private set; }

		internal static string Apply(Harmony harmony)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			//IL_003b: Expected O, but got Unknown
			if (_applied)
			{
				return null;
			}
			try
			{
				Type typeFromHandle = typeof(Relay);
				harmony.Patch((MethodBase)Reflect.SendZDOToPeers2, new HarmonyMethod(typeFromHandle, "SendZDOToPeers2Prefix", (Type[])null), new HarmonyMethod(typeFromHandle, "SendZDOToPeers2Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				_applied = true;
				return null;
			}
			catch (Exception ex)
			{
				return ex.Message;
			}
		}

		internal static string Enable(bool clearLatch)
		{
			if (HermodPlugin.MeasureOnly != null && HermodPlugin.MeasureOnly.Value)
			{
				return "Hermod: MeasureOnly is on, so the relay stays off and the game keeps driving its own ZDO sends. Turn MeasureOnly off in the BepInEx config if you want to change anything.";
			}
			if (!Reflect.Usable)
			{
				return "Hermod: the relay cannot run - " + HermodPlugin.MissingTarget + " did not resolve in this build of the game.";
			}
			if (Interop.SuppressNetworkChanges)
			{
				return "Hermod: the relay is suppressed because " + Interop.Summary() + ". Set deferToOtherNetworkMods to false if you wan