Decompiled source of ValheimOne v0.13.29

plugins/ValheimOne.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Sockets;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Splatform;
using Steamworks;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using ValheimOne.ActivityLog;
using ValheimOne.Configuration;
using ValheimOne.Discord;
using ValheimOne.Infrastructure;
using ValheimOne.LiveMap;
using ValheimOne.Modules;
using ValheimOne.Networking;
using ValheimOne.Query;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("ValheimOne")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.13.29.0")]
[assembly: AssemblyInformationalVersion("0.13.29")]
[assembly: AssemblyProduct("ValheimOne")]
[assembly: AssemblyTitle("ValheimOne")]
[assembly: InternalsVisibleTo("ValheimOne.RuntimeRegression")]
[assembly: AssemblyVersion("0.13.29.0")]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace ValheimOne
{
	[BepInPlugin("com.humangenome.valheimone", "ValheimOne", "0.13.29")]
	public sealed class ValheimOnePlugin : BaseUnityPlugin
	{
		private const int ConfigReloadDebounceMilliseconds = 750;

		public const string PluginGuid = "com.humangenome.valheimone";

		public const string PluginName = "ValheimOne";

		public const string PluginVersion = "0.13.29";

		private Harmony? _harmony;

		private ConfigHotReloadWatcher? _configWatcher;

		private IVersionHandshake? _versionHandshake;

		private ActivityLogModule? _activityLogModule;

		private LiveMapModule? _liveMapModule;

		private DiscordModule? _discordModule;

		private NetworkModule? _networkModule;

		private ServerSessionEventSource? _sessionEvents;

		private ValheimOneConfig? _settings;

		private ModLogger? _log;

		private void Awake()
		{
			//IL_02d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e2: Expected O, but got Unknown
			_log = new ModLogger(((BaseUnityPlugin)this).Logger);
			LogStartupBanner(_log);
			string text = Path.Combine(Paths.ConfigPath, "valheimone.cfg");
			ValheimOneConfig valheimOneConfig = (_settings = new ValheimOneConfig(text));
			ServerConfig serverConfig = new ServerConfig(valheimOneConfig.Features);
			MapSharingModule mapSharingModule = new MapSharingModule(valheimOneConfig.Features, _log);
			ServerSessionEventSource sessionEvents = (_sessionEvents = ServerSessionEventSource.Initialize(((Component)this).gameObject, _log));
			string text2 = Path.Combine(Paths.ConfigPath, "ValheimOne");
			string text3 = WorldDataDirectory.Resolve(text2, _log);
			ActivityLogModule activityLogModule = (_activityLogModule = new ActivityLogModule(valheimOneConfig.Features, text3, sessionEvents, _log));
			DiscordModule discordModule = (_discordModule = new DiscordModule(valheimOneConfig.Features, sessionEvents, _log));
			LiveMapModule liveMapModule = (_liveMapModule = new LiveMapModule(valheimOneConfig.Features, activityLogModule, sessionEvents, text2, text3));
			NetworkModule networkModule = (_networkModule = new NetworkModule(valheimOneConfig.Features, _log));
			IReadOnlyList<IFeatureModule> readOnlyList = new IFeatureModule[32]
			{
				new PlayerModule(valheimOneConfig.Features),
				new PlayerStaminaModule(valheimOneConfig.Features),
				new BuildingQoLModule(valheimOneConfig.Features),
				new StructuralIntegrityModule(valheimOneConfig.Features),
				new FoodDurationModule(valheimOneConfig.Features),
				new ItemTweaksModule(valheimOneConfig.Features),
				new ItemDropMultiplierModule(valheimOneConfig.Features),
				new GatheringModule(valheimOneConfig.Features),
				new TamesModule(valheimOneConfig.Features),
				new WorldEventsModule(valheimOneConfig.Features),
				new DayNightLengthModule(valheimOneConfig.Features),
				new TraderModule(valheimOneConfig.Features),
				new BeehiveModule(valheimOneConfig.Features),
				new FireSourceModule(valheimOneConfig.Features, _log),
				new FermenterModule(valheimOneConfig.Features),
				new SapCollectorModule(valheimOneConfig.Features),
				new ProductionSpeedsModule(valheimOneConfig.Features, _log),
				new ContainerSizesModule(valheimOneConfig.Features, _log),
				new WardsModule(valheimOneConfig.Features),
				new PortalsModule(valheimOneConfig.Features),
				new CraftFromChestModule(valheimOneConfig.Features),
				new StationAutomationModule(valheimOneConfig.Features),
				new CookingStationModule(valheimOneConfig.Features),
				new ExperienceRatesModule(valheimOneConfig.Features),
				new DeathPenaltyModule(valheimOneConfig.Features),
				mapSharingModule,
				activityLogModule,
				liveMapModule,
				new QueryModule(valheimOneConfig.Features),
				discordModule,
				new ServerHostModule(serverConfig, _log),
				networkModule
			};
			valheimOneConfig.WriteDefaultsIfNeeded();
			_harmony = new Harmony("com.humangenome.valheimone");
			List<string> list = (ContractDiagnostics.IsEnabled ? new List<string>() : null);
			int num = 0;
			foreach (IFeatureModule item in readOnlyList)
			{
				try
				{
					item.ApplyPatches(_harmony);
					valheimOneConfig.Features.RecordPatchSuccess(item.Section);
					num++;
					string arg = (item.IsEnabled ? "enabled" : "disabled");
					_log.Info($"Feature patches ready: {item.Section} ({item.Classification}, {arg}).");
				}
				catch (Exception ex)
				{
					string text4 = ContractDiagnostics.DescribePatchFailure(item, ex);
					valheimOneConfig.Features.RecordPatchFailure(item.Section, text4);
					list?.Add(text4);
					_log.Error("Feature patch application failed: " + text4 + " (" + ex.GetType().Name + ": " + ContractDiagnostics.SingleLineMessage(ex) + "). Continuing with remaining modules.");
				}
			}
			_versionHandshake = new VersionHandshake(valheimOneConfig, serverConfig, _log, new IVersionHandshakeExtension[1] { mapSharingModule });
			_versionHandshake.Initialize(_harmony);
			if (ContractDiagnostics.IsEnabled)
			{
				ContractDiagnostics.Initialize(_harmony, _log, readOnlyList, num, list);
			}
			_configWatcher = new ConfigHotReloadWatcher(text, _log);
			_configWatcher.Start();
			_log.Info("Configuration: " + text);
		}

		private void Start()
		{
			_networkModule?.ReportStartupState();
		}

		private void Update()
		{
			ConfigHotReloadWatcher configWatcher = _configWatcher;
			ValheimOneConfig settings = _settings;
			ModLogger log = _log;
			if (configWatcher == null || settings == null || log == null || !configWatcher.TryConsumeChange(750))
			{
				return;
			}
			Dictionary<IConfigEntry, string> dictionary = SnapshotEffectiveValues(settings.Features);
			try
			{
				settings.File.Reload();
			}
			catch (Exception ex)
			{
				log.Warning("Config hot-reload failed; effective values were not reapplied: " + ex.GetType().Name + ": " + ContractDiagnostics.SingleLineMessage(ex));
				return;
			}
			Dictionary<IConfigEntry, string> dictionary2 = SnapshotEffectiveValues(settings.Features);
			bool flag = false;
			foreach (FeatureDefinition feature in settings.Features.Features)
			{
				foreach (IConfigEntry key in feature.Keys)
				{
					string text = dictionary[key];
					string text2 = dictionary2[key];
					if (!string.Equals(text, text2, StringComparison.Ordinal))
					{
						flag = true;
						string text3 = ((key == feature.Enabled) ? " (feature gate — patches stay installed; enabling/disabling applies live, new patch topology requires restart)" : string.Empty);
						if (key.Definition.IsSensitive)
						{
							log.Info("Config hot-reload: [" + key.Definition.Section + "] " + key.Definition.Name + ": value changed (applied live)" + text3);
						}
						else
						{
							log.Info("Config hot-reload: [" + key.Definition.Section + "] " + key.Definition.Name + ": " + text + " -> " + text2 + " (applied live)" + text3);
						}
					}
				}
			}
			if (flag)
			{
				settings.Features.NotifyEffectiveValuesChanged();
			}
			else
			{
				log.Debug("Config hot-reload: config file touched; no effective changes");
			}
		}

		private void OnDestroy()
		{
			_configWatcher?.Dispose();
			_configWatcher = null;
			_discordModule?.Shutdown();
			_discordModule = null;
			_networkModule = null;
			_liveMapModule?.Shutdown();
			_liveMapModule = null;
			_activityLogModule?.Shutdown();
			_activityLogModule = null;
			_sessionEvents?.StopPermanently();
			_sessionEvents = null;
			_settings = null;
			_versionHandshake?.Shutdown();
			_versionHandshake = null;
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			_harmony = null;
		}

		private static Dictionary<IConfigEntry, string> SnapshotEffectiveValues(FeatureRegistry registry)
		{
			Dictionary<IConfigEntry, string> dictionary = new Dictionary<IConfigEntry, string>();
			foreach (FeatureDefinition feature in registry.Features)
			{
				foreach (IConfigEntry key in feature.Keys)
				{
					dictionary.Add(key, key.GetSerializedValue());
				}
			}
			return dictionary;
		}

		private static void LogStartupBanner(ModLogger log)
		{
			string text = GameVersionDetector.TryDetect(log);
			string text2 = text ?? "unavailable";
			log.Info("ValheimOne v0.13.29 starting | Valheim detected: " + text2 + " | supported: 1.0.21 | " + $"schema: {1}");
			if (text != null && text.IndexOf("1.0.21", StringComparison.OrdinalIgnoreCase) < 0)
			{
				log.Warning("This build targets Valheim 1.0.21; detected " + text + ". Verify compatibility before enabling gameplay features.");
			}
		}
	}
}
namespace ValheimOne.Query
{
	internal sealed class QueryBehaviour : MonoBehaviour
	{
		private const int DefaultGamePort = 2456;

		private const float SnapshotIntervalSeconds = 2f;

		private const float IdleSnapshotIntervalSeconds = 30f;

		private const float BindRetrySeconds = 30f;

		private QueryConfig? _config;

		private ModLogger? _log;

		private Func<bool>? _enabledCheck;

		private QueryResponder? _responder;

		private volatile QuerySnapshot _snapshot = QuerySnapshot.Empty;

		private ZNet? _sessionNetwork;

		private string _gameVersion = "unknown";

		private DateTime _startTimeUtc;

		private float _nextSnapshotRefresh;

		private float _nextStartAttempt;

		private int _gamePort = 2456;

		private int _responderPort;

		private int _lastInvalidQueryPort = int.MinValue;

		private int _deprecatedMaxPlayers;

		private bool _deprecatedMaxPlayersChecked;

		private bool _passworded;

		private bool _versionDetected;

		private bool _sessionActive;

		private bool _idle;

		private bool _stopped;

		public static QueryBehaviour? Instance { get; private set; }

		public static void Initialize(GameObject host, QueryConfig config, ModLogger log, Func<bool> enabledCheck)
		{
			QueryBehaviour queryBehaviour = host.AddComponent<QueryBehaviour>();
			queryBehaviour._config = config;
			queryBehaviour._log = log;
			queryBehaviour._enabledCheck = enabledCheck;
			queryBehaviour.ReadCommandLine();
			Instance = queryBehaviour;
		}

