Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ChestAPI v0.2.0
plugins/ChestAPI/ChestAPI.dll
Decompiled a week agousing 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>(); } } } }