Decompiled source of ChestAPI v0.2.0

plugins/ChestAPI/ChestAPI.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ChestAPI
{
	public sealed class Area
	{
		public string Name;

		public string Kind;

		public double MinX;

		public double MinZ;

		public double MaxX;

		public double MaxZ;

		public double X;

		public double Z;

		public double Radius;

		public bool Contains(double x, double z)
		{
			if (Kind == "circle")
			{
				double num = x - X;
				double num2 = z - Z;
				return num * num + num2 * num2 <= Radius * Radius;
			}
			if (x >= MinX && x <= MaxX && z >= MinZ)
			{
				return z <= MaxZ;
			}
			return false;
		}
	}
	public static class AreaParser
	{
		public static Dictionary<string, Area> Parse(string text, out List<string> problems)
		{
			problems = new List<string>();
			Dictionary<string, Area> dictionary = new Dictionary<string, Area>(StringComparer.OrdinalIgnoreCase);
			if (string.IsNullOrWhiteSpace(text))
			{
				return dictionary;
			}
			string[] array = text.Split(new char[3] { ';', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length == 0)
				{
					continue;
				}
				string[] array2 = text2.Split(new char[1] { ':' }, 3);
				if (array2.Length != 3 || array2[0].Trim().Length == 0)
				{
					problems.Add("'" + text2 + "': expected name:rect:minX,minZ,maxX,maxZ or name:circle:x,z,radius");
					continue;
				}
				string text3 = array2[0].Trim();
				string text4 = array2[1].Trim().ToLowerInvariant();
				List<double> list = new List<double>();
				bool flag = true;
				string[] array3 = array2[2].Split(new char[1] { ',' });
				for (int j = 0; j < array3.Length; j++)
				{
					if (double.TryParse(array3[j].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
					{
						list.Add(result);
					}
					else
					{
						flag = false;
					}
				}
				if (!flag)
				{
					problems.Add("'" + text2 + "': coordinates must be numbers");
				}
				else if (text4 == "rect" && list.Count == 4)
				{
					dictionary[text3] = new Area
					{
						Name = text3,
						Kind = "rect",
						MinX = Math.Min(list[0], list[2]),
						MaxX = Math.Max(list[0], list[2]),
						MinZ = Math.Min(list[1], list[3]),
						MaxZ = Math.Max(list[1], list[3])
					};
				}
				else if (text4 == "circle" && list.Count == 3 && list[2] > 0.0)
				{
					dictionary[text3] = new Area
					{
						Name = text3,
						Kind = "circle",
						X = list[0],
						Z = list[1],
						Radius = list[2]
					};
				}
				else
				{
					problems.Add("'" + text2 + "': expected rect with 4 numbers or circle with 3 numbers and a positive radius");
				}
			}
			return dictionary;
		}
	}
	public sealed class ChestFilter
	{
		private static readonly string[] CommonParams = new string[17]
		{
			"owner", "prefab", "excludePrefab", "biome", "area", "minX", "maxX", "minZ", "maxZ", "minY",
			"maxY", "x", "z", "radius", "loaded", "item", "includeItems"
		};

		public bool PlayerOnly;

		public HashSet<long> OwnerIds;

		public HashSet<string> Prefabs;

		public HashSet<string> ExcludePrefabs;

		public HashSet<string> Biomes;

		public HashSet<string> Items;

		public List<Area> NamedAreas;

		public Area Circle;

		public double? MinX;

		public double? MaxX;

		public double? MinZ;

		public double? MaxZ;

		public double? MinY;

		public double? MaxY;

		public bool? Loaded;

		public bool IncludeItems = true;

		public static ChestFilter Parse(NameValueCollection query, bool playerOnly, IDictionary<string, Area> areas, PlayerNames names, string[] extraParams, out string error)
		{
			error = null;
			HashSet<string> hashSet = new HashSet<string>(CommonParams.Concat(extraParams ?? new string[0]), StringComparer.OrdinalIgnoreCase);
			string[] allKeys = query.AllKeys;
			foreach (string text in allKeys)
			{
				if (text != null && !hashSet.Contains(text))
				{
					error = "Unknown parameter '" + text + "'.";
					return null;
				}
			}
			ChestFilter chestFilter = new ChestFilter
			{
				PlayerOnly = playerOnly
			};
			chestFilter.Prefabs = List(query, "prefab");
			chestFilter.ExcludePrefabs = List(query, "excludePrefab");
			chestFilter.Biomes = List(query, "biome");
			chestFilter.Items = List(query, "item");
			HashSet<string> hashSet2 = List(query, "owner");
			if (hashSet2 != null)
			{
				chestFilter.OwnerIds = new HashSet<long>();
				foreach (string item in hashSet2)
				{
					if (long.TryParse(item, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
					{
						chestFilter.OwnerIds.Add(result);
						continue;
					}
					List<long> list = ((names == null) ? new List<long>() : names.FindIds(item));
					if (list.Count == 0)
					{
						error = "Unknown player '" + item + "'. Use a player id, or see /api/players for known names.";
						return null;
					}
					foreach (long item2 in list)
					{
						chestFilter.OwnerIds.Add(item2);
					}
				}
			}
			HashSet<string> hashSet3 = List(query, "area");
			if (hashSet3 != null)
			{
				chestFilter.NamedAreas = new List<Area>();
				foreach (string item3 in hashSet3)
				{
					if (areas == null || !areas.TryGetValue(item3, out var value))
					{
						error = "Unknown area '" + item3 + "'. See /api/areas.";
						return null;
					}
					chestFilter.NamedAreas.Add(value);
				}
			}
			if (!Number(query, "minX", out chestFilter.MinX, ref error) || !Number(query, "maxX", out chestFilter.MaxX, ref error) || !Number(query, "minZ", out chestFilter.MinZ, ref error) || !Number(query, "maxZ", out chestFilter.MaxZ, ref error) || !Number(query, "minY", out chestFilter.MinY, ref error) || !Number(query, "maxY", out chestFilter.MaxY, ref error))
			{
				return null;
			}
			if (!Number(query, "x", out var value2, ref error) || !Number(query, "z", out var value3, ref error) || !Number(query, "radius", out var value4, ref error))
			{
				return null;
			}
			switch ((value2.HasValue ? 1 : 0) + (value3.HasValue ? 1 : 0) + (value4.HasValue ? 1 : 0))
			{
			case 3:
				if (value4.Value <= 0.0)
				{
					error = "radius must be greater than 0.";
					return null;
				}
				chestFilter.Circle = new Area
				{
					Kind = "circle",
					X = value2.Value,
					Z = value3.Value,
					Radius = value4.Value
				};
				break;
			default:
				error = "x, z and radius must be given together.";
				return null;
			case 0:
				break;
			}
			if (chestFilter.MinX > chestFilter.MaxX || chestFilter.MinZ > chestFilter.MaxZ || chestFilter.MinY > chestFilter.MaxY)
			{
				error = "A min value is greater than its max.";
				return null;
			}
			if (!Bool(query, "loaded", out chestFilter.Loaded, ref error))
			{
				return null;
			}
			if (!Bool(query, "includeItems", out var value5, ref error))
			{
				return null;
			}
			if (value5.HasValue)
			{
				chestFilter.IncludeItems = value5.Value;
			}
			return chestFilter;
		}

		public bool Matches(ChestDto c)
		{
			if (PlayerOnly && !c.PlayerPlaced)
			{
				return false;
			}
			if (OwnerIds != null && (c.Owner == null || !OwnerIds.Contains(c.Owner.PlayerId)))
			{
				return false;
			}
			if (Prefabs != null && !Prefabs.Contains(c.PrefabName ?? string.Empty))
			{
				return false;
			}
			if (ExcludePrefabs != null && ExcludePrefabs.Contains(c.PrefabName ?? string.Empty))
			{
				return false;
			}
			if (Biomes != null && !Biomes.Contains(c.Biome ?? string.Empty))
			{
				return false;
			}
			if (Loaded.HasValue && c.Loaded != Loaded.Value)
			{
				return false;
			}
			if (Items != null && !c.Items.Any((ItemDto i) => Items.Contains(i.PrefabName ?? string.Empty)))
			{
				return false;
			}
			Vec3Dto pos = c.Position;
			if (pos == null)
			{
				if (NamedAreas == null && Circle == null && !MinX.HasValue && !MaxX.HasValue && !MinZ.HasValue && !MaxZ.HasValue && !MinY.HasValue)
				{
					return !MaxY.HasValue;
				}
				return false;
			}
			if (MinX.HasValue && (double)pos.X < MinX.Value)
			{
				return false;
			}
			if (MaxX.HasValue && (double)pos.X > MaxX.Value)
			{
				return false;
			}
			if (MinZ.HasValue && (double)pos.Z < MinZ.Value)
			{
				return false;
			}
			if (MaxZ.HasValue && (double)pos.Z > MaxZ.Value)
			{
				return false;
			}
			if (MinY.HasValue && (double)pos.Y < MinY.Value)
			{
				return false;
			}
			if (MaxY.HasValue && (double)pos.Y > MaxY.Value)
			{
				return false;
			}
			if (Circle != null && !Circle.Contains(pos.X, pos.Z))
			{
				return false;
			}
			if (NamedAreas != null && !NamedAreas.Any((Area a) => a.Contains(pos.X, pos.Z)))
			{
				return false;
			}
			return true;
		}

		public List<ChestDto> Apply(IEnumerable<ChestDto> chests)
		{
			return chests.Where(Matches).ToList();
		}

		private static HashSet<string> List(NameValueCollection q, string key)
		{
			string[] values = q.GetValues(key);
			if (values == null)
			{
				return null;
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			string[] array = values;
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split(new char[1] { ',' });
				for (int j = 0; j < array2.Length; j++)
				{
					string text = array2[j].Trim();
					if (text.Length > 0)
					{
						hashSet.Add(text);
					}
				}
			}
			if (hashSet.Count != 0)
			{
				return hashSet;
			}
			return null;
		}

		private static bool Number(NameValueCollection q, string key, out double? value, ref string error)
		{
			value = null;
			string text = q[key];
			if (string.IsNullOrWhiteSpace(text))
			{
				return true;
			}
			if (double.TryParse(text.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				value = result;
				return true;
			}
			error = "'" + key + "' must be a number.";
			return false;
		}

		private static bool Bool(NameValueCollection q, string key, out bool? value, ref string error)
		{
			value = null;
			string text = q[key];
			if (string.IsNullOrWhiteSpace(text))
			{
				return true;
			}
			switch (text.Trim().ToLowerInvariant())
			{
			case "true":
			case "1":
				value = true;
				return true;
			case "false":
			case "0":
				value = false;
				return true;
			default:
				error = "'" + key + "' must be true or false.";
				return false;
			}
		}
	}
	public class ResultMeta
	{
		public int ChestCount;

		public int LiveChests;

		public int ScannedChests;

		public DateTime? OldestUpdateUtc;

		public DateTime GeneratedAtUtc;
	}
	public class ItemTotal
	{
		public string PrefabName;

		public string DisplayName;

		public int TotalStack;

		public int ChestCount;
	}
	public class ItemLocation
	{
		public string ChestId;

		public string ChestPrefab;

		public Vec3Dto Position;

		public int Stack;
	}
	public class NeedLine
	{
		public string PrefabName;

		public string DisplayName;

		public int Needed;

		public int Have;

		public int Missing;

		public List<ItemLocation> Where;
	}
	public class NeedResult
	{
		public bool AllSatisfied;

		public List<NeedLine> Items;

		public ResultMeta Meta;
	}
	public class PlayerSummary
	{
		public long PlayerId;

		public string PlayerName;

		public int ChestCount;
	}
	public static class ChestAggregates
	{
		public static ResultMeta Meta(IList<ChestDto> chests)
		{
			return new ResultMeta
			{
				ChestCount = chests.Count,
				LiveChests = chests.Count((ChestDto c) => c.Loaded),
				ScannedChests = chests.Count((ChestDto c) => !c.Loaded),
				OldestUpdateUtc = ((chests.Count == 0) ? ((DateTime?)null) : new DateTime?(chests.Min((ChestDto c) => c.LastUpdatedUtc))),
				GeneratedAtUtc = DateTime.UtcNow
			};
		}

		public static List<ItemTotal> Totals(IEnumerable<ChestDto> chests, ISet<string> only)
		{
			Dictionary<string, ItemTotal> dictionary = new Dictionary<string, ItemTotal>(StringComparer.OrdinalIgnoreCase);
			foreach (ChestDto chest in chests)
			{
				HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
				foreach (ItemDto item in chest.Items)
				{
					string text = item.PrefabName ?? string.Empty;
					if (only == null || only.Contains(text))
					{
						if (!dictionary.TryGetValue(text, out var value))
						{
							value = (dictionary[text] = new ItemTotal
							{
								PrefabName = text,
								DisplayName = item.DisplayName
							});
						}
						value.TotalStack += item.Stack;
						if (hashSet.Add(text))
						{
							value.ChestCount++;
						}
					}
				}
			}
			return dictionary.Values.OrderByDescending((ItemTotal t) => t.TotalStack).ThenBy<ItemTotal, string>((ItemTotal t) => t.PrefabName, StringComparer.OrdinalIgnoreCase).ToList();
		}

		public static List<KeyValuePair<string, int>> ParseNeeds(string[] raw, out string error)
		{
			error = null;
			Dictionary<string, int> merged = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
			List<string> list = new List<string>();
			string[] array = raw ?? new string[0];
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split(new char[1] { ',' });
				for (int j = 0; j < array2.Length; j++)
				{
					string text = array2[j].Trim();
					if (text.Length != 0)
					{
						int num = text.IndexOfAny(new char[2] { ':', '=' });
						string text2 = ((num < 0) ? text : text.Substring(0, num).Trim());
						int result = 1;
						if (num >= 0 && !int.TryParse(text.Substring(num + 1).Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result))
						{
							error = "'" + text + "': the amount must be a whole number.";
							return null;
						}
						if (text2.Length == 0 || result <= 0)
						{
							error = "'" + text + "': need an item name and an amount above 0.";
							return null;
						}
						if (!merged.ContainsKey(text2))
						{
							merged[text2] = 0;
							list.Add(text2);
						}
						merged[text2] += result;
					}
				}
			}
			if (list.Count == 0)
			{
				error = "Give at least one item, e.g. need=Iron:20,Wood:50.";
				return null;
			}
			return list.Select((string n) => new KeyValuePair<string, int>(n, merged[n])).ToList();
		}

		public static NeedResult Check(IList<ChestDto> chests, List<KeyValuePair<string, int>> needs, bool includeWhere)
		{
			HashSet<string> only = new HashSet<string>(needs.Select((KeyValuePair<string, int> n) => n.Key), StringComparer.OrdinalIgnoreCase);
			Dictionary<string, ItemTotal> dictionary = Totals(chests, only).ToDictionary<ItemTotal, string>((ItemTotal t) => t.PrefabName, StringComparer.OrdinalIgnoreCase);
			List<NeedLine> list = new List<NeedLine>();
			foreach (KeyValuePair<string, int> need in needs)
			{
				dictionary.TryGetValue(need.Key, out var value);
				int num = value?.TotalStack ?? 0;
				NeedLine needLine = new NeedLine
				{
					PrefabName = (value?.PrefabName ?? need.Key),
					DisplayName = value?.DisplayName,
					Needed = need.Value,
					Have = num,
					Missing = Math.Max(0, need.Value - num)
				};
				if (includeWhere)
				{
					needLine.Where = (from c in chests
						select new ItemLocation
						{
							ChestId = c.Id,
							ChestPrefab = c.PrefabName,
							Position = c.Position,
							Stack = c.Items.Where((ItemDto i) => string.Equals(i.PrefabName, need.Key, StringComparison.OrdinalIgnoreCase)).Sum((ItemDto i) => i.Stack)
						} into l
						where l.Stack > 0
						orderby l.Stack descending
						select l).ToList();
				}
				list.Add(needLine);
			}
			return new NeedResult
			{
				AllSatisfied = list.All((NeedLine l) => l.Missing == 0),
				Items = list,
				Meta = Meta(chests)
			};
		}

		public static List<PlayerSummary> Players(IEnumerable<ChestDto> chests, PlayerNames names)
		{
			return (from c in chests
				where c.PlayerPlaced
				group c by c.Owner.PlayerId into g
				select new PlayerSummary
				{
					PlayerId = g.Key,
					PlayerName = (g.Select((ChestDto c) => c.Owner.PlayerName).FirstOrDefault((string n) => !string.IsNullOrEmpty(n)) ?? names?.GetName(g.Key)),
					ChestCount = g.Count()
				} into p
				orderby p.ChestCount descending
				select p).ToList();
		}
	}
	internal class ChestRegistry
	{
		public static readonly ChestRegistry Instance = new ChestRegistry();

		public PlayerNames Names = new PlayerNames(null);

		private readonly ConcurrentDictionary<string, ChestDto> _chests = new ConcurrentDictionary<string, ChestDto>();

		private readonly ConcurrentDictionary<int, GameObject> _containerPrefabs = new ConcurrentDictionary<int, GameObject>();

		public void BuildContainerPrefabIndex(ZNetScene scene)
		{
			try
			{
				List<GameObject> field = ReflectionHelpers.GetField<List<GameObject>>(scene, "m_prefabs");
				if (field == null)
				{
					return;
				}
				foreach (GameObject item in field)
				{
					if (!((Object)(object)item == (Object)null) && !((Object)(object)item.GetComponent<Container>() == (Object)null))
					{
						int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)item).name);
						_containerPrefabs[stableHashCode] = item;
					}
				}
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)$"[ChestAPI] Indexed {_containerPrefabs.Count} container prefab(s).");
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("[ChestAPI] Failed to build container prefab index: " + ex.Message));
				}
			}
		}

		public void ScanPersistedWorld()
		{
			try
			{
				if (ZDOMan.instance == null || _containerPrefabs.IsEmpty)
				{
					return;
				}
				LearnConnectedPlayers();
				IDictionary field = ReflectionHelpers.GetField<IDictionary>(ZDOMan.instance, "m_objectsByID");
				if (field == null)
				{
					return;
				}
				List<ZDO> list = field.Values.OfType<ZDO>().ToList();
				int num = 0;
				int num2 = 0;
				foreach (ZDO item in list)
				{
					if (!_containerPrefabs.TryGetValue(item.GetPrefab(), out var value))
					{
						continue;
					}
					num++;
					string key = ((object)Unsafe.As<ZDOID, ZDOID>(ref item.m_uid)/*cast due to .constrained prefix*/).ToString();
					if (!_chests.TryGetValue(key, out var value2) || !value2.Loaded)
					{
						ChestDto chestDto = BuildDtoFromZdo(item, value);
						if (chestDto != null)
						{
							_chests[key] = chestDto;
							num2++;
						}
					}
				}
				Names.SaveIfDirty();
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)$"[ChestAPI] Persisted-world scan: {num} container ZDO(s) found, {num2} unloaded snapshot(s) refreshed, {Names.Count} player name(s) known.");
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("[ChestAPI] Persisted-world scan failed: " + ex.Message));
				}
			}
		}

		public void OnContainerLoaded(Container container)
		{
			try
			{
				ChestDto chestDto = BuildDto(container);
				if (chestDto != null)
				{
					_chests[chestDto.Id] = chestDto;
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[ChestAPI] Failed to snapshot container: " + ex.Message));
				}
			}
		}

		public void OnContainerUnloaded(Container container)
		{
			try
			{
				ZNetView component = ((Component)container).GetComponent<ZNetView>();
				ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null);
				if (val != null)
				{
					string key = ((object)Unsafe.As<ZDOID, ZDOID>(ref val.m_uid)/*cast due to .constrained prefix*/).ToString();
					if (_chests.TryGetValue(key, out var value))
					{
						value.Loaded = false;
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[ChestAPI] Failed to mark container unloaded: " + ex.Message));
				}
			}
		}

		public List<ChestDto> GetAll()
		{
			List<ChestDto> list = _chests.Values.ToList();
			foreach (ChestDto item in list)
			{
				FillOwnerName(item);
			}
			return list;
		}

		public ChestDto GetById(string id)
		{
			if (!_chests.TryGetValue(id, out var value))
			{
				return null;
			}
			FillOwnerName(value);
			return value;
		}

		private void FillOwnerName(ChestDto chest)
		{
			if (chest.Owner != null && string.IsNullOrEmpty(chest.Owner.PlayerName))
			{
				chest.Owner.PlayerName = Names.GetName(chest.Owner.PlayerId);
			}
		}

		private ChestDto BuildDto(Container container)
		{
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			ZNetView component = ((Component)container).GetComponent<ZNetView>();
			if ((Object)(object)component == (Object)null || !component.IsValid())
			{
				return null;
			}
			ZDO zDO = component.GetZDO();
			Inventory inventory = container.GetInventory();
			if (inventory == null)
			{
				return null;
			}
			ChestDto chestDto = new ChestDto
			{
				Id = ((object)Unsafe.As<ZDOID, ZDOID>(ref zDO.m_uid)/*cast due to .constrained prefix*/).ToString(),
				PrefabName = ((Object)((Component)component).gameObject).name.Replace("(Clone)", string.Empty),
				Loaded = true,
				LastUpdatedUtc = DateTime.UtcNow,
				Width = inventory.GetWidth(),
				Height = inventory.GetHeight()
			};
			Vector3 position = zDO.GetPosition();
			chestDto.Position = new Vec3Dto
			{
				X = position.x,
				Y = position.y,
				Z = position.z
			};
			Quaternion rotation = zDO.GetRotation();
			Vector3 eulerAngles = ((Quaternion)(ref rotation)).eulerAngles;
			chestDto.RotationEuler = new Vec3Dto
			{
				X = eulerAngles.x,
				Y = eulerAngles.y,
				Z = eulerAngles.z
			};
			chestDto.Biome = TryGetBiome(position);
			chestDto.DisplayName = TryGetDisplayName(container);
			chestDto.Privacy = TryGetPrivacy(container);
			chestDto.Owner = TryGetOwner(container);
			List<ItemData> allItems = inventory.GetAllItems();
			chestDto.UsedSlots = allItems.Count;
			foreach (ItemData item in allItems)
			{
				chestDto.Items.Add(BuildItemDto(item));
			}
			return chestDto;
		}

		private ChestDto BuildDtoFromZdo(ZDO zdo, GameObject prefab)
		{
			//IL_01d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Expected O, but got Unknown
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Expected O, but got Unknown
			try
			{
				Container component = prefab.GetComponent<Container>();
				int num = ReflectionHelpers.GetField(component, "m_width", 0);
				int num2 = ReflectionHelpers.GetField(component, "m_height", 0);
				if (num <= 0)
				{
					num = 8;
				}
				if (num2 <= 0)
				{
					num2 = 4;
				}
				Inventory val = new Inventory(((Object)prefab).name, (Sprite)null, num, num2);
				byte[] byteArray = zdo.GetByteArray("items", (byte[])null);
				if (byteArray != null && byteArray.Length != 0)
				{
					val.Load(new ZPackage(byteArray));
				}
				ChestDto chestDto = new ChestDto
				{
					Id = ((object)Unsafe.As<ZDOID, ZDOID>(ref zdo.m_uid)/*cast due to .constrained prefix*/).ToString(),
					PrefabName = ((Object)prefab).name,
					Loaded = false,
					LastUpdatedUtc = DateTime.UtcNow,
					Width = val.GetWidth(),
					Height = val.GetHeight()
				};
				Vector3 position = zdo.GetPosition();
				chestDto.Position = new Vec3Dto
				{
					X = position.x,
					Y = position.y,
					Z = position.z
				};
				Quaternion rotation = zdo.GetRotation();
				Vector3 eulerAngles = ((Quaternion)(ref rotation)).eulerAngles;
				chestDto.RotationEuler = new Vec3Dto
				{
					X = eulerAngles.x,
					Y = eulerAngles.y,
					Z = eulerAngles.z
				};
				chestDto.Biome = TryGetBiome(position);
				chestDto.DisplayName = TryGetDisplayNameFromPrefab(prefab);
				chestDto.Privacy = "Unknown";
				chestDto.Owner = TryGetOwnerFromZdo(zdo);
				List<ItemData> allItems = val.GetAllItems();
				chestDto.UsedSlots = allItems.Count;
				foreach (ItemData item in allItems)
				{
					chestDto.Items.Add(BuildItemDto(item));
				}
				return chestDto;
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)$"[ChestAPI] Failed to decode persisted container {zdo.m_uid}: {ex.Message}");
				}
				return null;
			}
		}

		private string TryGetDisplayNameFromPrefab(GameObject prefab)
		{
			try
			{
				string field = ReflectionHelpers.GetField<string>(prefab.GetComponent<Piece>(), "m_name");
				if (string.IsNullOrEmpty(field))
				{
					return null;
				}
				return ReflectionHelpers.TryLocalize(field);
			}
			catch
			{
				return null;
			}
		}

		private OwnerDto TryGetOwnerFromZdo(ZDO zdo)
		{
			try
			{
				long num = zdo.GetLong("creator", 0L);
				if (num == 0L)
				{
					return null;
				}
				return new OwnerDto
				{
					PlayerId = num,
					PlayerName = TryResolvePlayerName(num)
				};
			}
			catch
			{
				return null;
			}
		}

		private ItemDto BuildItemDto(ItemData item)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			SharedData shared = item.m_shared;
			Vector2i gridPos = item.m_gridPos;
			Names.Learn(item.m_crafterID, item.m_crafterName);
			string text = null;
			try
			{
				text = ReflectionHelpers.TryLocalize(shared.m_name);
			}
			catch
			{
				text = shared.m_name;
			}
			return new ItemDto
			{
				PrefabName = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : shared.m_name),
				NameKey = shared.m_name,
				DisplayName = text,
				Stack = item.m_stack,
				MaxStack = shared.m_maxStackSize,
				Durability = item.m_durability,
				Quality = item.m_quality,
				Variant = item.m_variant,
				Equipped = item.m_equipped,
				CrafterId = item.m_crafterID,
				CrafterName = item.m_crafterName,
				GridX = gridPos.x,
				GridY = gridPos.y
			};
		}

		private string TryGetDisplayName(Container container)
		{
			try
			{
				string field = ReflectionHelpers.GetField<string>(((Component)container).GetComponent<Piece>(), "m_name");
				if (string.IsNullOrEmpty(field))
				{
					return null;
				}
				return ReflectionHelpers.TryLocalize(field);
			}
			catch
			{
				return null;
			}
		}

		private string TryGetPrivacy(Container container)
		{
			string text = ReflectionHelpers.FindFirstMemberValueContaining(container, "privacy", "private");
			if (text == null)
			{
				return "Unknown";
			}
			if (bool.TryParse(text, out var result))
			{
				if (!result)
				{
					return "Public";
				}
				return "Private";
			}
			return text;
		}

		private OwnerDto TryGetOwner(Container container)
		{
			try
			{
				Piece component = ((Component)container).GetComponent<Piece>();
				if ((Object)(object)component == (Object)null)
				{
					return null;
				}
				long num = (long)(ReflectionHelpers.CallMethod(component, "GetCreator") ?? ((object)0L));
				if (num == 0L)
				{
					return null;
				}
				return new OwnerDto
				{
					PlayerId = num,
					PlayerName = TryResolvePlayerName(num)
				};
			}
			catch
			{
				return null;
			}
		}

		private string TryResolvePlayerName(long playerId)
		{
			LearnConnectedPlayers();
			return Names.GetName(playerId);
		}

		public void LearnConnectedPlayers()
		{
			try
			{
				foreach (Player allPlayer in Player.GetAllPlayers())
				{
					if ((Object)(object)allPlayer != (Object)null)
					{
						Names.Learn(allPlayer.GetPlayerID(), allPlayer.GetPlayerName());
					}
				}
			}
			catch
			{
			}
		}

		private string TryGetBiome(Vector3 pos)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (WorldGenerator.instance == null)
				{
					return null;
				}
				return ((object)WorldGenerator.instance.GetBiome(pos)/*cast due to .constrained prefix*/).ToString();
			}
			catch
			{
				return null;
			}
		}
	}
	internal class HttpApiServer
	{
		private readonly HttpListener _listener = new HttpListener();

		private readonly string _apiKey;

		private readonly Dictionary<string, Area> _areas;

		private Thread _thread;

		private volatile bool _running;

		public HttpApiServer(string bindAddress, int port, string apiKey, Dictionary<string, Area> areas)
		{
			_apiKey = apiKey;
			_areas = areas ?? new Dictionary<string, Area>(StringComparer.OrdinalIgnoreCase);
			string arg = ((bindAddress == "0.0.0.0" || bindAddress == "::") ? "*" : bindAddress);
			_listener.Prefixes.Add($"http://{arg}:{port}/");
		}

		public void Start()
		{
			_listener.Start();
			_running = true;
			_thread = new Thread(Loop)
			{
				IsBackground = true,
				Name = "ChestAPI-Http"
			};
			_thread.Start();
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogInfo((object)("[ChestAPI] HTTP API listening on " + string.Join(", ", _listener.Prefixes)));
			}
		}

		public void Stop()
		{
			_running = false;
			try
			{
				_listener.Stop();
			}
			catch
			{
			}
			try
			{
				_listener.Close();
			}
			catch
			{
			}
		}

		private void Loop()
		{
			while (_running)
			{
				try
				{
					HttpListenerContext context = _listener.GetContext();
					ThreadPool.QueueUserWorkItem(delegate
					{
						Handle(context);
					});
				}
				catch (Exception ex)
				{
					if (_running)
					{
						ManualLogSource log = Plugin.Log;
						if (log != null)
						{
							log.LogWarning((object)("[ChestAPI] HTTP listener error: " + ex.Message));
						}
					}
				}
			}
		}

		private void Handle(HttpListenerContext context)
		{
			try
			{
				if (!CheckAuth(context))
				{
					WriteJson(context, 401, new ErrorResponse
					{
						Error = "Missing or invalid API key."
					});
					return;
				}
				string text = context.Request.Url.AbsolutePath.TrimEnd(new char[1] { '/' });
				NameValueCollection queryString = context.Request.QueryString;
				if (text.Equals("/health", StringComparison.OrdinalIgnoreCase))
				{
					WriteJson(context, 200, new
					{
						status = "ok",
						utc = DateTime.UtcNow
					});
					return;
				}
				if (text.Equals("/api/areas", StringComparison.OrdinalIgnoreCase))
				{
					WriteJson(context, 200, _areas.Values.OrderBy<Area, string>((Area a) => a.Name, StringComparer.OrdinalIgnoreCase).ToList());
					return;
				}
				if (text.Equals("/api/players", StringComparison.OrdinalIgnoreCase))
				{
					WriteJson(context, 200, ChestAggregates.Players(ChestRegistry.Instance.GetAll(), ChestRegistry.Instance.Names));
					return;
				}
				bool playerOnly;
				if (TryStrip(text, "/api/playerchests", out var rest))
				{
					playerOnly = true;
				}
				else if (TryStrip(text, "/api/chests", out rest))
				{
					playerOnly = false;
				}
				else
				{
					if (!text.StartsWith("/api/items/", StringComparison.OrdinalIgnoreCase) || !text.EndsWith("/count", StringComparison.OrdinalIgnoreCase))
					{
						WriteJson(context, 404, new ErrorResponse
						{
							Error = "Unknown endpoint."
						});
						return;
					}
					rest = "/items/" + text.Substring("/api/items/".Length);
					playerOnly = false;
				}
				HandleChestRoute(context, rest, playerOnly, queryString);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[ChestAPI] Request handling error: " + ex.Message));
				}
				try
				{
					WriteJson(context, 500, new ErrorResponse
					{
						Error = "Internal error."
					});
				}
				catch
				{
				}
			}
		}

		private static bool TryStrip(string path, string prefix, out string rest)
		{
			rest = null;
			if (path.Equals(prefix, StringComparison.OrdinalIgnoreCase))
			{
				rest = string.Empty;
				return true;
			}
			if (path.StartsWith(prefix + "/", StringComparison.OrdinalIgnoreCase))
			{
				rest = path.Substring(prefix.Length);
				return true;
			}
			return false;
		}

		private void HandleChestRoute(HttpListenerContext context, string rest, bool playerOnly, NameValueCollection query)
		{
			ChestRegistry instance = ChestRegistry.Instance;
			if (rest.Length > 1 && !rest.StartsWith("/items", StringComparison.OrdinalIgnoreCase) && !rest.Equals("/check", StringComparison.OrdinalIgnoreCase))
			{
				string text = Uri.UnescapeDataString(rest.Substring(1));
				ChestDto byId = instance.GetById(text);
				if (byId == null || (playerOnly && !byId.PlayerPlaced))
				{
					WriteJson(context, 404, new ErrorResponse
					{
						Error = "No chest with id '" + text + "'."
					});
				}
				else
				{
					WriteJson(context, 200, byId);
				}
				return;
			}
			bool flag = rest.Equals("/check", StringComparison.OrdinalIgnoreCase);
			string[] extraParams = ((!flag) ? new string[0] : new string[2] { "need", "where" });
			string error;
			ChestFilter chestFilter = ChestFilter.Parse(query, playerOnly, _areas, instance.Names, extraParams, out error);
			if (chestFilter == null)
			{
				WriteJson(context, 400, new ErrorResponse
				{
					Error = error
				});
				return;
			}
			List<ChestDto> list = chestFilter.Apply(instance.GetAll());
			if (rest.Length == 0)
			{
				WriteJson(context, 200, new ChestListResponse
				{
					Count = list.Count,
					GeneratedAtUtc = DateTime.UtcNow,
					Meta = ChestAggregates.Meta(list),
					Chests = (chestFilter.IncludeItems ? list : list.Select((ChestDto c) => c.WithoutItems()).ToList())
				});
			}
			else if (rest.Equals("/items", StringComparison.OrdinalIgnoreCase))
			{
				WriteJson(context, 200, new ItemTotalsResponse
				{
					Meta = ChestAggregates.Meta(list),
					Items = ChestAggregates.Totals(list, chestFilter.Items)
				});
			}
			else if (rest.StartsWith("/items/", StringComparison.OrdinalIgnoreCase) && rest.EndsWith("/count", StringComparison.OrdinalIgnoreCase))
			{
				string text2 = Uri.UnescapeDataString(rest.Substring("/items/".Length, rest.Length - "/items/".Length - "/count".Length));
				HashSet<string> only = new HashSet<string>(new string[1] { text2 }, StringComparer.OrdinalIgnoreCase);
				ItemTotal itemTotal = ChestAggregates.Totals(list, only).FirstOrDefault();
				WriteJson(context, 200, new ItemCountResponse
				{
					PrefabName = text2,
					TotalStack = (itemTotal?.TotalStack ?? 0),
					ChestCount = (itemTotal?.ChestCount ?? 0),
					GeneratedAtUtc = DateTime.UtcNow,
					Meta = ChestAggregates.Meta(list)
				});
			}
			else if (flag)
			{
				string error2;
				List<KeyValuePair<string, int>> list2 = ChestAggregates.ParseNeeds(query.GetValues("need"), out error2);
				if (list2 == null)
				{
					WriteJson(context, 400, new ErrorResponse
					{
						Error = error2
					});
				}
				else
				{
					bool includeWhere = string.Equals(query["where"], "true", StringComparison.OrdinalIgnoreCase) || query["where"] == "1";
					WriteJson(context, 200, ChestAggregates.Check(list, list2, includeWhere));
				}
			}
			else
			{
				WriteJson(context, 404, new ErrorResponse
				{
					Error = "Unknown endpoint."
				});
			}
		}

		private bool CheckAuth(HttpListenerContext context)
		{
			if (string.IsNullOrEmpty(_apiKey))
			{
				return false;
			}
			string text = context.Request.Headers["X-Api-Key"];
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			return FixedTimeEquals(text, _apiKey);
		}

		private static bool FixedTimeEquals(string a, string b)
		{
			byte[] bytes = Encoding.UTF8.GetBytes(a);
			byte[] bytes2 = Encoding.UTF8.GetBytes(b);
			int num = bytes.Length ^ bytes2.Length;
			for (int i = 0; i < Math.Min(bytes.Length, bytes2.Length); i++)
			{
				num |= bytes[i] ^ bytes2[i];
			}
			return num == 0;
		}

		private void WriteJson(HttpListenerContext context, int statusCode, object payload)
		{
			string s = JsonConvert.SerializeObject(payload, (Formatting)0);
			byte[] bytes = Encoding.UTF8.GetBytes(s);
			context.Response.StatusCode = statusCode;
			context.Response.ContentType = "application/json; charset=utf-8";
			context.Response.ContentLength64 = bytes.Length;
			context.Response.OutputStream.Write(bytes, 0, bytes.Length);
			context.Response.OutputStream.Close();
		}
	}
	public class Vec3Dto
	{
		public float X;

		public float Y;

		public float Z;
	}
	public class OwnerDto
	{
		public long PlayerId;

		public string PlayerName;
	}
	public class ItemDto
	{
		public string PrefabName;

		public string NameKey;

		public string DisplayName;

		public int Stack;

		public int MaxStack;

		public float Durability;

		public int Quality;

		public int Variant;

		public bool Equipped;

		public long CrafterId;

		public string CrafterName;

		public int GridX;

		public int GridY;
	}
	public class ChestDto
	{
		public string Id;

		public string PrefabName;

		public string DisplayName;

		public Vec3Dto Position;

		public Vec3Dto RotationEuler;

		public string Biome;

		public string Privacy = "Unknown";

		public OwnerDto Owner;

		public bool Loaded;

		public DateTime LastUpdatedUtc;

		public int Width;

		public int Height;

		public int UsedSlots;

		public List<ItemDto> Items = new List<ItemDto>();

		public bool PlayerPlaced
		{
			get
			{
				if (Owner != null)
				{
					return Owner.PlayerId != 0;
				}
				return false;
			}
		}

		public ChestDto WithoutItems()
		{
			ChestDto obj = (ChestDto)MemberwiseClone();
			obj.Items = new List<ItemDto>();
			return obj;
		}
	}
	public class ChestListResponse
	{
		public int Count;

		public DateTime GeneratedAtUtc;

		public ResultMeta Meta;

		public List<ChestDto> Chests;
	}
	public class ItemTotalsResponse
	{
		public ResultMeta Meta;

		public List<ItemTotal> Items;
	}
	public class ItemCountResponse
	{
		public string PrefabName;

		public int TotalStack;

		public int ChestCount;

		public DateTime GeneratedAtUtc;

		public ResultMeta Meta;
	}
	public class ErrorResponse
	{
		public string Error;
	}
	[HarmonyPatch(typeof(ZNetScene), "Awake")]
	internal static class ZNetScene_Awake_Patch
	{
		private static void Postfix(ZNetScene __instance)
		{
			ChestRegistry.Instance.BuildContainerPrefabIndex(__instance);
		}
	}
	[HarmonyPatch(typeof(Container), "Awake")]
	internal static class Container_Awake_Patch
	{
		private static void Postfix(Container __instance)
		{
			ChestRegistry.Instance.OnContainerLoaded(__instance);
			if ((Object)(object)((Component)__instance).GetComponent<ContainerUnloadTracker>() == (Object)null)
			{
				((Component)__instance).gameObject.AddComponent<ContainerUnloadTracker>();
			}
		}
	}
	[HarmonyPatch(typeof(Container), "Save")]
	internal static class Container_Save_Patch
	{
		private static void Postfix(Container __instance)
		{
			ChestRegistry.Instance.OnContainerLoaded(__instance);
		}
	}
	internal sealed class ContainerUnloadTracker : MonoBehaviour
	{
		private void OnDestroy()
		{
			Container component = ((Component)this).GetComponent<Container>();
			if ((Object)(object)component != (Object)null)
			{
				ChestRegistry.Instance.OnContainerUnloaded(component);
			}
		}
	}
	public sealed class PlayerNames
	{
		private readonly ConcurrentDictionary<long, string> _byId = new ConcurrentDictionary<long, string>();

		private readonly string _path;

		private readonly object _saveLock = new object();

		private volatile bool _dirty;

		public int Count => _byId.Count;

		public PlayerNames(string path)
		{
			_path = path;
		}

		public void Load()
		{
			if (string.IsNullOrEmpty(_path) || !File.Exists(_path))
			{
				return;
			}
			Dictionary<string, string> dictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(_path));
			if (dictionary == null)
			{
				return;
			}
			foreach (KeyValuePair<string, string> item in dictionary)
			{
				if (long.TryParse(item.Key, out var result) && result != 0L && !string.IsNullOrWhiteSpace(item.Value))
				{
					_byId[result] = item.Value;
				}
			}
		}

		public bool Learn(long playerId, string name)
		{
			if (playerId == 0L || string.IsNullOrWhiteSpace(name))
			{
				return false;
			}
			bool changed = false;
			_byId.AddOrUpdate(playerId, delegate
			{
				changed = true;
				return name;
			}, delegate(long _, string existing)
			{
				if (existing == name)
				{
					return existing;
				}
				changed = true;
				return name;
			});
			if (changed)
			{
				_dirty = true;
			}
			return changed;
		}

		public string GetName(long playerId)
		{
			if (!_byId.TryGetValue(playerId, out var value))
			{
				return null;
			}
			return value;
		}

		public List<long> FindIds(string name)
		{
			return (from p in _byId
				where string.Equals(p.Value, name, StringComparison.OrdinalIgnoreCase)
				select p.Key).ToList();
		}

		public void SaveIfDirty()
		{
			if (!_dirty || string.IsNullOrEmpty(_path))
			{
				return;
			}
			lock (_saveLock)
			{
				if (_dirty)
				{
					_dirty = false;
					Dictionary<string, string> dictionary = _byId.ToDictionary((KeyValuePair<long, string> p) => p.Key.ToString(), (KeyValuePair<long, string> p) => p.Value);
					string text = _path + ".tmp";
					File.WriteAllText(text, JsonConvert.SerializeObject((object)dictionary, (Formatting)1));
					if (File.Exists(_path))
					{
						File.Delete(_path);
					}
					File.Move(text, _path);
				}
			}
		}
	}
	[BepInPlugin("mathias.valheim.chestapi", "ChestAPI", "0.2.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "mathias.valheim.chestapi";

		public const string PluginName = "ChestAPI";

		public const string PluginVersion = "0.2.0";

		public const string ApiTokenEnvVar = "CHESTAPI_API_TOKEN";

		internal static ManualLogSource Log;

		private Harmony _harmony;

		private HttpApiServer _server;

		private ConfigEntry<string> _bindAddress;

		private ConfigEntry<int> _port;

		private ConfigEntry<string> _apiKey;

		private ConfigEntry<bool> _scanUnloadedContainers;

		private ConfigEntry<float> _scanIntervalSeconds;

		private ConfigEntry<string> _areas;

		private void Awake()
		{
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dd: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			_bindAddress = ((BaseUnityPlugin)this).Config.Bind<string>("Http", "BindAddress", "127.0.0.1", "Address to bind the HTTP API to. Use 127.0.0.1 unless you specifically want it reachable from other machines (then also set ApiKey).");
			_port = ((BaseUnityPlugin)this).Config.Bind<int>("Http", "Port", 8085, "TCP port for the HTTP API.");
			_apiKey = ((BaseUnityPlugin)this).Config.Bind<string>("Http", "ApiKey", "", "Clients must send this value in an 'X-Api-Key' header. Fallback only - prefer setting the CHESTAPI_API_TOKEN environment variable instead, which always takes priority over this value. The API denies every request until a token is set by one of these two methods.");
			_scanUnloadedContainers = ((BaseUnityPlugin)this).Config.Bind<bool>("Scan", "ScanUnloadedContainers", true, "Periodically sweep the server's full persisted world state to find chests outside currently loaded zones (e.g. far-away bases with nobody nearby). Without this, those chests only show up once a player gets close enough for the zone to load. Experimental - see README.");
			_scanIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Scan", "ScanIntervalSeconds", 60f, "How often to run the unloaded-container scan. It runs on the main thread, so very low values on a large world could cost a noticeable chunk of a server tick.");
			_areas = ((BaseUnityPlugin)this).Config.Bind<string>("Areas", "Definitions", "", "Named map regions that API callers can select with ?area=name. Separate entries with ';'. Rectangle: name:rect:minX,minZ,maxX,maxZ   Circle: name:circle:x,z,radius   Example: base:circle:0,0,150;farm:rect:-300,100,-200,200");
			List<string> problems;
			Dictionary<string, Area> areas = AreaParser.Parse(_areas.Value, out problems);
			foreach (string item in problems)
			{
				Log.LogWarning((object)("[ChestAPI] Ignoring area definition " + item));
			}
			ChestRegistry.Instance.Names = new PlayerNames(Path.Combine(Paths.ConfigPath, "mathias.valheim.chestapi.players.json"));
			try
			{
				ChestRegistry.Instance.Names.Load();
				Log.LogInfo((object)$"[ChestAPI] Loaded {ChestRegistry.Instance.Names.Count} known player name(s).");
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("[ChestAPI] Could not read the player name file, starting empty: " + ex.Message));
			}
			string text = ResolveApiToken();
			if (string.IsNullOrEmpty(text))
			{
				Log.LogWarning((object)"[ChestAPI] No API token configured - set the CHESTAPI_API_TOKEN environment variable (recommended) or the ApiKey config entry. Until then, the HTTP API will deny every request (fail closed).");
			}
			_harmony = new Harmony("mathias.valheim.chestapi");
			Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(Plugin).Assembly);
			foreach (Type type in typesFromAssembly)
			{
				try
				{
					_harmony.CreateClassProcessor(type).Patch();
				}
				catch (Exception ex2)
				{
					Log.LogError((object)("[ChestAPI] Patch " + type.Name + " failed, continuing without it: " + ex2.Message));
				}
			}
			_server = new HttpApiServer(_bindAddress.Value, _port.Value, text, areas);
			_server.Start();
			if (_scanUnloadedContainers.Value)
			{
				((MonoBehaviour)this).InvokeRepeating("RunUnloadedContainerScan", _scanIntervalSeconds.Value, _scanIntervalSeconds.Value);
			}
			Log.LogInfo((object)"[ChestAPI] Loaded v0.2.0.");
		}

		private void RunUnloadedContainerScan()
		{
			ChestRegistry.Instance.ScanPersistedWorld();
		}

		private string ResolveApiToken()
		{
			string environmentVariable = Environment.GetEnvironmentVariable("CHESTAPI_API_TOKEN");
			if (string.IsNullOrEmpty(environmentVariable))
			{
				return _apiKey.Value;
			}
			return environmentVariable;
		}

		private void OnDestroy()
		{
			((MonoBehaviour)this).CancelInvoke("RunUnloadedContainerScan");
			try
			{
				ChestRegistry.Instance.Names.SaveIfDirty();
			}
			catch
			{
			}
			_server?.Stop();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class ReflectionHelpers
	{
		private static readonly ConcurrentDictionary<(Type, string), FieldInfo> FieldCache = new ConcurrentDictionary<(Type, string), FieldInfo>();

		private static readonly ConcurrentDictionary<(Type, string), PropertyInfo> PropertyCache = new ConcurrentDictionary<(Type, string), PropertyInfo>();

		private static bool _localizationResolved;

		private static Func<object> _getLocalizationInstance;

		private static MethodInfo _localizeMethod;

		public static T GetField<T>(object instance, string fieldName, T fallback = default(T))
		{
			if (instance == null)
			{
				return fallback;
			}
			try
			{
				Type type = instance.GetType();
				FieldInfo orAdd = FieldCache.GetOrAdd((type, fieldName), ((Type, string) key) => key.Item1.GetField(key.Item2, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
				if (orAdd == null)
				{
					return fallback;
				}
				return (orAdd.GetValue(instance) is T val) ? val : fallback;
			}
			catch
			{
				return fallback;
			}
		}

		public static T GetProperty<T>(object instance, string propertyName, T fallback = default(T))
		{
			if (instance == null)
			{
				return fallback;
			}
			try
			{
				Type type = instance.GetType();
				PropertyInfo orAdd = PropertyCache.GetOrAdd((type, propertyName), ((Type, string) key) => key.Item1.GetProperty(key.Item2, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
				if (orAdd == null)
				{
					return fallback;
				}
				return (orAdd.GetValue(instance) is T val) ? val : fallback;
			}
			catch
			{
				return fallback;
			}
		}

		public static string FindFirstMemberValueContaining(object instance, params string[] nameFragments)
		{
			if (instance == null)
			{
				return null;
			}
			try
			{
				Type type = instance.GetType();
				foreach (var item3 in (from f in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
					select ((MemberInfo Member, Func<object> Get))(Member: f, Get: () => f.GetValue(instance))).Concat(from p in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
					where p.GetIndexParameters().Length == 0
					select ((MemberInfo Member, Func<object> Get))(Member: p, Get: () => p.GetValue(instance))))
				{
					MemberInfo item = item3.Member;
					Func<object> item2 = item3.Get;
					string lower = item.Name.ToLowerInvariant();
					if (!nameFragments.Any((string f) => lower.Contains(f.ToLowerInvariant())))
					{
						continue;
					}
					try
					{
						object obj = item2();
						if (obj != null)
						{
							return obj.ToString();
						}
					}
					catch
					{
					}
				}
			}
			catch
			{
			}
			return null;
		}

		public static object CallMethod(object instance, string methodName, params object[] args)
		{
			if (instance == null)
			{
				return null;
			}
			try
			{
				return instance.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(instance, args);
			}
			catch
			{
				return null;
			}
		}

		public static string TryLocalize(string key)
		{
			if (string.IsNullOrEmpty(key))
			{
				return key;
			}
			try
			{
				if (!_localizationResolved)
				{
					ResolveLocalization();
				}
				object obj = _getLocalizationInstance?.Invoke();
				if (obj == null || _localizeMethod == null)
				{
					return key;
				}
				return (_localizeMethod.Invoke(obj, new object[1] { key }) as string) ?? key;
			}
			catch
			{
				return key;
			}
		}

		private static void ResolveLocalization()
		{
			_localizationResolved = true;
			try
			{
				Type type = AppDomain.CurrentDomain.GetAssemblies().SelectMany(SafeGetTypes).FirstOrDefault((Type t) => t.Name == "Localization");
				if (type == null)
				{
					return;
				}
				PropertyInfo instanceProperty = type.GetProperty("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if (instanceProperty != null)
				{
					_getLocalizationInstance = () => instanceProperty.GetValue(null);
				}
				else
				{
					FieldInfo instanceField = type.GetField("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					if (instanceField != null)
					{
						_getLocalizationInstance = () => instanceField.GetValue(null);
					}
				}
				_localizeMethod = type.GetMethod("Localize", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(string) }, null);
			}
			catch
			{
			}
		}

		private static IEnumerable<Type> SafeGetTypes(Assembly assembly)
		{
			try
			{
				return assembly.GetTypes();
			}
			catch (ReflectionTypeLoadException ex)
			{
				return ex.Types.Where((Type t) => t != null);
			}
			catch
			{
				return Array.Empty<Type>();
			}
		}
	}
}