		private void Update()
		{
			QueryConfig config = _config;
			ModLogger log = _log;
			if (_stopped || config == null || log == null || _enabledCheck == null)
			{
				return;
			}
			if (!_enabledCheck())
			{
				StopSession();
				return;
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !instance.IsServer())
			{
				StopSession();
				return;
			}
			World world = QueryGameAccess.GetWorld();
			if (world == null)
			{
				StopSession();
				return;
			}
			if (!_sessionActive || _sessionNetwork != instance)
			{
				StopSession();
				StartSession(instance, log);
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			bool flag = QueryGameAccess.GetPeers(instance).Count == 0;
			if (flag != _idle)
			{
				RefreshSnapshot(instance, world, config);
				_idle = flag;
				_nextSnapshotRefresh = realtimeSinceStartup + (flag ? 30f : 2f);
			}
			else if (realtimeSinceStartup >= _nextSnapshotRefresh)
			{
				RefreshSnapshot(instance, world, config);
				_nextSnapshotRefresh = realtimeSinceStartup + (_idle ? 30f : 2f);
			}
			int effectiveQueryPort = GetEffectiveQueryPort(config, log);
			if (_responder == null || _responderPort != effectiveQueryPort)
			{
				StopResponder();
				_responder = new QueryResponder(effectiveQueryPort, () => _snapshot, log);
				_responderPort = effectiveQueryPort;
				_nextStartAttempt = 0f;
			}
			if (!_responder.IsRunning && realtimeSinceStartup >= _nextStartAttempt && !_responder.Start())
			{
				_nextStartAttempt = realtimeSinceStartup + 30f;
			}
		}

		private void StartSession(ZNet network, ModLogger log)
		{
			_sessionNetwork = network;
			_startTimeUtc = DateTime.UtcNow;
			_nextSnapshotRefresh = 0f;
			_nextStartAttempt = 0f;
			_idle = QueryGameAccess.GetPeers(network).Count == 0;
			_sessionActive = true;
			if (!_versionDetected)
			{
				_gameVersion = GameVersionDetector.TryDetect(log) ?? "unknown";
				_versionDetected = true;
			}
		}

		private void RefreshSnapshot(ZNet network, World world, QueryConfig config)
		{
			string text = (string.IsNullOrWhiteSpace(world.m_name) ? "world" : world.m_name);
			string text2 = QueryGameAccess.GetServerName();
			if (string.IsNullOrWhiteSpace(text2))
			{
				text2 = text;
			}
			int num = ServerHostModule.EffectiveMaxPlayers();
			if (num == 10)
			{
				int deprecatedQueryMaxPlayers = GetDeprecatedQueryMaxPlayers();
				if (deprecatedQueryMaxPlayers > 0)
				{
					num = deprecatedQueryMaxPlayers;
				}
			}
			List<string> list = new List<string>();
			List<ZNetPeer> peers = QueryGameAccess.GetPeers(network);
			for (int i = 0; i < peers.Count; i++)
			{
				ZNetPeer val = peers[i];
				if (val != null && !((ZDOID)(ref val.m_characterID)).IsNone())
				{
					string item = (config.PublicPlayerNames ? (val.m_playerName ?? string.Empty) : $"Player {list.Count + 1}");
					list.Add(item);
				}
			}
			string[] array = list.ToArray();
			_snapshot = new QuerySnapshot(text2, text, array.Length, array, num, _gamePort, _passworded, _gameVersion, "0.13.29", _startTimeUtc);
		}

		private int GetDeprecatedQueryMaxPlayers()
		{
			if (_deprecatedMaxPlayersChecked)
			{
				return _deprecatedMaxPlayers;
			}
			_deprecatedMaxPlayersChecked = true;
			try
			{
				string path = Path.Combine(Paths.ConfigPath, "valheimone.cfg");
				if (!File.Exists(path))
				{
					return 0;
				}
				bool flag = false;
				string[] array = File.ReadAllLines(path);
				for (int i = 0; i < array.Length; i++)
				{
					string text = array[i].Trim();
					if (text.StartsWith("[", StringComparison.Ordinal))
					{
						flag = string.Equals(text, "[Query]", StringComparison.OrdinalIgnoreCase);
					}
					else if (flag && text.StartsWith("MaxPlayers", StringComparison.OrdinalIgnoreCase))
					{
						int num = text.IndexOf('=');
						if (num >= 0 && int.TryParse(text.Substring(num + 1).Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result > 0 && result != 10)
						{
							_deprecatedMaxPlayers = Math.Min(result, 255);
							_log?.Warning("[Query] MaxPlayers is deprecated; move the value to [Server] MaxPlayers. This fallback will be removed in the next release.");
							break;
						}
					}
				}
			}
			catch (Exception)
			{
			}
			return _deprecatedMaxPlayers;
		}

		private int GetEffectiveQueryPort(QueryConfig config, ModLogger log)
		{
			int queryPort = config.QueryPort;
			if (queryPort == 0)
			{
				_lastInvalidQueryPort = int.MinValue;
				return _gamePort + 4;
			}
			if (queryPort >= 1 && queryPort <= 65535)
			{
				_lastInvalidQueryPort = int.MinValue;
				return queryPort;
			}
			if (_lastInvalidQueryPort != queryPort)
			{
				log.Warning($"[Query] QueryPort {queryPort} is invalid; using game port + 4 ({_gamePort + 4}).");
				_lastInvalidQueryPort = queryPort;
			}
			return _gamePort + 4;
		}

		private void ReadCommandLine()
		{
			string[] commandLineArgs = Environment.GetCommandLineArgs();
			if (int.TryParse(GetCommandLineValue(commandLineArgs, "-port"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result >= 1 && result <= 65531)
			{
				_gamePort = result;
			}
			_passworded = !string.IsNullOrEmpty(GetCommandLineValue(commandLineArgs, "-password"));
		}

		private static string GetCommandLineValue(string[] args, string name)
		{
			string text = name + "=";
			for (int i = 0; i < args.Length; i++)
			{
				string text2 = args[i] ?? string.Empty;
				if (text2.StartsWith(text, StringComparison.OrdinalIgnoreCase))
				{
					return text2.Substring(text.Length);
				}
				if (string.Equals(text2, name, StringComparison.OrdinalIgnoreCase) && i + 1 < args.Length)
				{
					string text3 = args[i + 1] ?? string.Empty;
					if (!text3.StartsWith("-", StringComparison.Ordinal))
					{
						return text3;
					}
					return string.Empty;
				}
			}
			return string.Empty;
		}

		private void OnApplicationQuit()
		{
			StopServices();
		}

		private void OnDestroy()
		{
			StopServices();
			if (Instance == this)
			{
				Instance = null;
			}
		}

		private void StopServices()
		{
			if (!_stopped)
			{
				_stopped = true;
				StopSession();
			}
		}

		private void StopSession()
		{
			StopResponder();
			_snapshot = QuerySnapshot.Empty;
			_sessionNetwork = null;
			_idle = false;
			_sessionActive = false;
		}

		private void StopResponder()
		{
			_responder?.Stop();
			_responder = null;
			_responderPort = 0;
		}
	}
	internal sealed class QueryConfig
	{
		private readonly ConfigEntryInt _queryPort;

		private readonly ConfigEntryBool _publicPlayerNames;

		public int QueryPort => _queryPort.Value;

		public bool PublicPlayerNames => _publicPlayerNames.Value;

		public QueryConfig(ConfigEntryInt queryPort, ConfigEntryBool publicPlayerNames)
		{
			_queryPort = queryPort;
			_publicPlayerNames = publicPlayerNames;
		}
	}
	internal static class QueryGameAccess
	{
		private static readonly Lazy<FieldInfo> WorldField = new Lazy<FieldInfo>(() => AccessTools.Field(typeof(ZNet), "m_world") ?? throw new MissingFieldException(typeof(ZNet).FullName, "m_world"));

		private static readonly Lazy<FieldInfo> PeersField = new Lazy<FieldInfo>(() => AccessTools.Field(typeof(ZNet), "m_peers") ?? throw new MissingFieldException(typeof(ZNet).FullName, "m_peers"));

		private static readonly Lazy<FieldInfo> ServerNameField = new Lazy<FieldInfo>(() => AccessTools.Field(typeof(ZNet), "m_ServerName") ?? throw new MissingFieldException(typeof(ZNet).FullName, "m_ServerName"));

		public static World? GetWorld()
		{
			object? value = WorldField.Value.GetValue(null);
			return (World?)((value is World) ? value : null);
		}

		public static List<ZNetPeer> GetPeers(ZNet network)
		{
			return (PeersField.Value.GetValue(network) as List<ZNetPeer>) ?? throw new InvalidOperationException("ZNet.m_peers did not contain a peer list.");
		}

		public static string GetServerName()
		{
			return (ServerNameField.Value.GetValue(null) as string) ?? string.Empty;
		}
	}
	public sealed class QueryModule : IFeatureModule
	{
		private readonly FeatureDefinition _feature;

		private readonly QueryConfig _config;

		private readonly ModLogger _log;

		public string Name => "Server query";

		public string Section => "Query";

		public bool IsEnabled => _feature.Enabled.Value;

		public FeatureClassification Classification => FeatureClassification.ServerAuthoritative;

		public QueryModule(FeatureRegistry registry)
		{
			_log = new ModLogger(Logger.CreateLogSource("ValheimOne.Query"));
			_feature = registry.Register(Name, Section, Classification);
			ConfigEntryInt queryPort = _feature.Int("QueryPort", 0, "UDP port for the A2S query responder. 0 uses game port + 4. (Game port + 1 is Valheim's own Steam query port when crossplay is off; ValheimOne avoids it.)");
			ConfigEntryBool publicPlayerNames = _feature.Bool("PublicPlayerNames", defaultValue: false, "Report real player names in A2S_PLAYER replies. When disabled, report generic slots.");
			_config = new QueryConfig(queryPort, publicPlayerNames);
		}

		public void ApplyPatches(Harmony harmony)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Expected O, but got Unknown
			//IL_0053: Expected O, but got Unknown
			if ((Object)(object)QueryBehaviour.Instance != (Object)null)
			{
				_log.Warning("[Query] behaviour already exists; skipping duplicate initialization.");
				return;
			}
			GameObject val = new GameObject("ValheimOne.Query")
			{
				hideFlags = (HideFlags)61
			};
			Object.DontDestroyOnLoad((Object)val);
			QueryBehaviour.Initialize(val, _config, _log, () => _feature.Enabled.Value);
		}
	}
	internal sealed class QueryResponder
	{
		private sealed class ChallengeEntry
		{
			public int Value { get; }

			public long IssuedTicks { get; }

			public long ExpiresTicks { get; }

			public ChallengeEntry(int value, long issuedTicks, long expiresTicks)
			{
				Value = value;
				IssuedTicks = issuedTicks;
				ExpiresTicks = expiresTicks;
			}
		}

		private sealed class PacketWriter : IDisposable
		{
			private readonly MemoryStream _stream = new MemoryStream();

			public void WriteHeader(byte responseType)
			{
				WriteByte(byte.MaxValue);
				WriteByte(byte.MaxValue);
				WriteByte(byte.MaxValue);
				WriteByte(byte.MaxValue);
				WriteByte(responseType);
			}

			public void WriteByte(byte value)
			{
				_stream.WriteByte(value);
			}

			public void WriteInt16(short value)
			{
				ushort num = (ushort)value;
				WriteByte((byte)num);
				WriteByte((byte)(num >> 8));
			}

			public void WriteInt32(int value)
			{
				WriteByte((byte)value);
				WriteByte((byte)((uint)value >> 8));
				WriteByte((byte)((uint)value >> 16));
				WriteByte((byte)((uint)value >> 24));
			}

			public void WriteUInt64(ulong value)
			{
				for (int i = 0; i < 64; i += 8)
				{
					WriteByte((byte)(value >> i));
				}
			}

			public void WriteSingle(float value)
			{
				byte[] bytes = BitConverter.GetBytes(value);
				if (!BitConverter.IsLittleEndian)
				{
					Array.Reverse((Array)bytes);
				}
				_stream.Write(bytes, 0, bytes.Length);
			}

			public void WriteString(string value)
			{
				string s = (value ?? string.Empty).Replace('\0', ' ');
				byte[] bytes = Encoding.UTF8.GetBytes(s);
				_stream.Write(bytes, 0, bytes.Length);
				WriteByte(0);
			}

			public byte[] ToArray()
			{
				return _stream.ToArray();
			}

			public void Dispose()
			{
				_stream.Dispose();
			}
		}

		private const int AppId = 892970;

		private const int MaximumDatagramSize = 1400;

		private const int MaximumChallenges = 256;

		private const long ChallengeLifetimeTicks = 300000000L;

		private static readonly byte EnvironmentByte = (byte)((Environment.OSVersion.Platform == PlatformID.Win32NT) ? 119 : 108);

		private static readonly byte[] RequestPrefix = new byte[4] { 255, 255, 255, 255 };

		private static readonly byte[] InfoRequestName = Encoding.ASCII.GetBytes("Source Engine Query\0");

		private readonly int _port;

		private readonly Func<QuerySnapshot> _snapshotProvider;

		private readonly ModLogger _log;

		private readonly RandomNumberGenerator _random = RandomNumberGenerator.Create();

		private readonly Dictionary<IPEndPoint, ChallengeEntry> _challenges = new Dictionary<IPEndPoint, ChallengeEntry>();

		private UdpClient? _socket;

		private Thread? _thread;

		private volatile bool _stopping;

		private bool _bindWarningLogged;

		private bool _workerWarningLogged;

		public bool IsRunning
		{
			get
			{
				if (_thread != null)
				{
					return _thread.IsAlive;
				}
				return false;
			}
		}

		public QueryResponder(int port, Func<QuerySnapshot> snapshotProvider, ModLogger log)
		{
			_port = port;
			_snapshotProvider = snapshotProvider;
			_log = log;
		}

		public bool Start()
		{
			if (IsRunning)
			{
				return true;
			}
			CloseSocket();
			UdpClient socket;
			try
			{
				socket = new UdpClient(new IPEndPoint(IPAddress.Any, _port));
			}
			catch (Exception ex)
			{
				if (!_bindWarningLogged)
				{
					_bindWarningLogged = true;
					_log.Warning($"[Query] A2S responder could not bind UDP {_port}: " + ex.GetType().Name + ": " + ex.Message);
				}
				return false;
			}
			_stopping = false;
			_workerWarningLogged = false;
			_socket = socket;
			_thread = new Thread((ThreadStart)delegate
			{
				Listen(socket);
			})
			{
				IsBackground = true,
				Name = "ValheimOne.Query"
			};
			_thread.Start();
			_log.Info($"[Query] A2S responder listening on UDP {_port}");
			return true;
		}

		public void Stop()
		{
			bool isRunning = IsRunning;
			_stopping = true;
			CloseSocket();
			Thread thread = _thread;
			if (thread != null && thread.IsAlive && Thread.CurrentThread != thread)
			{
				thread.Join();
			}
			_thread = null;
			_challenges.Clear();
			_random.Dispose();
			if (isRunning)
			{
				_log.Info($"[Query] A2S responder stopped on UDP {_port}");
			}
		}

		private void Listen(UdpClient socket)
		{
			while (!_stopping)
			{
				try
				{
					IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
					byte[] datagram = socket.Receive(ref remoteEP);
					HandleDatagram(socket, remoteEP, datagram);
				}
				catch (ObjectDisposedException)
				{
					if (!_stopping)
					{
						LogWorkerFailure("UDP socket was disposed unexpectedly.");
					}
				}
				catch (SocketException ex2)
				{
					if (!_stopping)
					{
						LogWorkerFailure("SocketException: " + ex2.Message);
					}
				}
				catch (Exception ex3)
				{
					if (!_stopping)
					{
						LogWorkerFailure(ex3.GetType().Name + ": " + ex3.Message);
					}
				}
			}
		}

		private void HandleDatagram(UdpClient socket, IPEndPoint remoteEndpoint, byte[] datagram)
		{
			if (datagram.Length >= 5 && datagram.Length <= 1400 && HasPrefix(datagram))
			{
				switch (datagram[4])
				{
				case 84:
					HandleInfoRequest(socket, remoteEndpoint, datagram);
					break;
				case 85:
					HandlePlayerRequest(socket, remoteEndpoint, datagram);
					break;
				}
			}
		}

		private void HandleInfoRequest(UdpClient socket, IPEndPoint remoteEndpoint, byte[] datagram)
		{
			int num = 5 + InfoRequestName.Length;
			if ((datagram.Length != num && datagram.Length != num + 4) || !MatchesInfoRequestName(datagram))
			{
				return;
			}
			if (datagram.Length == num || !HasValidChallenge(remoteEndpoint, ReadInt32(datagram, num)))
			{
				SendChallenge(socket, remoteEndpoint);
				return;
			}
			QuerySnapshot querySnapshot = _snapshotProvider();
			using PacketWriter packetWriter = new PacketWriter();
			packetWriter.WriteHeader(73);
			packetWriter.WriteByte(17);
			packetWriter.WriteString(querySnapshot.ServerName);
			packetWriter.WriteString(querySnapshot.WorldName);
			packetWriter.WriteString("valheim");
			packetWriter.WriteString("Valheim");
			packetWriter.WriteInt16(-24534);
			packetWriter.WriteByte(ClampByte(querySnapshot.PlayerCount));
			packetWriter.WriteByte(ClampByte(querySnapshot.MaxPlayers));
			packetWriter.WriteByte(0);
			packetWriter.WriteByte(100);
			packetWriter.WriteByte(EnvironmentByte);
			packetWriter.WriteByte(querySnapshot.Passworded ? ((byte)1) : ((byte)0));
			packetWriter.WriteByte(0);
			packetWriter.WriteString(querySnapshot.GameVersion);
			packetWriter.WriteByte(161);
			packetWriter.WriteInt16((short)querySnapshot.GamePort);
			packetWriter.WriteString("valheimone,vo=" + querySnapshot.PluginVersion);
			packetWriter.WriteUInt64(892970uL);
			Send(socket, remoteEndpoint, packetWriter.ToArray());
		}

		private void HandlePlayerRequest(UdpClient socket, IPEndPoint remoteEndpoint, byte[] datagram)
		{
			if (datagram.Length != 9)
			{
				return;
			}
			int num = ReadInt32(datagram, 5);
			if (num == -1 || !HasValidChallenge(remoteEndpoint, num))
			{
				SendChallenge(socket, remoteEndpoint);
				return;
			}
			QuerySnapshot querySnapshot = _snapshotProvider();
			int num2 = Math.Min(255, Math.Max(0, querySnapshot.PlayerCount));
			double totalSeconds = (DateTime.UtcNow - querySnapshot.StartTimeUtc).TotalSeconds;
			float value = ((totalSeconds <= 0.0) ? 0f : ((totalSeconds >= 3.4028234663852886E+38) ? float.MaxValue : ((float)totalSeconds)));
			using PacketWriter packetWriter = new PacketWriter();
			packetWriter.WriteHeader(68);
			packetWriter.WriteByte((byte)num2);
			for (int i = 0; i < num2; i++)
			{
				string value2 = querySnapshot.GetPlayerName(i);
				if (string.IsNullOrEmpty(value2))
				{
					value2 = $"Player {i + 1}";
				}
				packetWriter.WriteByte((byte)i);
				packetWriter.WriteString(value2);
				packetWriter.WriteInt32(0);
				packetWriter.WriteSingle(value);
			}
			Send(socket, remoteEndpoint, packetWriter.ToArray());
		}

		private bool HasValidChallenge(IPEndPoint remoteEndpoint, int challenge)
		{
			long ticks = DateTime.UtcNow.Ticks;
			RemoveExpiredChallenges(ticks);
			if (_challenges.TryGetValue(remoteEndpoint, out ChallengeEntry value) && value.ExpiresTicks > ticks)
			{
				return value.Value == challenge;
			}
			return false;
		}

		private void SendChallenge(UdpClient socket, IPEndPoint remoteEndpoint)
		{
			int value = CreateChallenge(remoteEndpoint);
			using PacketWriter packetWriter = new PacketWriter();
			packetWriter.WriteHeader(65);
			packetWriter.WriteInt32(value);
			Send(socket, remoteEndpoint, packetWriter.ToArray());
		}

		private int CreateChallenge(IPEndPoint remoteEndpoint)
		{
			long ticks = DateTime.UtcNow.Ticks;
			RemoveExpiredChallenges(ticks);
			if (_challenges.Count >= 256)
			{
				IPEndPoint iPEndPoint = null;
				long num = long.MaxValue;
				foreach (KeyValuePair<IPEndPoint, ChallengeEntry> challenge in _challenges)
				{
					if (challenge.Value.IssuedTicks < num)
					{
						iPEndPoint = challenge.Key;
						num = challenge.Value.IssuedTicks;
					}
				}
				if (iPEndPoint != null)
				{
					_challenges.Remove(iPEndPoint);
				}
			}
			byte[] array = new byte[4];
			int num2;
			do
			{
				_random.GetBytes(array);
				num2 = ReadInt32(array, 0);
			}
			while (num2 == 0 || num2 == -1);
			IPEndPoint key = new IPEndPoint(remoteEndpoint.Address, remoteEndpoint.Port);
			_challenges[key] = new ChallengeEntry(num2, ticks, ticks + 300000000);
			return num2;
		}

		private void RemoveExpiredChallenges(long nowTicks)
		{
			if (_challenges.Count == 0)
			{
				return;
			}
			List<IPEndPoint> list = new List<IPEndPoint>();
			foreach (KeyValuePair<IPEndPoint, ChallengeEntry> challenge in _challenges)
			{
				if (challenge.Value.ExpiresTicks <= nowTicks)
				{
					list.Add(challenge.Key);
				}
			}
			for (int i = 0; i < list.Count; i++)
			{
				_challenges.Remove(list[i]);
			}
		}

		private static bool HasPrefix(byte[] datagram)
		{
			for (int i = 0; i < RequestPrefix.Length; i++)
			{
				if (datagram[i] != RequestPrefix[i])
				{
					return false;
				}
			}
			return true;
		}

		private static bool MatchesInfoRequestName(byte[] datagram)
		{
			for (int i = 0; i < InfoRequestName.Length; i++)
			{
				if (datagram[i + 5] != InfoRequestName[i])
				{
					return false;
				}
			}
			return true;
		}

		private static int ReadInt32(byte[] bytes, int offset)
		{
			return bytes[offset] | (bytes[offset + 1] << 8) | (bytes[offset + 2] << 16) | (bytes[offset + 3] << 24);
		}

		private static byte ClampByte(int value)
		{
			return (byte)Math.Min(255, Math.Max(0, value));
		}

		private static void Send(UdpClient socket, IPEndPoint remoteEndpoint, byte[] response)
		{
			socket.Send(response, response.Length, remoteEndpoint);
		}

		private void CloseSocket()
		{
			UdpClient socket = _socket;
			_socket = null;
			if (socket == null)
			{
				return;
			}
			try
			{
				socket.Close();
			}
			catch (ObjectDisposedException)
			{
			}
		}

		private void LogWorkerFailure(string message)
		{
			if (!_workerWarningLogged)
			{
				_workerWarningLogged = true;
				_log.Warning("[Query] A2S responder worker error: " + message);
			}
		}
	}
	internal sealed class QuerySnapshot
	{
		private readonly string[] _playerNames;

		public static readonly QuerySnapshot Empty = new QuerySnapshot(string.Empty, string.Empty, 0, Array.Empty<string>(), 0, 2456, passworded: false, "unknown", "0.13.29", DateTime.UtcNow);

		public string ServerName { get; }

		public string WorldName { get; }

		public int PlayerCount { get; }

		public int MaxPlayers { get; }

		public int GamePort { get; }

		public bool Passworded { get; }

		public string GameVersion { get; }

		public string PluginVersion { get; }

		public DateTime StartTimeUtc { get; }

		public QuerySnapshot(string serverName, string worldName, int playerCount, string[] playerNames, int maxPlayers, int gamePort, bool passworded, string gameVersion, string pluginVersion, DateTime startTimeUtc)
		{
			ServerName = serverName;
			WorldName = worldName;
			PlayerCount = playerCount;
			_playerNames = (string[])playerNames.Clone();
			MaxPlayers = maxPlayers;
			GamePort = gamePort;
			Passworded = passworded;
			GameVersion = gameVersion;
			PluginVersion = pluginVersion;
			StartTimeUtc = startTimeUtc;
		}

		public string GetPlayerName(int index)
		{
			if (index < 0 || index >= _playerNames.Length)
			{
				return string.Empty;
			}
			return _playerNames[index];
		}
	}
}
namespace ValheimOne.Networking
{
	internal sealed class AckGatedChunkQueue<T> where T : class, IAcknowledgedChunk
	{
		private readonly Queue<T> _pending = new Queue<T>();

		public bool IsAwaitingAcknowledgement { get; private set; }

		public int AwaitingIndex { get; private set; } = -1;

		public int AcknowledgedCount { get; private set; }

		public int PendingCount => _pending.Count;

		public bool IsIdle
		{
			get
			{
				if (!IsAwaitingAcknowledgement)
				{
					return _pending.Count == 0;
				}
				return false;
			}
		}

		public void Enqueue(T chunk)
		{
			_pending.Enqueue(chunk);
		}

		public bool TryStartNext(out T? chunk)
		{
			if (IsAwaitingAcknowledgement || _pending.Count == 0)
			{
				chunk = null;
				return false;
			}
			chunk = _pending.Dequeue();
			IsAwaitingAcknowledgement = true;
			AwaitingIndex = chunk.Index;
			return true;
		}

		public bool TryAcknowledge(int index)
		{
			if (!IsAwaitingAcknowledgement || AwaitingIndex != index)
			{
				return false;
			}
			IsAwaitingAcknowledgement = false;
			AwaitingIndex = -1;
			AcknowledgedCount++;
			return true;
		}

		public void Reset()
		{
			_pending.Clear();
			IsAwaitingAcknowledgement = false;
			AwaitingIndex = -1;
			AcknowledgedCount = 0;
		}
	}
	internal static class ConfigSyncSerializer
	{
		public const int MaximumChunkTextBytes = 4000;

		public static string Serialize(FeatureRegistry registry)
		{
			StringBuilder stringBuilder = new StringBuilder();
			foreach (FeatureDefinition feature in registry.Features)
			{
				if (feature.Classification == FeatureClassification.ClientOnly || feature.Classification == FeatureClassification.ServerOnly)
				{
					continue;
				}
				foreach (IConfigEntry key in feature.Keys)
				{
					if (!key.Definition.IsSensitive)
					{
						stringBuilder.Append('[').Append(feature.Section).Append("] / ")
							.Append(key.Definition.Name)
							.Append('=')
							.Append(key.GetSerializedValue())
							.Append('\n');
					}
				}
			}
			return stringBuilder.ToString();
		}

		public static string ComputeHash(string serializedConfig)
		{
			byte[] bytes = Encoding.UTF8.GetBytes(serializedConfig);
			byte[] array;
			using (SHA256 sHA = SHA256.Create())
			{
				array = sHA.ComputeHash(bytes);
			}
			StringBuilder stringBuilder = new StringBuilder(array.Length * 2);
			byte[] array2 = array;
			foreach (byte b in array2)
			{
				stringBuilder.Append(b.ToString("x2", CultureInfo.InvariantCulture));
			}
			return stringBuilder.ToString();
		}

		public static IReadOnlyList<string> CreateChunks(string serializedConfig)
		{
			List<string> list = new List<string>();
			StringBuilder stringBuilder = new StringBuilder();
			int num = 0;
			using StringReader stringReader = new StringReader(serializedConfig);
			string text;
			while ((text = stringReader.ReadLine()) != null)
			{
				string text2 = text + "\n";
				int byteCount = Encoding.UTF8.GetByteCount(text2);
				if (byteCount > 4000)
				{
					throw new InvalidOperationException($"A serialized config line exceeds the {4000}-byte chunk limit.");
				}
				if (stringBuilder.Length != 0 && num + byteCount > 4000)
				{
					list.Add(stringBuilder.ToString());
					stringBuilder.Clear();
					num = 0;
				}
				stringBuilder.Append(text2);
				num += byteCount;
			}
			if (stringBuilder.Length != 0 || list.Count == 0)
			{
				list.Add(stringBuilder.ToString());
			}
			return list;
		}
	}
	internal readonly struct ExploredMapRange
	{
		public int StartX { get; }

		public int EndX { get; }

		public int Y { get; }

		public ExploredMapRange(int startX, int endX, int y)
		{
			StartX = startX;
			EndX = endX;
			Y = y;
		}
	}
	internal interface IAcknowledgedChunk
	{
		int Index { get; }
	}
	public interface IVersionHandshake
	{
		bool IsAvailable { get; }

		void Initialize(Harmony harmony);

		void Shutdown();
	}
	public interface IVersionHandshakeExtension
	{
		void RegisterRpcHandlers(ZRoutedRpc routedRpc);

		void OnPeerCompatible(long peerId);

		void Pump(ZNet net, ZRoutedRpc routedRpc, IReadOnlyCollection<long> compatiblePeerIds);

		bool TryHandleAcknowledgement(long sender, string channel, int index);

		void ResetNetworkState();

		void Shutdown();
	}
	public sealed class VersionHandshake : IVersionHandshake
	{
		private sealed class PeerHandshakeState
		{
			public ZNetPeer Peer { get; }

			public long PeerId { get; }

			public float FirstObservedAt { get; }

			public bool HelloReceived { get; set; }

			public bool Compatible { get; set; }

			public bool VanillaClassified { get; set; }

			public bool KickIssued { get; set; }

			public string RemoteVersion { get; set; } = "unknown";

			public int RemoteSchema { get; set; } = -1;

			public string RemoteConfigHash { get; set; } = string.Empty;

			public AckGatedChunkQueue<OutboundConfigChunk> ConfigQueue { get; } = new AckGatedChunkQueue<OutboundConfigChunk>();

			public PeerHandshakeState(ZNetPeer peer, float firstObservedAt)
			{
				Peer = peer;
				PeerId = peer.m_uid;
				FirstObservedAt = firstObservedAt;
			}

			public void ResetConfigPush()
			{
				ConfigQueue.Reset();
			}
		}

		private sealed class ClientConfigBuffer
		{
			public int TotalChunks { get; }

			public string?[] Chunks { get; }

			public int ReceivedChunks { get; set; }

			public ClientConfigBuffer(int totalChunks)
			{
				TotalChunks = totalChunks;
				Chunks = new string[totalChunks];
			}
		}

		private sealed class OutboundConfigChunk : IAcknowledgedChunk
		{
			public int TotalChunks { get; }

			public int Index { get; }

			public string Text { get; }

			public OutboundConfigChunk(int totalChunks, int index, string text)
			{
				TotalChunks = totalChunks;
				Index = index;
				Text = text;
			}
		}

		private const string HelloRpc = "VO_Hello";

		private const string ConfigRpc = "VO_Config";

		internal const string AckRpc = "VO_Ack";

		private const int MaximumConfigChunks = 1024;

		private static VersionHandshake? _active;

		private readonly ValheimOneConfig _settings;

		private readonly ServerConfig _serverConfig;

		private readonly ModLogger _log;

		private readonly IReadOnlyList<IVersionHandshakeExtension> _extensions;

		private readonly Dictionary<long, PeerHandshakeState> _peerStates = new Dictionary<long, PeerHandshakeState>();

		private readonly List<ZRoutedRpc> _registeredRpcInstances = new List<ZRoutedRpc>();

		private readonly List<ZRoutedRpc> _failedRpcInstances = new List<ZRoutedRpc>();

		private ClientConfigBuffer? _clientConfigBuffer;

		private bool _clientHelloFailureLogged;

		private bool _clientHelloSent;

		private bool _initialized;

		public bool IsAvailable
		{
			get
			{
				if (_active == this)
				{
					return _registeredRpcInstances.Count != 0;
				}
				return false;
			}
		}

		private bool IsEnforcementEnabled
		{
			get
			{
				if (_serverConfig.Enabled)
				{
					return _serverConfig.EnforceMod.Value;
				}
				return false;
			}
		}

		public VersionHandshake(ValheimOneConfig settings, ServerConfig serverConfig, ModLogger log)
			: this(settings, serverConfig, log, Array.Empty<IVersionHandshakeExtension>())
		{
		}

		internal VersionHandshake(ValheimOneConfig settings, ServerConfig serverConfig, ModLogger log, IReadOnlyList<IVersionHandshakeExtension> extensions)
		{
			_settings = settings;
			_serverConfig = serverConfig;
			_log = log;
			_extensions = extensions;
		}

		public void Initialize(Harmony harmony)
		{
			if (!_initialized)
			{
				_active = this;
				PatchPostfix(harmony, typeof(Game), "Start", Type.EmptyTypes, "GameStartPostfix");
				PatchPostfix(harmony, typeof(ZNet), "RPC_PeerInfo", new Type[2]
				{
					typeof(ZRpc),
					typeof(ZPackage)
				}, "PeerInfoPostfix");
				PatchPostfix(harmony, typeof(ZNet), "SendPeriodicData", new Type[1] { typeof(float) }, "SendPeriodicDataPostfix");
				PatchPostfix(harmony, typeof(ZNet), "OnDestroy", Type.EmptyTypes, "ZNetOnDestroyPostfix");
				_initialized = true;
				_log.Info("Server enforcement chassis ready (VO_Hello, VO_Config, VO_Ack).");
			}
		}

		public void Shutdown()
		{
			if (!_initialized)
			{
				return;
			}
			if (_active == this)
			{
				_active = null;
			}
			foreach (IVersionHandshakeExtension extension in _extensions)
			{
				extension.Shutdown();
			}
			_settings.ClearOverlay();
			_peerStates.Clear();
			_clientConfigBuffer = null;
			_clientHelloFailureLogged = false;
			_clientHelloSent = false;
			_registeredRpcInstances.Clear();
			_failedRpcInstances.Clear();
			_initialized = false;
		}

		private static void PatchPostfix(Harmony harmony, Type declaringType, string methodName, Type[] parameterTypes, string postfixName)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(declaringType, methodName, parameterTypes, (Type[])null) ?? throw new MissingMethodException(declaringType.FullName, methodName);
			HarmonyMethod val = new HarmonyMethod(typeof(VersionHandshake), postfixName, (Type[])null);
			harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private static void GameStartPostfix()
		{
			VersionHandshake active = _active;
			if (active != null)
			{
				active.RegisterRpcHandlers();
				ZNet instance = ZNet.instance;
				if ((Object)(object)instance != (Object)null && !instance.IsServer())
				{
					active.TrySendClientHello(instance);
				}
			}
		}

		private static void PeerInfoPostfix(ZNet __instance)
		{
			if (!__instance.IsServer())
			{
				_active?.TrySendClientHello(__instance);
			}
		}

		private static void SendPeriodicDataPostfix(ZNet __instance)
		{
			VersionHandshake active = _active;
			if (active != null)
			{
				active.RegisterRpcHandlers();
				if (__instance.IsServer())
				{
					active.PumpServer(__instance);
				}
				else if (!active._clientHelloSent)
				{
					active.TrySendClientHello(__instance);
				}
				active.PumpExtensions(__instance);
			}
		}

		private static void ZNetOnDestroyPostfix()
		{
			_active?.ResetNetworkState();
		}

		private void RegisterRpcHandlers()
		{
			if (_active != this)
			{
				return;
			}
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance == null || IsRegistered(instance) || IsRegistrationFailed(instance))
			{
				return;
			}
			try
			{
				instance.Register<ZPackage>("VO_Hello", (Action<long, ZPackage>)HandleHello);
				instance.Register<ZPackage>("VO_Config", (Action<long, ZPackage>)HandleConfig);
				instance.Register<ZPackage>("VO_Ack", (Action<long, ZPackage>)HandleAck);
				foreach (IVersionHandshakeExtension extension in _extensions)
				{
					extension.RegisterRpcHandlers(instance);
				}
				_registeredRpcInstances.Add(instance);
				_log.Debug("Registered ValheimOne routed RPC handlers.");
			}
			catch (Exception ex)
			{
				_failedRpcInstances.Add(instance);
				_log.Error("Unable to register ValheimOne routed RPCs: " + ex.Message);
			}
		}

		private bool IsRegistered(ZRoutedRpc routedRpc)
		{
			foreach (ZRoutedRpc registeredRpcInstance in _registeredRpcInstances)
			{
				if (registeredRpcInstance == routedRpc)
				{
					return true;
				}
			}
			return false;
		}

		private bool IsRegistrationFailed(ZRoutedRpc routedRpc)
		{
			foreach (ZRoutedRpc failedRpcInstance in _failedRpcInstances)
			{
				if (failedRpcInstance == routedRpc)
				{
					return true;
				}
			}
			return false;
		}

		private void TrySendClientHello(ZNet net)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Expected O, but got Unknown
			if (_clientHelloSent || net.IsServer())
			{
				return;
			}
			RegisterRpcHandlers();
			ZRoutedRpc instance = ZRoutedRpc.instance;
			ZNetPeer serverPeer = net.GetServerPeer();
			if (instance == null || !IsRegistered(instance) || serverPeer == null || !serverPeer.IsReady())
			{
				return;
			}
			long uid = serverPeer.m_uid;
			if (uid == 0L)
			{
				return;
			}
			try
			{
				string serializedConfig = ConfigSyncSerializer.Serialize(_settings.Features);
				ZPackage val = new ZPackage();
				val.Write("0.13.29");
				val.Write(1);
				val.Write(ConfigSyncSerializer.ComputeHash(serializedConfig));
				instance.InvokeRoutedRPC(uid, "VO_Hello", new object[1] { val });
				_clientHelloSent = true;
				_log.Info("Sent VO_Hello to server (ValheimOne v0.13.29, " + $"schema {1}).");
			}
			catch (Exception ex)
			{
				if (!_clientHelloFailureLogged)
				{
					_clientHelloFailureLogged = true;
					_log.Warning("Unable to send VO_Hello to the server: " + ex.Message);
				}
			}
		}

		private void HandleHello(long sender, ZPackage package)
		{
			ZNet instance = ZNet.instance;
			if (_active != this || (Object)(object)instance == (Object)null || !instance.IsServer())
			{
				return;
			}
			ZNetPeer peer = instance.GetPeer(sender);
			if (peer == null || peer.m_server)
			{
				_log.Warning(string.Format("Ignored {0} from unknown peer uid {1}.", "VO_Hello", sender));
				return;
			}
			string text;
			int num;
			string text2;
			try
			{
				text = package.ReadString();
				num = package.ReadInt();
				text2 = package.ReadString();
			}
			catch (Exception ex)
			{
				HandleMalformedHello(instance, peer, ex.Message);
				return;
			}
			if (text.Length > 64 || text2.Length > 128)
			{
				HandleMalformedHello(instance, peer, "version or config hash exceeds the protocol limit");
				return;
			}
			PeerHandshakeState orCreateState = GetOrCreateState(peer);
			if (orCreateState.HelloReceived)
			{
				return;
			}
			orCreateState.HelloReceived = true;
			orCreateState.RemoteVersion = text;
			orCreateState.RemoteSchema = num;
			orCreateState.RemoteConfigHash = text2;
			orCreateState.Compatible = VersionInfo.IsCompatible(text, num);
			if (!orCreateState.Compatible)
			{
				string text3 = $"incompatible ValheimOne handshake (client v{text}, schema {num}; " + string.Format("server v{0}, schema {1})", "0.13.29", 1);
				if (IsEnforcementEnabled)
				{
					KickPeer(instance, orCreateState, text3);
					return;
				}
				_log.Info("Peer " + PeerLabel(peer) + " is modded but " + text3 + "; allowed because Server.EnforceMod=false.");
				return;
			}
			string serializedConfig;
			string a;
			try
			{
				serializedConfig = ConfigSyncSerializer.Serialize(_settings.Features);
				a = ConfigSyncSerializer.ComputeHash(serializedConfig);
			}
			catch (Exception ex2)
			{
				_log.Error("Handshake with peer " + PeerLabel(peer) + " succeeded, but server config serialization failed: " + ex2.Message);
				return;
			}
			string arg = (string.Equals(a, text2, StringComparison.OrdinalIgnoreCase) ? "config already matches" : "server config will win");
			_log.Info("Peer " + PeerLabel(peer) + " is modded: handshake ok " + $"(ValheimOne v{text}, schema {num}; {arg}).");
			foreach (IVersionHandshakeExtension extension in _extensions)
			{
				extension.OnPeerCompatible(sender);
			}
			if (_serverConfig.Enabled && _serverConfig.SyncConfig.Value)
			{
				QueueConfigPush(orCreateState, serializedConfig);
			}
		}

		private void HandleMalformedHello(ZNet net, ZNetPeer peer, string detail)
		{
			PeerHandshakeState orCreateState = GetOrCreateState(peer);
			orCreateState.HelloReceived = true;
			orCreateState.Compatible = false;
			string text = "malformed ValheimOne handshake (" + detail + ")";
			if (IsEnforcementEnabled)
			{
				KickPeer(net, orCreateState, text);
				return;
			}
			_log.Info("Peer " + PeerLabel(peer) + " sent a " + text + "; allowed because Server.EnforceMod=false.");
		}

		private void QueueConfigPush(PeerHandshakeState state, string serializedConfig)
		{
			IReadOnlyList<string> readOnlyList;
			try
			{
				readOnlyList = ConfigSyncSerializer.CreateChunks(serializedConfig);
			}
			catch (Exception ex)
			{
				_log.Error("Unable to prepare config push for peer " + PeerLabel(state.Peer) + ": " + ex.Message);
				return;
			}
			state.ResetConfigPush();
			for (int i = 0; i < readOnlyList.Count; i++)
			{
				state.ConfigQueue.Enqueue(new OutboundConfigChunk(readOnlyList.Count, i, readOnlyList[i]));
			}
			_log.Info($"Config push to peer {PeerLabel(state.Peer)} queued: {readOnlyList.Count} chunk(s).");
		}

		private void HandleConfig(long sender, ZPackage package)
		{
			ZNet instance = ZNet.instance;
			ZRoutedRpc instance2 = ZRoutedRpc.instance;
			if (_active != this || (Object)(object)instance == (Object)null || instance2 == null || instance.IsServer())
			{
				return;
			}
			long num = instance.GetServerPeer()?.m_uid ?? 0;
			if (sender != num)
			{
				_log.Warning(string.Format("Ignored spoofed {0} from peer uid {1}.", "VO_Config", sender));
				return;
			}
			int num2;
			int num3;
			string text;
			try
			{
				num2 = package.ReadInt();
				num3 = package.ReadInt();
				text = package.ReadString();
			}
			catch (Exception ex)
			{
				_log.Warning("Ignored malformed VO_Config from the server: " + ex.Message);
				return;
			}
			if (num2 <= 0 || num2 > 1024 || num3 < 0 || num3 >= num2)
			{
				_log.Warning(string.Format("Ignored invalid {0} chunk header ({1}/{2}).", "VO_Config", num3 + 1, num2));
				return;
			}
			if (Encoding.UTF8.GetByteCount(text) > 4000)
			{
				_log.Warning(string.Format("Ignored oversized {0} chunk {1}/{2}.", "VO_Config", num3 + 1, num2));
				return;
			}
			ClientConfigBuffer clientConfigBuffer = _clientConfigBuffer;
			if (clientConfigBuffer == null)
			{
				if (num3 != 0)
				{
					_log.Warning(string.Format("Ignored out-of-sequence {0} chunk {1}/{2}.", "VO_Config", num3 + 1, num2));
					return;
				}
				clientConfigBuffer = (_clientConfigBuffer = new ClientConfigBuffer(num2));
			}
			if (clientConfigBuffer.TotalChunks != num2)
			{
				_log.Warning("Ignored config chunk whose total does not match the active push.");
				return;
			}
			string text2 = clientConfigBuffer.Chunks[num3];
			if (text2 != null && !string.Equals(text2, text, StringComparison.Ordinal))
			{
				_log.Warning(string.Format("Ignored conflicting duplicate {0} chunk {1}/{2}.", "VO_Config", num3 + 1, num2));
				return;
			}
			if (text2 == null)
			{
				if (num3 != clientConfigBuffer.ReceivedChunks)
				{
					_log.Warning(string.Format("Ignored out-of-sequence {0} chunk {1}/{2}.", "VO_Config", num3 + 1, num2));
					return;
				}
				clientConfigBuffer.Chunks[num3] = text;
				clientConfigBuffer.ReceivedChunks++;
			}
			SendAck(instance2, sender, num3);
			if (clientConfigBuffer.ReceivedChunks != clientConfigBuffer.TotalChunks)
			{
				return;
			}
			_clientConfigBuffer = null;
			StringBuilder stringBuilder = new StringBuilder();
			string[] chunks = clientConfigBuffer.Chunks;
			foreach (string value in chunks)
			{
				stringBuilder.Append(value);
			}
			try
			{
				int num4 = _settings.ApplyOverlay(stringBuilder.ToString());
				_log.Info($"Server config applied: {num4} value(s) from " + $"{clientConfigBuffer.TotalChunks} chunk(s); local cfg unchanged.");
			}
			catch (Exception ex2)
			{
				_log.Error("Server config overlay rejected; local settings remain active: " + ex2.Message);
			}
		}

		private void SendAck(ZRoutedRpc routedRpc, long serverPeerId, int index)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write("VO_Config");
			val.Write(index);
			try
			{
				routedRpc.InvokeRoutedRPC(serverPeerId, "VO_Ack", new object[1] { val });
			}
			catch (Exception ex)
			{
				_log.Warning($"Unable to acknowledge config chunk {index + 1}: {ex.Message}");
			}
		}

		private void HandleAck(long sender, ZPackage package)
		{
			ZNet instance = ZNet.instance;
			if (_active != this || (Object)(object)instance == (Object)null)
			{
				return;
			}
			string text;
			int num;
			try
			{
				text = package.ReadString();
				num = package.ReadInt();
			}
			catch (Exception ex)
			{
				_log.Warning(string.Format("Ignored malformed {0} from peer uid {1}: {2}", "VO_Ack", sender, ex.Message));
				return;
			}
			PeerHandshakeState value;
			if (!string.Equals(text, "VO_Config", StringComparison.Ordinal))
			{
				foreach (IVersionHandshakeExtension extension in _extensions)
				{
					if (extension.TryHandleAcknowledgement(sender, text, num))
					{
						return;
					}
				}
				_log.Warning(string.Format("Ignored unexpected {0} for {1} chunk {2} from peer uid {3}.", "VO_Ack", text, num + 1, sender));
			}
			else if (!instance.IsServer() || !_peerStates.TryGetValue(sender, out value) || !value.ConfigQueue.TryAcknowledge(num))
			{
				_log.Warning(string.Format("Ignored unexpected {0} for config chunk {1} from peer uid {2}.", "VO_Ack", num + 1, sender));
			}
			else if (value.ConfigQueue.IsIdle)
			{
				_log.Info("Config push to peer " + PeerLabel(value.Peer) + " complete: " + $"{value.ConfigQueue.AcknowledgedCount} chunk(s) sent and acknowledged.");
				value.ResetConfigPush();
			}
		}

		private void PumpServer(ZNet net)
		{
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Expected O, but got Unknown
			ReconcileServerPeers(net);
			if (!_serverConfig.Enabled)
			{
				return;
			}
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance == null || !IsRegistered(instance))
			{
				return;
			}
			foreach (PeerHandshakeState item in new List<PeerHandshakeState>(_peerStates.Values))
			{
				if (!item.KickIssued && item.Compatible && item.ConfigQueue.TryStartNext(out OutboundConfigChunk chunk) && chunk != null)
				{
					ZPackage val = new ZPackage();
					val.Write(chunk.TotalChunks);
					val.Write(chunk.Index);
					val.Write(chunk.Text);
					try
					{
						instance.InvokeRoutedRPC(item.PeerId, "VO_Config", new object[1] { val });
						_log.Debug("Config push to peer " + PeerLabel(item.Peer) + " sent chunk " + $"{chunk.Index + 1}/{chunk.TotalChunks}.");
					}
					catch (Exception ex)
					{
						item.ResetConfigPush();
						_log.Error("Config push to peer " + PeerLabel(item.Peer) + " failed: " + ex.Message);
					}
				}
			}
		}

		private void ReconcileServerPeers(ZNet net)
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			HashSet<long> hashSet = new HashSet<long>();
			foreach (ZNetPeer item in new List<ZNetPeer>(net.GetPeers()))
			{
				if (item.m_server || !item.IsReady())
				{
					continue;
				}
				hashSet.Add(item.m_uid);
				PeerHandshakeState orCreateState = GetOrCreateState(item, realtimeSinceStartup);
				if (orCreateState.KickIssued)
				{
					continue;
				}
				if (orCreateState.HelloReceived && !orCreateState.Compatible)
				{
					if (IsEnforcementEnabled)
					{
						KickPeer(net, orCreateState, "incompatible ValheimOne handshake (client v" + orCreateState.RemoteVersion + ", " + $"schema {orCreateState.RemoteSchema})");
					}
				}
				else
				{
					if (orCreateState.HelloReceived)
					{
						continue;
					}
					int num = Math.Max(0, _serverConfig.HandshakeGraceSeconds.Value);
					if (realtimeSinceStartup - orCreateState.FirstObservedAt < (float)num)
					{
						continue;
					}
					if (orCreateState.VanillaClassified)
					{
						if (IsEnforcementEnabled)
						{
							KickPeer(net, orCreateState, string.Format("no {0} received within {1}s (vanilla client)", "VO_Hello", num));
						}
					}
					else if (_serverConfig.Enabled)
					{
						orCreateState.VanillaClassified = true;
						string text = string.Format("no {0} received within {1}s (vanilla client)", "VO_Hello", num);
						if (IsEnforcementEnabled)
						{
							KickPeer(net, orCreateState, text);
						}
						else if (_serverConfig.Enabled)
						{
							_log.Info("Peer " + PeerLabel(item) + " is vanilla: " + text + "; allowed because Server.EnforceMod=false.");
						}
					}
				}
			}
			List<long> list = new List<long>();
			foreach (long key in _peerStates.Keys)
			{
				if (!hashSet.Contains(key))
				{
					list.Add(key);
				}
			}
			foreach (long item2 in list)
			{
				_peerStates.Remove(item2);
			}
		}

		private PeerHandshakeState GetOrCreateState(ZNetPeer peer)
		{
			return GetOrCreateState(peer, Time.realtimeSinceStartup);
		}

		private PeerHandshakeState GetOrCreateState(ZNetPeer peer, float observedAt)
		{
			if (_peerStates.TryGetValue(peer.m_uid, out PeerHandshakeState value) && value.Peer == peer)
			{
				return value;
			}
			value = new PeerHandshakeState(peer, observedAt);
			_peerStates[peer.m_uid] = value;
			return value;
		}

		private void KickPeer(ZNet net, PeerHandshakeState state, string reason)
		{
			if (state.KickIssued)
			{
				return;
			}
			state.KickIssued = true;
			state.ResetConfigPush();
			_log.Warning("Peer " + PeerLabel(state.Peer) + " kicked: " + reason + ".");
			try
			{
				net.Kick(state.Peer.m_playerName);
			}
			catch (Exception ex)
			{
				_log.Error("Failed to kick peer " + PeerLabel(state.Peer) + " after handshake rejection: " + ex.Message);
			}
		}

		private void ResetNetworkState()
		{
			foreach (IVersionHandshakeExtension extension in _extensions)
			{
				extension.ResetNetworkState();
			}
			if (_settings.ClearOverlay())
			{
				_log.Info("Server config overlay cleared; local settings restored.");
			}
			_peerStates.Clear();
			_clientConfigBuffer = null;
			_clientHelloFailureLogged = false;
			_clientHelloSent = false;
		}

		private void PumpExtensions(ZNet net)
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance == null || !IsRegistered(instance))
			{
				return;
			}
			List<long> list = new List<long>();
			if (net.IsServer())
			{
				foreach (PeerHandshakeState value in _peerStates.Values)
				{
					if (value.Compatible && !value.KickIssued && value.Peer.IsReady())
					{
						list.Add(value.PeerId);
					}
				}
			}
			foreach (IVersionHandshakeExtension extension in _extensions)
			{
				extension.Pump(net, instance, list);
			}
		}

		private static string PeerLabel(ZNetPeer peer)
		{
			if (!string.IsNullOrWhiteSpace(peer.m_playerName))
			{
				return $"'{peer.m_playerName}' (uid {peer.m_uid})";
			}
			return $"uid {peer.m_uid}";
		}
	}
	public static class VersionInfo
	{
		public const string PluginVersion = "0.13.29";

		public const string SupportedGameVersion = "1.0.21";

		public const int NetworkConfigSchema = 1;

		public static bool IsCompatible(string remotePluginVersion, int remoteSchema)
		{
			if (remoteSchema != 1 || !Version.TryParse("0.13.29", out Version result) || !Version.TryParse(remotePluginVersion, out Version result2))
			{
				return false;
			}
			if (result.Major == result2.Major)
			{
				return result.Minor == result2.Minor;
			}
			return false;
		}
	}
}
namespace ValheimOne.Modules
{
	public sealed class BeehiveModule : IFeatureModule
	{
		private sealed class BeehiveBaseline
		{
			public WeakReference<Beehive> Beehive { get; }

			public float SecondsPerUnit { get; }

			public int MaxHoney { get; }

			public BeehiveBaseline(Beehive beehive)
			{
				Beehive = new WeakReference<Beehive>(beehive);
				SecondsPerUnit = beehive.m_secPerUnit;
				MaxHoney = beehive.m_maxHoney;
			}

			public void Restore(Beehive beehive)
			{
				beehive.m_secPerUnit = SecondsPerUnit;
				beehive.m_maxHoney = MaxHoney;
			}
		}

		private static BeehiveModule? _active;

		private readonly FeatureDefinition _feature;

		private readonly ConfigEntryInt _productionSeconds;

		private readonly ConfigEntryInt _maxHoney;

		private readonly List<BeehiveBaseline> _baselines = new List<BeehiveBaseline>();

		public string Name => "Beehive";

		public string Section => "Beehive";

		public bool IsEnabled => _feature.Enabled.Value;

		public FeatureClassification Classification => FeatureClassification.Synced;

		public BeehiveModule(FeatureRegistry registry)
		{
			_feature = registry.Register(Name, Section, Classification);
			_productionSeconds = _feature.Int("ProductionSeconds", 0, "Absolute seconds required to produce one honey. Zero disables this override; Valheim's default is 1200.");
			_maxHoney = _feature.Int("MaxHoney", 0, "Absolute maximum stored honey. Zero disables this override; Valheim's default is 4.");
			registry.EffectiveValuesChanged += OnEffectiveValuesChanged;
		}

		public void ApplyPatches(Harmony harmony)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Expected O, but got Unknown
			_active = this;
			MethodInfo methodInfo = AccessTools.Method(typeof(Beehive), "Awake", Type.EmptyTypes, (Type[])null) ?? throw new MissingMethodException("Beehive", "Awake");
			harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(BeehiveModule), "BeehiveAwakePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private static void BeehiveAwakePostfix(Beehive __instance)
		{
			BeehiveModule active = _active;
			if (active != null && active.IsEnabled)
			{
				active.ApplyToBeehive(__instance, active.GetOrAddBaseline(__instance));
			}
		}

		private void OnEffectiveValuesChanged()
		{
			Beehive[] array = Object.FindObjectsOfType<Beehive>();
			foreach (Beehive beehive in array)
			{
				BeehiveBaseline beehiveBaseline = FindBaseline(beehive);
				if (IsEnabled)
				{
					ApplyToBeehive(beehive, beehiveBaseline ?? AddBaseline(beehive));
				}
				else
				{
					beehiveBaseline?.Restore(beehive);
				}
			}
		}

		private BeehiveBaseline GetOrAddBaseline(Beehive beehive)
		{
			return FindBaseline(beehive) ?? AddBaseline(beehive);
		}

		private BeehiveBaseline? FindBaseline(Beehive beehive)
		{
			for (int num = _baselines.Count - 1; num >= 0; num--)
			{
				BeehiveBaseline beehiveBaseline = _baselines[num];
				if (!beehiveBaseline.Beehive.TryGetTarget(out Beehive target) || (Object)(object)target == (Object)null)
				{
					_baselines.RemoveAt(num);
				}
				else if (target == beehive)
				{
					return beehiveBaseline;
				}
			}
			return null;
		}

		private BeehiveBaseline AddBaseline(Beehive beehive)
		{
			BeehiveBaseline beehiveBaseline = new BeehiveBaseline(beehive);
			_baselines.Add(beehiveBaseline);
			return beehiveBaseline;
		}

		private void ApplyToBeehive(Beehive beehive, BeehiveBaseline baseline)
		{
			int value = _productionSeconds.Value;
			beehive.m_secPerUnit = ((value > 0) ? ((float)value) : baseline.SecondsPerUnit);
			int value2 = _maxHoney.Value;
			beehive.m_maxHoney = ((value2 > 0) ? value2 : baseline.MaxHoney);
		}
	}
	public sealed class BuildingQoLModule : IFeatureModule
	{
		private sealed class PlayerBuildingBaseline
		{
			public WeakReference<Player> Player { get; }

			public float MaxPlacementDistance { get; }

			public float PlaceRotationDegrees { get; }

			public PlayerBuildingBaseline(Player player)
			{
				Player = new WeakReference<Player>(player);
				MaxPlacementDistance = player.m_maxPlaceDistance;
				PlaceRotationDegrees = PlaceRotationDegreesField.Invoke(player);
			}

			public void Restore(Player player)
			{
				player.m_maxPlaceDistance = MaxPlacementDistance;
				PlaceRotationDegreesField.Invoke(player) = PlaceRotationDegrees;
			}
		}

		private static readonly FieldRef<Player, PlacementStatus> PlacementStatusField = AccessTools.FieldRefAccess<Player, PlacementStatus>("m_placementStatus");

		private static readonly FieldRef<Player, GameObject> PlacementGhostField = AccessTools.FieldRefAccess<Player, GameObject>("m_placementGhost");

		private static readonly FieldRef<Player, int> PlaceRotationField = AccessTools.FieldRefAccess<Player, int>("m_placeRotation");

		private static readonly FieldRef<Player, float> PlaceRotationDegreesField = AccessTools.FieldRefAccess<Player, float>("m_placeRotationDegrees");

		private static BuildingQoLModule? _active;

		private readonly FeatureDefinition _feature;

		private readonly ConfigEntryBool _noStructuralSupport;

		private readonly ConfigEntryBool _noPlacementBlocking;

		private readonly ConfigEntryFloat _maxPlacementDistance;

		private readonly ConfigEntryFloat _freeRotationStepDegrees;

		private readonly List<PlayerBuildingBaseline> _playerBaselines = new List<PlayerBuildingBaseline>();

		public string Name => "Building quality of life";

		public string Section => "Building";

		public bool IsEnabled => _feature.Enabled.Value;

		public FeatureClassification Classification => FeatureClassification.Synced;

		public BuildingQoLModule(FeatureRegistry registry)
		{
			_feature = registry.Register(Name, Section, Classification);
			_noStructuralSupport = _feature.Bool("NoStructuralSupport", defaultValue: false, "Treat every structural-support check as supported, allowing pieces to remain in free air.");
			_noPlacementBlocking = _feature.Bool("NoPlacementBlocking", defaultValue: false, "Allow overlapping and otherwise invalid placement. No-build zones (mystical forces), wards, teleport-only areas, and other location or progression restrictions remain enforced.");
			_maxPlacementDistance = _feature.Float("MaxPlacementDistance", 0f, "Absolute hammer build reach. Zero disables this override; Valheim's default is 8.");
			_freeRotationStepDegrees = _feature.Float("FreeRotationStepDegrees", 0f, "Absolute placement rotation step in degrees. Zero disables this override; Valheim's default is 22.5.");
			registry.EffectiveValuesChanged += OnEffectiveValuesChanged;
		}

		public void ApplyPatches(Harmony harmony)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: 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
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Expected O, but got Unknown
			_active = this;
			MethodInfo methodInfo = AccessTools.Method(typeof(WearNTear), "HaveSupport", Type.EmptyTypes, (Type[])null) ?? throw new MissingMethodException("WearNTear", "HaveSupport");
			harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(BuildingQoLModule), "HaveSupportPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(Player), "Awake", Type.EmptyTypes, (Type[])null) ?? throw new MissingMethodException("Player", "Awake");
			harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(BuildingQoLModule), "PlayerAwakePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			MethodInfo methodInfo3 = AccessTools.Method(typeof(Player), "UpdatePlacementGhost", new Type[1] { typeof(bool) }, (Type[])null) ?? throw new MissingMethodException("Player", "UpdatePlacementGhost");
			harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(BuildingQoLModule), "UpdatePlacementGhostPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private static void HaveSupportPostfix(ref bool __result)
		{
			BuildingQoLModule active = _active;
			if (active != null && active.IsEnabled && active._noStructuralSupport.Value)
			{
				__result = true;
			}
		}

		private static void PlayerAwakePostfix(Player __instance)
		{
			BuildingQoLModule active = _active;
			if (active != null)
			{
				PlayerBuildingBaseline orAddBaseline = active.GetOrAddBaseline(__instance);
				if (active.IsEnabled)
				{
					active.ApplyToPlayer(__instance, orAddBaseline);
				}
			}
		}

		private static void UpdatePlacementGhostPostfix(Player __instance)
		{
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			BuildingQoLModule active = _active;
			if (active == null || !active.IsEnabled)
			{
				return;
			}
			GameObject val = PlacementGhostField.Invoke(__instance);
			if (active._noPlacementBlocking.Value && IsSuppressiblePlacementStatus(PlacementStatusField.Invoke(__instance)))
			{
				PlacementStatusField.Invoke(__instance) = (PlacementStatus)0;
				Piece obj = (((Object)(object)val == (Object)null) ? null : val.GetComponent<Piece>());
				if (obj != null)
				{
					obj.SetInvalidPlacementHeightlight(false);
				}
			}
			float value = active._freeRotationStepDegrees.Value;
			if (!(value <= 0f) && !((Object)(object)val == (Object)null))
			{
				PlaceRotationDegreesField.Invoke(__instance) = value;
				val.transform.rotation = Quaternion.Euler(0f, value * (float)PlaceRotationField.Invoke(__instance), 0f);
			}
		}

		private static bool IsSuppressiblePlacementStatus(PlacementStatus status)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0004: Invalid comparison between Unknown and I4
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Invalid comparison between Unknown and I4
			if (status - 1 <= 1 || (int)status == 5)
			{
				return true;
			}
			return false;
		}

		private void OnEffectiveValuesChanged()
		{
			for (int num = _playerBaselines.Count - 1; num >= 0; num--)
			{
				PlayerBuildingBaseline playerBuildingBaseline = _playerBaselines[num];
				if (!playerBuildingBaseline.Player.TryGetTarget(out Player target) || (Object)(object)target == (Object)null)
				{
					_playerBaselines.RemoveAt(num);
				}
				else if (IsEnabled)
				{
					ApplyToPlayer(target, playerBuildingBaseline);
				}
				else
				{
					playerBuildingBaseline.Restore(target);
				}
			}
		}

		private PlayerBuildingBaseline GetOrAddBaseline(Player player)
		{
			for (int num = _playerBaselines.Count - 1; num >= 0; num--)
			{
				PlayerBuildingBaseline playerBuildingBaseline = _playerBaselines[num];
				if (!playerBuildingBaseline.Player.TryGetTarget(out Player target) || (Object)(object)target == (Object)null)
				{
					_playerBaselines.RemoveAt(num);
				}
				else if (target == player)
				{
					return playerBuildingBaseline;
				}
			}
			PlayerBuildingBaseline playerBuildingBaseline2 = new PlayerBuildingBaseline(player);
			_playerBaselines.Add(playerBuildingBaseline2);
			return playerBuildingBaseline2;
		}

		private void ApplyToPlayer(Player player, PlayerBuildingBaseline baseline)
		{
			float value = _maxPlacementDistance.Value;
			player.m_maxPlaceDistance = ((value > 0f) ? value : baseline.MaxPlacementDistance);
			float value2 = _freeRotationStepDegrees.Value;
			PlaceRotationDegreesField.Invoke(player) = ((value2 > 0f) ? value2 : baseline.PlaceRotationDegrees);
		}
	}
	internal static class ChestOwnership
	{
		internal enum Result
		{
			Ready,
			Pending,
			Unavailable
		}

		private sealed class State
		{
			public bool Registered;

			public int Request;

			public long Owner;

			public float Deadline;

			public bool Granted;

			public bool Denied;

			public ushort OwnerRevision;

			public uint DataRevision;
		}

		private const string RequestRpc = "VO_CraftChestRequest";

		private const string ReplyRpc = "VO_CraftChestReply";

		private static readonly ConditionalWeakTable<Container, State> States = new ConditionalWeakTable<Container, State>();

		private static readonly Dictionary<string, Func<bool>> EnabledFeatures = new Dictionary<string, Func<bool>>();

		private static int _nextRequest;

		public static void Install(Harmony harmony, string feature, Func<bool> enabled)
		{
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Expected O, but got Unknown
			EnabledFeatures[feature] = enabled;
			MethodInfo methodInfo = AccessTools.Method(typeof(Container), "Awake", Type.EmptyTypes, (Type[])null);
			MethodInfo register = AccessTools.Method(typeof(ChestOwnership), "Register", (Type[])null, (Type[])null);
			Patches patchInfo = Harmony.GetPatchInfo((MethodBase)methodInfo);
			if (patchInfo == null || !patchInfo.Postfixes.Any((Patch p) => p.owner == harmony.Id && p.PatchMethod == register))
			{
				harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(ChestOwnership), "Register", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}

		private static void Register(Container __instance)
		{
			ZNetView networkView = ChestScanner.GetNetworkView(__instance);
			if ((Object)(object)networkView == (Object)null || !networkView.IsValid())
			{
				return;
			}
			State value = States.GetValue(__instance, (Container _) => new State());
			if (!value.Registered)
			{
				value.Registered = true;
				networkView.Register<ZPackage>("VO_CraftChestRequest", (Action<long, ZPackage>)delegate(long sender, ZPackage package)
				{
					HandleRequest(__instance, sender, package);
				});
				networkView.Register<ZPackage>("VO_CraftChestReply", (Action<long, ZPackage>)delegate(long sender, ZPackage package)
				{
					HandleReply(__instance, sender, package);
				});
			}
		}

		public static Result Prepare(Container container, Player player)
		{
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Expected O, but got Unknown
			Register(container);
			ZNetView networkView = ChestScanner.GetNetworkView(container);
			if ((Object)(object)networkView == (Object)null || !networkView.IsValid() || ChestScanner.IsBusy(container, networkView) || !ChestScanner.HasContainerAccess(container, player.GetPlayerID()))
			{
				return Result.Unavailable;
			}
			State value = States.GetValue(container, (Container _) => new State());
			ZDO zDO = networkView.GetZDO();
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (value.Request != 0)
			{
				if (value.Denied || realtimeSinceStartup >= value.Deadline)
				{
					value.Request = 0;
					return Result.Unavailable;
				}
				if (!value.Granted || !networkView.IsOwner() || zDO.OwnerRevision != value.OwnerRevision || zDO.DataRevision < value.DataRevision)
				{
					return Result.Pending;
				}
				value.Request = 0;
				ChestScanner.RefreshInventory(container);
				return Result.Ready;
			}
			if (networkView.IsOwner())
			{
				ChestScanner.RefreshInventory(container);
				return Result.Ready;
			}
			long owner = zDO.GetOwner();
			if (owner == 0L)
			{
				return Result.Pending;
			}
			value.Request = (_nextRequest = ((_nextRequest == int.MaxValue) ? 1 : (_nextRequest + 1)));
			value.Owner = owner;
			value.Deadline = realtimeSinceStartup + 5f;
			value.Granted = false;
			value.Denied = false;
			ZPackage val = new ZPackage();
			val.Write(value.Request);
			val.Write(player.GetPlayerID());
			networkView.InvokeRPC(owner, "VO_CraftChestRequest", new object[1] { val });
			return Result.Pending;
		}

		private static void HandleRequest(Container container, long sender, ZPackage package)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Expected O, but got Unknown
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			int num;
			long playerId;
			try
			{
				num = package.ReadInt();
				playerId = package.ReadLong();
			}
			catch (Exception)
			{
				return;
			}
			if (num <= 0 || sender == 0L)
			{
				return;
			}
			ZNetView networkView = ChestScanner.GetNetworkView(container);
			if (!((Object)(object)networkView == (Object)null) && networkView.IsValid() && networkView.IsOwner())
			{
				ZPackage val = new ZPackage();
				val.Write(num);
				bool flag = EnabledFeatures.Values.Any((Func<bool> enabled) => enabled()) && !ChestScanner.IsBusy(container, networkView) && ChestScanner.HasContainerAccess(container, playerId);
				val.Write(flag);
				if (flag)
				{
					ZDO zDO = networkView.GetZDO();
					zDO.SetOwner(sender);
					val.Write((int)zDO.OwnerRevision);
					val.Write((long)zDO.DataRevision);
					ZDOMan.instance.ForceSendZDO(sender, zDO.m_uid);
				}
				networkView.InvokeRPC(sender, "VO_CraftChestReply", new object[1] { val });
			}
		}

		private static void HandleReply(Container container, long sender, ZPackage package)
		{
			if (!States.TryGetValue(container, out State value) || value.Request == 0 || sender != value.Owner)
			{
				return;
			}
			try
			{
				if (package.ReadInt() != value.Request)
				{
					return;
				}
				if (!package.ReadBool())
				{
					value.Denied = true;
					return;
				}
				int num = package.ReadInt();
				long num2 = package.ReadLong();
				if (num >= 0 && num <= 65535 && num2 >= 0 && num2 <= uint.MaxValue)
				{
					value.OwnerRevision = (ushort)num;
					value.DataRevision = (uint)num2;
					value.Granted = true;
				}
			}
			catch (Exception)
			{
			}
		}
	}
	internal sealed class ChestScanner
	{
		private static readonly Func<Container, long, bool> CheckContainerAccess = AccessTools.MethodDelegate<Func<Container, long, bool>>(AccessTools.Method(typeof(Container), "CheckAccess", new Type[1] { typeof(long) }, (Type[])null) ?? throw new MissingMethodException("Container", "CheckAccess"), (object)null, true);

		private readonly List<Container> _cachedContainers = new List<Container>();

		private readonly List<Inventory> _accessibleInventories = new List<Inventory>();

		private readonly HashSet<Inventory> _seenInventories = new HashSet<Inventory>();

		private readonly bool _includeRemote;

		private Player? _cachedPlayer;

		private Vector3 _cachedCenter;

		private float _cachedRange;

		private bool _cachedIgnoreWardedChests;

		private float _refreshAt;

		private static readonly Func<Container, bool> LoadInventory = AccessTools.MethodDelegate<Func<Container, bool>>(AccessTools.Method(typeof(Container), "Load", Type.EmptyTypes, (Type[])null), (object)null, true);

		public ChestScanner(bool includeRemote = false)
		{
			_includeRemote = includeRemote;
		}

		public Container? FindContainer(Inventory inventory)
		{
			foreach (Container cachedContainer in _cachedContainers)
			{
				if ((Object)(object)cachedContainer != (Object)null && cachedContainer.GetInventory() == inventory)
				{
					return cachedContainer;
				}
			}
			return null;
		}

		public bool TryPrepareInventory(Inventory inventory, Player player)
		{
			Container val = FindContainer(inventory);
			if ((Object)(object)val != (Object)null)
			{
				return ChestOwnership.Prepare(val, player) == ChestOwnership.Result.Ready;
			}
			return false;
		}

		public IReadOnlyList<Inventory> GetInventories(Player player, float range, bool ignoreWardedChests, float cacheSeconds)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			return GetInventories(player, ((Component)player).transform.position, range, ignoreWardedChests, cacheSeconds);
		}

		public IReadOnlyList<Inventory> GetInventories(Player player, Vector3 center, float range, bool ignoreWardedChests, float cacheSeconds)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			float num = Math.Max(1f, Math.Min(50f, range));
			float num2 = Math.Max(1f, cacheSeconds);
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (_cachedPlayer != player || _cachedCenter != center || _cachedRange != num || _cachedIgnoreWardedChests != ignoreWardedChests || realtimeSinceStartup >= _refreshAt)
			{
				Refresh(player, center, num, ignoreWardedChests, realtimeSinceStartup + num2);
			}
			RebuildAccessibleInventories(player, ignoreWardedChests);
			return _accessibleInventories;
		}

		private void Refresh(Player player, Vector3 center, float range, bool ignoreWardedChests, float refreshAt)
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			_cachedContainers.Clear();
			float num = range * range;
			Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0);
			foreach (Container val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					Vector3 val2 = ((Component)val).transform.position - center;
					if (((Vector3)(ref val2)).sqrMagnitude <= num)
					{
						_cachedContainers.Add(val);
					}
				}
			}
			_cachedPlayer = player;
			_cachedCenter = center;
			_cachedRange = range;
			_cachedIgnoreWardedChests = ignoreWardedChests;
			_refreshAt = refreshAt;
		}

		private void RebuildAccessibleInventories(Player player, bool ignoreWardedChests)
		{
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			_accessibleInventories.Clear();
			_seenInventories.Clear();
			long playerID = player.GetPlayerID();
			foreach (Container cachedContainer in _cachedContainers)
			{
				ZNetView val = (((Object)(object)cachedContainer == (Object)null) ? null : GetNetworkView(cachedContainer));
				if (!((Object)(object)cachedContainer == (Object)null) && !((Object)(object)val == (Object)null) && val.IsValid() && (_includeRemote || cachedContainer.IsOwner()) && (!_includeRemote || !IsBusy(cachedContainer, val)) && (!cachedContainer.m_checkGuardStone || ignoreWardedChests || PrivateArea.CheckAccess(((Component)cachedContainer).transform.position, 0f, false, false)) && HasContainerAccess(cachedContainer, playerID))
				{
					if (_includeRemote)
					{
						RefreshInventory(cachedContainer);
					}
					Inventory inventory = cachedContainer.GetInventory();
					if (inventory != null && _seenInventories.Add(inventory))
					{
						_accessibleInventories.Add(inventory);
					}
				}
			}
		}

		internal static void RefreshInventory(Container container)
		{
			LoadInventory(container);
		}

		internal static bool HasContainerAccess(Container container, long playerId)
		{
			return CheckContainerAccess(container, playerId);
		}

		internal static bool IsBusy(Container container, ZNetView networkView)
		{
			if (!container.IsInUse() && networkView.GetZDO().GetInt(ZDOVars.s_inUse, 0) == 0)
			{
				if ((Object)(object)container.m_wagon != (Object)null)
				{
					return container.m_wagon.InUse();
				}
				return false;
			}
			return true;
		}

		internal static ZNetView? GetNetworkView(Container container)
		{
			if (!((Object)(object)container.m_rootObjectOverride != (Object)null))
			{
				return ((Component)container).GetComponent<ZNetView>();
			}
			return container.m_rootObjectOverride;
		}
	}
	public sealed class ContainerSizesModule : IFeatureModule
	{
		private sealed class ContainerSettings
		{
			public ConfigEntryInt Columns { get; }

			public ConfigEntryInt Rows { get; }

			public ContainerSettings(ConfigEntryInt columns, ConfigEntryInt rows)
			{
				Columns = columns;
				Rows = rows;
			}
		}

		private sealed class ContainerBaseline
		{
			public WeakReference<Container> Container { get; }

			public int Width { get; }

			public int Height { get; }

			public string? LastWarningSignature { get; set; }

			public ContainerBaseline(Container container)
			{
				Container = new WeakReference<Container>(container);
				Width = container.m_width;
				Height = container.m_height;
			}
		}

		private static readonly FieldInfo InventoryWidthField = AccessTools.Field(typeof(Inventory), "m_width") ?? throw new MissingFieldException("Inventory", "m_width");

		private static readonly FieldInfo InventoryHeightField = AccessTools.Field(typeof(Inventory), "m_height") ?? throw new MissingFieldException("Inventory", "m_height");

		private static ContainerSizesModule? _active;

		private readonly FeatureDefinition _feature;

		private readonly ModLogger _log;

		private readonly ContainerSettings _woodChest;

		private readonly ContainerSettings _personalChest;

		private readonly ContainerSettings _reinforcedChest;

		private readonly ContainerSettings _blackmetalChest;

		private readonly ContainerSettings _graustenChest;

		private readonly ContainerSettings _wardrobe;

		private readonly ContainerSettings _cart;

		private readonly ContainerSettings _karve;

		private readonly ContainerSettings _longship;

		private readonly List<ContainerBaseline> _baselines = new List<ContainerBaseline>();

		private readonly HashSet<string> _loggedUnmatchedPrefabs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		public string Name => "Container sizes";

		public string Section => "ContainerSizes";

		public bool IsEnabled => _feature.Enabled.Value;

		public FeatureClassification Classification => FeatureClassification.Synced;

		public ContainerSizesModule(FeatureRegistry registry, ModLogger log)
		{
			_feature = registry.Register(Name, Section, Classification);
			_log = log;
			_woodChest = AddSettings("WoodChest");
			_personalChest = AddSettings("PersonalChest");
			_reinforcedChest = AddSettings("ReinforcedChest");
			_blackmetalChest = AddSettings("BlackmetalChest");
			_graustenChest = AddSettings("GraustenChest");
			_wardrobe = AddSettings("Wardrobe");
			_cart = AddSettings("Cart");
			_karve = AddSettings("Karve");
			_longship = AddSettings("Longship");
			registry.EffectiveValuesChanged += OnEffectiveValuesChanged;
		}

		public void ApplyPatches(Harmony harmony)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Expected O, but got Unknown
			//IL_0069: Expected O, but got Unknown
			_active = this;
			MethodInfo methodInfo = AccessTools.Method(typeof(Container), "Awake", Type.EmptyTypes, (Type[])null) ?? throw new MissingMethodException("Container", "Awake");
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ContainerSizesModule), "ContainerAwakePrefix", (Type[])null), new HarmonyMethod(typeof(ContainerSizesModule), "ContainerAwakePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private static void ContainerAwakePrefix(Container __instance)
		{
			ContainerSizesModule active = _active;
			if (active != null && active.IsEnabled)
			{
				active.ApplyBeforeAwake(__instance);
			}
		}

		private static void ContainerAwakePostfix(Container __instance)
		{
			ContainerSizesModule active = _active;
			if (active != null && active.IsEnabled)
			{
				ContainerBaseline containerBaseline = active.FindBaseline(__instance);
				if (containerBaseline != null && active.TryGetSettings(__instance, out ContainerSettings settings) && settings != null)
				{
					active.ApplyAfterLoad(__instance, containerBaseline, settings);
				}
			}
		}

		private ContainerSettings AddSettings(string keyPrefix)
		{
			return new ContainerSettings(_feature.Int(keyPrefix + "Columns", 0, "Absolute container columns. Zero leaves the vanilla value; nonzero values use their absolute value and clamp to 1-8."), _feature.Int(keyPrefix + "Rows", 0, "Absolute container rows. Zero leaves the vanilla value; nonzero values use their absolute value and clamp to 1-24."));
		}

		private void ApplyBeforeAwake(Container container)
		{
			if (TryGetSettings(container, out ContainerSettings settings) && settings != null)
			{
				ContainerBaseline orAddBaseline = GetOrAddBaseline(container);
				int val = ResolveDimension(settings.Columns.Value, orAddBaseline.Width, 8);
				int val2 = ResolveDimension(settings.Rows.Value, orAddBaseline.Height, 24);
				container.m_width = Math.Max(val, orAddBaseline.Width);
				container.m_height = Math.Max(val2, orAddBaseline.Height);
			}
		}

		private void ApplyAfterLoad(Container container, ContainerBaseline baseline, ContainerSettings settings)
		{
			int requestedWidth = ResolveDimension(settings.Columns.Value, baseline.Width, 8);
			int requestedHeight = ResolveDimension(settings.Rows.Value, baseline.Height, 24);
			ApplyLoadedDimensions(container, baseline, requestedWidth, requestedHeight);
		}

		private void ApplyLoadedDimensions(Container container, ContainerBaseline baseline, int requestedWidth, int requestedHeight)
		{
			Inventory inventory = container.GetInventory();
			if (inventory == null)
			{
				return;
			}
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				num++;
				num2 = Math.Max(num2, allItem.m_gridPos.x + 1);
				num3 = Math.Max(num3, allItem.m_gridPos.y + 1);
			}
			int val = requestedWidth;
			int val2 = requestedHeight;
			if (num != 0)
			{
				val = Math.Max(val, baseline.Width);
				val2 = Math.Max(val2, baseline.Height);
			}
			val = Math.Max(val, num2);
			val2 = Math.Max(val2, num3);
			container.m_width = val;
			container.m_height = val2;
			InventoryWidthField.SetValue(inventory, val);
			InventoryHeightField.SetValue(inventory, val2);
			if (val != requestedWidth || val2 != requestedHeight)
			{
				string prefabName = Utils.GetPrefabName(((Object)((Component)container).gameObject).name);
				string text = requestedWidth + "x" + requestedHeight + ":" + val + "x" + val2;
				if (!string.Equals(baseline.LastWarningSignature, text, StringComparison.Ordinal))
				{
					_log.Warning("ContainerSizes kept '" + prefabName + "' (" + container.m_name + ") at " + $"{val}x{val2} instead of requested " + $"{requestedWidth}x{requestedHeight} because it contains {num} item(s); " + "the grid was not shrunk so no occupied slot can be lost.");
					baseline.LastWarningSignature = text;
				}
			}
			else
			{
				baseline.LastWarningSignature = null;
			}
		}

		private void OnEffectiveValuesChanged()
		{
			Container[] array = Object.FindObjectsOfType<Container>();
			foreach (Container container in array)
			{
				ContainerBaseline containerBaseline = FindBaseline(container);
				if (IsEnabled)
				{
					if (TryGetSettings(container, out ContainerSettings settings) && settings != null)
					{
						if (containerBaseline == null)
						{
							containerBaseline = AddBaseline(container);
						}
						ApplyAfterLoad(container, containerBaseline, settings);
					}
				}
				else if (containerBaseline != null)
				{
					ApplyLoadedDimensions(container, containerBaseline, containerBaseline.Width, containerBaseline.Height);
				}
			}
		}

		private bool TryGetSettings(Container container, out ContainerSettings? settings)
		{
			string prefabName = Utils.GetPrefabName(((Object)((Component)container).gameObject).name);
			switch (prefabName.ToLowerInvariant())
			{
			case "piece_chest_wood":
				settings = _woodChest;
				return true;
			case "piece_chest_private":
				settings = _personalChest;
				return true;
			case "piece_chest":
				settings = _reinforcedChest;
				return true;
			case "piece_chest_blackmetal":
				settings = _blackmetalChest;
				return true;
			case "piece_chest_grausten":
				settings = _graustenChest;
				return true;
			case "piece_chest_warderobe":
				settings = _wardrobe;
				return true;
			case "cart":
				settings = _cart;
				return true;
			case "karve":
				settings = _karve;
				return true;
			case "vikingship":
				settings = _longship;
				return true;
			default:
				settings = null;
				LogUnmatchedPrefab(prefabName);
				return false;
			}
		}

		private void LogUnmatchedPrefab(string prefabName)
		{
			string text = (string.IsNullOrEmpty(prefabName) ? "<empty>" : prefabName);
			if (_loggedUnmatchedPrefabs.Add(text))
			{
				_log.Debug("ContainerSizes saw unmatched Container prefab '" + text + "'; its grid dimensions were left unchanged.");
			}
		}

		private ContainerBaseline GetOrAddBaseline(Container container)
		{
			return FindBaseline(container) ?? AddBaseline(container);
		}

		private ContainerBaseline? FindBaseline(Container container)
		{
			for (int num = _baselines.Count - 1; num >= 0; num--)
			{
				ContainerBaseline containerBaseline = _baselines[num];
				if (!containerBaseline.Container.TryGetTarget(out Container target) || (Object)(object)target == (Object)null)
				{
					_baselines.RemoveAt(num);
				}
				else if (target == container)
				{
					return containerBaseline;
				}
			}
			return null;
		}

		private ContainerBaseline AddBaseline(Container container)
		{
			ContainerBaseline containerBaseline = new ContainerBaseline(container);
			_baselines.Add(containerBaseline);
			return containerBaseline;
		}

		private static int ResolveDimension(int configured, int vanilla, int maximum)
		{
			if (configured == 0)
			{
				return vanilla;
			}
			long num = ((configured < 0) ? (-configured) : configured);
			if (num < 1)
			{
				return 1;
			}
			if (num <= maximum)
			{
				return (int)num;
			}
			return maximum;
		}
	}
	public sealed class CookingStationModule : IFeatureModule
	{
		private readonly struct UpdateCookingState
		{
			public bool Changed { get; }

			public bool RequireFire { get; }

			public UpdateCookingState(bool requireFire)
			{
				Changed = true;
				RequireFire = requireFire;
			}
		}

		private sealed class CookingStationBaseline
		{
			public WeakReference<CookingStation> Station { get; }

			public bool RequireFire { get; }

			public List<ConversionBaseline> Conversions { get; }

			public CookingStationBaseline(CookingStation station)
			{
				Station = new WeakReference<CookingStation>(station);
				RequireFire = station.m_requireFire;
				Conversions = new List<ConversionBaseline>();
				foreach (ItemConversion item in station.m_conversion)
				{
					if (item != null)
					{
						Conversions.Add(new ConversionBaseline(item));
					}
				}
			}

			public void Restore(CookingStation station)
			{
				station.m_requireFire = RequireFire;
				foreach (ConversionBaseline conversion in Conversions)
				{
					conversion.Conversion.m_cookTime = conversion.CookTime;
				}
			}
		}

		private sealed class ConversionBaseline
		{
			public ItemConversion Conversion { get; }

			public float CookTime { get; }

			public ConversionBaseline(ItemConversion conversion)
			{
				Conversion = conversion;
				CookTime = conversion.m_cookTime;
			}
		}

		private sealed class StationState
		{
			public ChestScanner Scanner { get; } = new ChestScanner(includeRemote: true);

			public float NextCheckAt { get; set; }
		}

		private const string AddFuelRpc = "RPC_AddFuel";

		private const string AddItemRpc = "RPC_AddItem";

		private static readonly Func<CookingStation, float> GetFuel = AccessTools.MethodDelegate<Func<CookingStation, float>>(AccessTools.Method(typeof(CookingStation), "GetFuel", Type.EmptyTypes, (Type[])null) ?? throw new MissingMethodException("CookingStation", "GetFuel"), (object)null, true);

		private static readonly Func<CookingStation, int> GetFreeSlot = AccessTools.MethodDelegate<Func<CookingStation, int>>(AccessTools.Method(typeof(CookingStation), "GetFreeSlot", Type.EmptyTypes, (Type[])null) ?? throw new MissingMethodException("CookingStation", "GetFreeSlot"), (object)null, true);

		private static readonly Func<CookingStation, bool> IsFireLit = AccessTools.MethodDelegate<Func<CookingStation, bool>>(AccessTools.Method(typeof(CookingStation), "IsFireLit", Type.EmptyTypes, (Type[])null) ?? throw new MissingMethodException("CookingStation", "IsFireLit"), (object)null, true);

		private static readonly Func<CookingStation, Inventory, ItemData?> FindCookableItem = AccessTools.MethodDelegate<Func<CookingStation, Inventory, ItemData>>(AccessTools.Method(typeof(CookingStation), "FindCookableItem", new Type[1] { typeof(Inventory) }, (Type[])null) ?? throw new MissingMethodException("CookingStation", "FindCookableItem"), (object)null, true);

		private static CookingStationModule? _active;

		private readonly FeatureDefinition _feature;

		private readonly ConfigEntryPercent _cookSpeedMultiplier;

		private readonly ConfigEntryBool _ignoreFireRequirement;

		private readonly ConfigEntryBool _autoFuel;

		private readonly ConfigEntryBool _autoFeedRaw;

		private readonly ConfigEntryFloat _range;

		private readonly ConfigEntryFloat _checkIntervalSeconds;

		private readonly List<CookingStationBaseline> _baselines = new List<CookingStationBaseline>();

		private readonly ConditionalWeakTable<CookingStation, StationState> _stationStates = new ConditionalWeakTable<CookingStation, StationState>();

		public string Name => "Cooking station";

		public string Section => "CookingStation";

		public bool IsEnabled => _feature.Enabled.Value;

		public FeatureClassification Classification => FeatureClassification.Synced;

		public CookingStationModule(FeatureRegistry registry)
		{
			_feature = registry.Register(Name, Section, Classification);
			_cookSpeedMultiplier = _feature.Percent("CookSpeedMultiplier", 0f, "Scale each cooking conversion's per-item cook time.");
			_ignoreFireRequirement = _feature.Bool("IgnoreFireRequirement", defaultValue: false, "Allow cooking stations to operate without their normal fire requirement.");
			_autoFuel = _feature.Bool("AutoFuel", defaultValue: false, "Pull fuel from nearby containers for fuel-using cooking stations such as stone ovens.");
			_autoFeedRaw = _feature.Bool("AutoFeedRaw", defaultValue: false, "Pull accepted raw items from nearby containers into free cooking slots.");
			_range = _feature.Float("Range", 10f, "Maximum distance in metres from the station to a source container. Clamped to 1-50.");
			_checkIntervalSeconds = _feature.Float("CheckIntervalSeconds", 2f, "Seconds between automation checks for each station. Values below 1 are clamped to 1.");
			registry.EffectiveValuesChanged += OnEffectiveValuesChanged;
		}

		public void ApplyPatches(Harmony harmony)
		{
			//IL_007f: 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_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Expected O, but got Unknown
			//IL_00b5: Expected O, but got Unknown
			//IL_00b5: Expected O, but got Unknown
			_active = this;
			ChestOwnership.Install(harmony, Section, () => IsEnabled);
			PatchPostfix(harmony, "Awake", "CookingStationAwakePostfix");
			PatchPostfix(harmony, "IsFireLit", "IsFireLitPostfix");
			MethodInfo methodInfo = AccessTools.Method(typeof(CookingStation), "UpdateCooking", Type.EmptyTypes, (Type[])null) ?? throw new MissingMethodException("CookingStation", "UpdateCooking");
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(CookingStationModule), "UpdateCookingPrefix", (Type[])null), new HarmonyMethod(typeof(CookingStationModule), "UpdateCookingPostfix", (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(CookingStationModule), "UpdateCookingFinalizer", (Type[])null), (HarmonyMethod)null);
		}

		private static void PatchPostfix(Harmony harmony, string methodName, string postfixName)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(typeof(CookingStation), methodName, Type.EmptyTypes, (Type[])null) ?? throw new MissingMethodException("CookingStation", methodNa