Decompiled source of ValheimLink v0.4.1

plugins/RlValheimLink/RlValheimLink.Contract.dll

Decompiled 13 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("RlValheimLink.Contract")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("RL Valheim Link: message model, JSON writer, pre-send validator and encoders for the pinned Raid Ledger valheim-link v1 contract. No Unity or game references.")]
[assembly: AssemblyFileVersion("0.4.1.0")]
[assembly: AssemblyInformationalVersion("0.4.1+ff1cdb186f76b7d41bda6fb07c33795e02baf157")]
[assembly: AssemblyProduct("RlValheimLink.Contract")]
[assembly: AssemblyTitle("RlValheimLink.Contract")]
[assembly: AssemblyVersion("0.4.1.0")]
namespace RlValheimLink.Contract;

public static class Bitset
{
	public static byte[] Pack(bool[] cells, int width, int height)
	{
		if (cells == null)
		{
			throw new ArgumentNullException("cells");
		}
		long num = (long)width * (long)height;
		if (cells.Length != num)
		{
			throw new ArgumentException($"expected {num} cells, got {cells.Length}");
		}
		byte[] array = new byte[Caps.BitmapBytes(width, height)];
		for (int i = 0; i < cells.Length; i++)
		{
			if (cells[i])
			{
				array[i >> 3] |= (byte)(1 << (i & 7));
			}
		}
		return array;
	}

	public static byte[] Pack(BitArray cells, int width, int height)
	{
		if (cells == null)
		{
			throw new ArgumentNullException("cells");
		}
		long num = (long)width * (long)height;
		if (cells.Length != num)
		{
			throw new ArgumentException($"expected {num} cells, got {cells.Length}");
		}
		byte[] array = new byte[Caps.BitmapBytes(width, height)];
		for (int i = 0; i < cells.Length; i++)
		{
			if (cells[i])
			{
				array[i >> 3] |= (byte)(1 << (i & 7));
			}
		}
		return array;
	}

	public static bool Any(BitArray cells)
	{
		if (cells == null)
		{
			return false;
		}
		for (int i = 0; i < cells.Length; i++)
		{
			if (cells[i])
			{
				return true;
			}
		}
		return false;
	}

	public static bool[] Unpack(byte[] bytes, int width, int height)
	{
		long num = (long)width * (long)height;
		if (bytes.Length != Caps.BitmapBytes(width, height))
		{
			throw new ArgumentException("wrong byte count for the mask");
		}
		bool[] array = new bool[num];
		for (int i = 0; i < array.Length; i++)
		{
			array[i] = (bytes[i >> 3] & (1 << (i & 7))) != 0;
		}
		return array;
	}

	public static string GzipBase64(byte[] raw)
	{
		using MemoryStream memoryStream = new MemoryStream();
		using (GZipStream gZipStream = new GZipStream(memoryStream, CompressionLevel.Optimal, leaveOpen: true))
		{
			gZipStream.Write(raw, 0, raw.Length);
		}
		byte[] array = memoryStream.ToArray();
		if (array.Length >= 10 && array[0] == 31 && array[1] == 139 && array[2] == 8 && array[3] == 0)
		{
			array[4] = (array[5] = (array[6] = (array[7] = 0)));
			array[8] = 0;
			array[9] = 3;
		}
		return Convert.ToBase64String(array);
	}

	public static ExploredBitmap Encode(bool[] cells, int width, int height)
	{
		return new ExploredBitmap
		{
			Width = width,
			Height = height,
			Data = GzipBase64(Pack(cells, width, height))
		};
	}

	public static byte[] Decode(string base64, int width, int height)
	{
		byte[] array;
		try
		{
			array = Convert.FromBase64String(base64);
		}
		catch (FormatException innerException)
		{
			throw new InvalidDataException("bad base64", innerException);
		}
		int num = Caps.BitmapBytes(width, height);
		if (array.Length < 18)
		{
			throw new InvalidDataException("gzip too short");
		}
		if (array[0] != 31 || array[1] != 139 || array[2] != 8)
		{
			throw new InvalidDataException("not a gzip member");
		}
		if (array[3] != 0)
		{
			throw new InvalidDataException("gzip FLG must be 0");
		}
		if (array.Length > Caps.BitmapMaxGzipBytes(width, height))
		{
			throw new InvalidDataException("compressed bitmap over the size-tied cap");
		}
		byte[] array2 = new byte[num];
		using (MemoryStream memoryStream = new MemoryStream(array, 10, array.Length - 18, writable: false))
		{
			using DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Decompress);
			int num2 = ReadFully(deflateStream, array2);
			if (num2 != num)
			{
				throw new InvalidDataException($"inflated {num2} bytes, expected {num}");
			}
			byte[] buffer = new byte[1];
			if (deflateStream.Read(buffer, 0, 1) != 0)
			{
				throw new InvalidDataException("inflated output over the limit");
			}
			if (memoryStream.Position != memoryStream.Length)
			{
				throw new InvalidDataException("trailing bytes after the deflate stream (second gzip member?)");
			}
		}
		uint num3 = BitConverter.ToUInt32(array, array.Length - 8);
		if (BitConverter.ToUInt32(array, array.Length - 4) != (uint)num)
		{
			throw new InvalidDataException("gzip ISIZE trailer mismatch");
		}
		if (num3 != Crc32.Compute(array2))
		{
			throw new InvalidDataException("gzip CRC mismatch");
		}
		return array2;
	}

	private static int ReadFully(Stream s, byte[] buf)
	{
		int i;
		int num;
		for (i = 0; i < buf.Length; i += num)
		{
			num = s.Read(buf, i, buf.Length - i);
			if (num <= 0)
			{
				break;
			}
		}
		return i;
	}
}
public static class Crc32
{
	private static readonly uint[] Table = BuildTable();

	private static uint[] BuildTable()
	{
		uint[] array = new uint[256];
		for (uint num = 0u; num < 256; num++)
		{
			uint num2 = num;
			for (int i = 0; i < 8; i++)
			{
				num2 = (((num2 & 1) != 0) ? (0xEDB88320u ^ (num2 >> 1)) : (num2 >> 1));
			}
			array[num] = num2;
		}
		return array;
	}

	public static uint Compute(byte[] data)
	{
		return Update(uint.MaxValue, data, 0, data.Length) ^ 0xFFFFFFFFu;
	}

	public static uint Update(uint crc, byte[] data, int offset, int count)
	{
		for (int i = offset; i < offset + count; i++)
		{
			crc = Table[(crc ^ data[i]) & 0xFF] ^ (crc >> 8);
		}
		return crc;
	}
}
public static class Caps
{
	public const int SchemaVersion = 1;

	public const int CharacterMaxBytes = 2097152;

	public const int MapMaxBytes = 8388608;

	public const int ItemsMaxBytes = 12582912;

	public const int ContainersMaxBytes = 10485760;

	public const int RecipesMaxBytes = 12582912;

	public const int BinaryMaxBytes = 16777216;

	public const int BitmapDataMaxChars = 3000000;

	public const int BitmapDataMinChars = 28;

	public const int BitmapEdgeMin = 64;

	public const int BitmapEdgeMax = 4096;

	public const int EquipmentMax = 32;

	public const int InventoryItemsMax = 512;

	public const int GridEdgeMax = 32;

	public const int VanillaRows = 4;

	public const int SkillsMax = 128;

	public const int TrophiesMax = 1024;

	public const int KnownMax = 8192;

	public const int PinsMax = 2000;

	public const int ItemExportMax = 10000;

	public const int ItemExportMin = 1;

	public const int ContainersMax = 4096;

	public const int ContainerItemsMax = 256;

	public const int ContainerSlotsMax = 4096;

	public const int ContainerEntriesMax = 50000;

	public const int RecipesMax = 4096;

	public const int PiecesMax = 4096;

	public const int StationsMax = 256;

	public const int ProcessorsMax = 256;

	public const int ProcessesMax = 2048;

	public const int RequirementsMax = 16;

	public const int ProcessInputsMax = 8;

	public const int RecipeEntriesMax = 12000;

	public const int StationLevelMax = 20;

	public const int TimestampMaxChars = 24;

	public const int WorldCoordMax = 32768;

	public const int BinaryEdgeMax = 16384;

	public const string SteamId64Min = "76561197960265728";

	public const string Int64Min = "-9223372036854775808";

	public const string Int64Max = "9223372036854775807";

	public static int BitmapBytes(int width, int height)
	{
		return (int)(((long)width * (long)height + 7) / 8);
	}

	public static int BitmapMaxGzipBytes(int width, int height)
	{
		int num = BitmapBytes(width, height);
		return num + (num + 7) / 8 + (num + 63) / 64 + 64;
	}

	public static int BitmapMaxDataChars(int width, int height)
	{
		int num = BitmapMaxGzipBytes(width, height);
		return 4 * ((num + 2) / 3);
	}

	public static int BodyCapFor(string kind)
	{
		return kind switch
		{
			"character" => 2097152, 
			"map" => 8388608, 
			"items" => 12582912, 
			"containers" => 10485760, 
			"recipes" => 12582912, 
			_ => 0, 
		};
	}
}
public static class MessageJson
{
	public static readonly UTF8Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);

	private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
	{
		Formatting = (Formatting)0,
		NullValueHandling = (NullValueHandling)0,
		DefaultValueHandling = (DefaultValueHandling)0,
		FloatFormatHandling = (FloatFormatHandling)1,
		DateParseHandling = (DateParseHandling)0,
		Culture = CultureInfo.InvariantCulture
	};

	private static readonly JsonLoadSettings ParseSettings = new JsonLoadSettings
	{
		DuplicatePropertyNameHandling = (DuplicatePropertyNameHandling)2,
		CommentHandling = (CommentHandling)0,
		LineInfoHandling = (LineInfoHandling)0
	};

	public static string Serialize(LinkMessage message)
	{
		return JsonConvert.SerializeObject((object)message, Settings);
	}

	public static byte[] SerializeToBytes(LinkMessage message)
	{
		return Utf8NoBom.GetBytes(Serialize(message));
	}

	public static JObject ToToken(LinkMessage message)
	{
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Expected O, but got Unknown
		return (JObject)Parse(Serialize(message));
	}

	public static JToken Parse(string json)
	{
		//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_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Expected O, but got Unknown
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		using StringReader stringReader = new StringReader(json);
		JsonTextReader val = new JsonTextReader((TextReader)stringReader)
		{
			DateParseHandling = (DateParseHandling)0,
			FloatParseHandling = (FloatParseHandling)0
		};
		try
		{
			JToken result = JToken.ReadFrom((JsonReader)(object)val, ParseSettings);
			if (((JsonReader)val).Read())
			{
				throw new JsonReaderException("trailing content after the JSON value");
			}
			return result;
		}
		finally
		{
			((IDisposable)val)?.Dispose();
		}
	}

	public static JToken Parse(byte[] utf8)
	{
		return Parse(Utf8NoBom.GetString(utf8));
	}

	public static byte[] ToBytes(JToken token)
	{
		return Utf8NoBom.GetBytes(token.ToString((Formatting)0, Array.Empty<JsonConverter>()));
	}

	public static string Timestamp(DateTime utc)
	{
		return utc.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss'Z'", CultureInfo.InvariantCulture);
	}

	public static bool TryParseTimestamp(string s, out DateTime utc)
	{
		return DateTime.TryParseExact(s, new string[4] { "yyyy-MM-dd'T'HH:mm:ss'Z'", "yyyy-MM-dd'T'HH:mm:ss.f'Z'", "yyyy-MM-dd'T'HH:mm:ss.ff'Z'", "yyyy-MM-dd'T'HH:mm:ss.fff'Z'" }, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out utc);
	}
}
public abstract class LinkMessage
{
	[JsonProperty("schemaVersion", Order = 0)]
	public int SchemaVersion { get; set; } = 1;

	[JsonProperty("serverId", Order = 1)]
	public string ServerId { get; set; }

	[JsonProperty("capturedAt", Order = 2)]
	public string CapturedAt { get; set; }

	[JsonProperty("gameVersion", Order = 3)]
	public string GameVersion { get; set; }

	[JsonProperty("modVersion", Order = 4)]
	public string ModVersion { get; set; }

	[JsonProperty("kind", Order = 5)]
	public abstract string Kind { get; }
}
public sealed class CharacterMessage : LinkMessage
{
	public override string Kind => "character";

	[JsonProperty("character", Order = 10)]
	public CharacterIdentity Character { get; set; } = new CharacterIdentity();

	[JsonProperty("equipment", Order = 11)]
	public List<EquippedItem> Equipment { get; set; } = new List<EquippedItem>();

	[JsonProperty("inventory", Order = 12)]
	public Inventory Inventory { get; set; } = new Inventory();

	[JsonProperty("skills", Order = 13)]
	public List<Skill> Skills { get; set; } = new List<Skill>();

	[JsonProperty("trophies", Order = 14)]
	public List<string> Trophies { get; set; } = new List<string>();

	[JsonProperty("knownItems", Order = 15)]
	public List<string> KnownItems { get; set; } = new List<string>();

	[JsonProperty("knownRecipes", Order = 16)]
	public List<string> KnownRecipes { get; set; } = new List<string>();
}
public sealed class CharacterIdentity
{
	[JsonProperty("steamId", Order = 0)]
	public string SteamId { get; set; }

	[JsonProperty("playerId", Order = 1)]
	public string PlayerId { get; set; }

	[JsonProperty("name", Order = 2)]
	public string Name { get; set; }

	[JsonProperty("worldUid", Order = 3)]
	public string WorldUid { get; set; }

	[JsonProperty("worldName", Order = 4)]
	public string WorldName { get; set; }
}
public sealed class ItemStack
{
	[JsonProperty("prefab", Order = 0)]
	public string Prefab { get; set; }

	[JsonProperty("stack", Order = 1)]
	public int Stack { get; set; }

	[JsonProperty("quality", Order = 2)]
	public int Quality { get; set; }

	[JsonProperty("variant", Order = 3)]
	public int Variant { get; set; }

	[JsonProperty("durability", Order = 4)]
	public double? Durability { get; set; }

	[JsonProperty("maxDurability", Order = 5)]
	public double? MaxDurability { get; set; }
}
public static class EquipSlot
{
	public const string Head = "head";

	public const string Chest = "chest";

	public const string Legs = "legs";

	public const string Shoulder = "shoulder";

	public const string Utility = "utility";

	public const string Trinket = "trinket";

	public const string RightHand = "rightHand";

	public const string LeftHand = "leftHand";

	public const string Ammo = "ammo";

	public static readonly string[] All = new string[9] { "head", "chest", "legs", "shoulder", "utility", "trinket", "rightHand", "leftHand", "ammo" };
}
public sealed class EquippedItem
{
	[JsonProperty("slot", Order = 0)]
	public string Slot { get; set; }

	[JsonProperty("item", Order = 1)]
	public ItemStack Item { get; set; }
}
public static class RowPurpose
{
	public const string Equipment = "equipment";

	public const string Quickslot = "quickslot";

	public const string Extended = "extended";

	public const string Other = "other";

	public static readonly string[] All = new string[4] { "equipment", "quickslot", "extended", "other" };
}
public sealed class GridExtraRow
{
	[JsonProperty("y", Order = 0)]
	public int Y { get; set; }

	[JsonProperty("purpose", Order = 1)]
	public string Purpose { get; set; }
}
public sealed class InventoryGrid
{
	[JsonProperty("width", Order = 0)]
	public int Width { get; set; }

	[JsonProperty("height", Order = 1)]
	public int Height { get; set; }

	[JsonProperty("extraRows", Order = 2)]
	public List<GridExtraRow> ExtraRows { get; set; } = new List<GridExtraRow>();
}
public sealed class InventorySlot
{
	[JsonProperty("x", Order = 0)]
	public int X { get; set; }

	[JsonProperty("y", Order = 1)]
	public int Y { get; set; }

	[JsonProperty("equipped", Order = 2)]
	public bool Equipped { get; set; }

	[JsonProperty("item", Order = 3)]
	public ItemStack Item { get; set; }
}
public sealed class Inventory
{
	[JsonProperty("grid", Order = 0)]
	public InventoryGrid Grid { get; set; } = new InventoryGrid();

	[JsonProperty("items", Order = 1)]
	public List<InventorySlot> Items { get; set; } = new List<InventorySlot>();
}
public sealed class Skill
{
	[JsonProperty("id", Order = 0)]
	public string Id { get; set; }

	[JsonProperty("level", Order = 1)]
	public double Level { get; set; }

	[JsonProperty("accumulator", Order = 2)]
	public double Accumulator { get; set; }
}
public sealed class MapMessage : LinkMessage
{
	public override string Kind => "map";

	[JsonProperty("character", Order = 10)]
	public MapCharacterRef Character { get; set; } = new MapCharacterRef();

	[JsonProperty("grid", Order = 11)]
	public MapGrid Grid { get; set; } = new MapGrid();

	[JsonProperty("explored", Order = 12)]
	public ExploredBitmap Explored { get; set; }

	[JsonProperty("sharedExplored", Order = 13)]
	public ExploredBitmap SharedExplored { get; set; }

	[JsonProperty("pins", Order = 14)]
	public List<MapPin> Pins { get; set; } = new List<MapPin>();

	[JsonProperty("baseMap", Order = 15)]
	public BinaryRef BaseMap { get; set; }
}
public sealed class MapCharacterRef
{
	[JsonProperty("steamId", Order = 0)]
	public string SteamId { get; set; }

	[JsonProperty("playerId", Order = 1)]
	public string PlayerId { get; set; }

	[JsonProperty("worldUid", Order = 2)]
	public string WorldUid { get; set; }
}
public sealed class MapGrid
{
	[JsonProperty("textureSize", Order = 0)]
	public int TextureSize { get; set; }

	[JsonProperty("pixelSizeMetres", Order = 1)]
	public double PixelSizeMetres { get; set; }
}
public sealed class ExploredBitmap
{
	public const string EncodingName = "gzip+base64-bitset-lsb0";

	[JsonProperty("encoding", Order = 0)]
	public string Encoding { get; set; } = "gzip+base64-bitset-lsb0";

	[JsonProperty("width", Order = 1)]
	public int Width { get; set; }

	[JsonProperty("height", Order = 2)]
	public int Height { get; set; }

	[JsonProperty("data", Order = 3)]
	public string Data { get; set; }
}
public sealed class MapPin
{
	[JsonProperty("type", Order = 0)]
	public string Type { get; set; }

	[JsonProperty("label", Order = 1)]
	public string Label { get; set; } = "";

	[JsonProperty("x", Order = 2)]
	public double X { get; set; }

	[JsonProperty("z", Order = 3)]
	public double Z { get; set; }

	[JsonProperty("checked", Order = 4)]
	public bool Checked { get; set; }

	[JsonProperty("shared", Order = 5)]
	public bool Shared { get; set; }
}
public sealed class BinaryRef
{
	[JsonProperty("sha256", Order = 0)]
	public string Sha256 { get; set; }

	[JsonProperty("byteLength", Order = 1)]
	public int ByteLength { get; set; }

	[JsonProperty("contentType", Order = 2)]
	public string ContentType { get; set; } = "image/png";

	[JsonProperty("width", Order = 3)]
	public int Width { get; set; }

	[JsonProperty("height", Order = 4)]
	public int Height { get; set; }
}
public sealed class ItemsMessage : LinkMessage
{
	public override string Kind => "items";

	[JsonProperty("locale", Order = 10)]
	public string Locale { get; set; }

	[JsonProperty("atlas", Order = 11)]
	public BinaryRef Atlas { get; set; }

	[JsonProperty("items", Order = 12)]
	public List<ItemDefinition> Items { get; set; } = new List<ItemDefinition>();
}
public sealed class ItemDefinition
{
	[JsonProperty("prefab", Order = 0)]
	public string Prefab { get; set; }

	[JsonProperty("name", Order = 1)]
	public string Name { get; set; }

	[JsonProperty("category", Order = 2)]
	public string Category { get; set; }

	[JsonProperty("maxStack", Order = 3)]
	public int MaxStack { get; set; }

	[JsonProperty("maxQuality", Order = 4)]
	public int MaxQuality { get; set; }

	[JsonProperty("variants", Order = 5)]
	public int Variants { get; set; }

	[JsonProperty("icon", Order = 6)]
	public ItemIcon Icon { get; set; }
}
public sealed class ItemIcon
{
	[JsonProperty("kind", Order = 0)]
	public string Kind { get; set; }

	[JsonProperty(/*Could not decode attribute arguments.*/)]
	public int? X { get; set; }

	[JsonProperty(/*Could not decode attribute arguments.*/)]
	public int? Y { get; set; }

	[JsonProperty(/*Could not decode attribute arguments.*/)]
	public int? W { get; set; }

	[JsonProperty(/*Could not decode attribute arguments.*/)]
	public int? H { get; set; }

	[JsonProperty(/*Could not decode attribute arguments.*/)]
	public string File { get; set; }

	[JsonProperty(/*Could not decode attribute arguments.*/)]
	public BinaryRef Blob { get; set; }

	public static ItemIcon Atlas(int x, int y, int w, int h)
	{
		return new ItemIcon
		{
			Kind = "atlas",
			X = x,
			Y = y,
			W = w,
			H = h
		};
	}

	public static ItemIcon FromFile(string file, BinaryRef blob = null)
	{
		return new ItemIcon
		{
			Kind = "file",
			File = file,
			Blob = blob
		};
	}
}
public sealed class ContainersMessage : LinkMessage
{
	public override string Kind => "containers";

	[JsonProperty("worldUid", Order = 10)]
	public string WorldUid { get; set; }

	[JsonProperty("containers", Order = 11)]
	public List<ContainerSnapshot> Containers { get; set; } = new List<ContainerSnapshot>();

	[JsonProperty("omittedShared", Order = 12)]
	public int OmittedShared { get; set; }

	[JsonProperty("omittedPrivate", Order = 13)]
	public int OmittedPrivate { get; set; }
}
public static class ContainerPrivacy
{
	public const string Public = "public";

	public const string Private = "private";

	public const string Group = "group";

	public static readonly string[] All = new string[3] { "public", "private", "group" };
}
public sealed class ContainerSnapshot
{
	[JsonProperty("prefab", Order = 0)]
	public string Prefab { get; set; }

	[JsonProperty("x", Order = 1)]
	public double X { get; set; }

	[JsonProperty("y", Order = 2)]
	public double Y { get; set; }

	[JsonProperty("z", Order = 3)]
	public double Z { get; set; }

	[JsonProperty("creatorPlayerId", Order = 4)]
	public string CreatorPlayerId { get; set; }

	[JsonProperty("privacy", Order = 5)]
	public string Privacy { get; set; } = "public";

	[JsonProperty("vehicle", Order = 6)]
	public bool Vehicle { get; set; }

	[JsonProperty("slots", Order = 7)]
	public int Slots { get; set; }

	[JsonProperty("usedSlots", Order = 8)]
	public int UsedSlots { get; set; }

	[JsonProperty("unreadable", Order = 9)]
	public bool Unreadable { get; set; }

	[JsonProperty("omittedEntries", Order = 10)]
	public int OmittedEntries { get; set; }

	[JsonProperty("items", Order = 11)]
	public List<ContainerStack> Items { get; set; } = new List<ContainerStack>();
}
public sealed class ContainerStack
{
	[JsonProperty("prefab", Order = 0)]
	public string Prefab { get; set; }

	[JsonProperty("quality", Order = 1)]
	public int Quality { get; set; }

	[JsonProperty("stack", Order = 2)]
	public int Stack { get; set; }
}
public sealed class RecipesMessage : LinkMessage
{
	public override string Kind => "recipes";

	[JsonProperty("locale", Order = 10)]
	public string Locale { get; set; }

	[JsonProperty("atlas", Order = 11)]
	public BinaryRef Atlas { get; set; }

	[JsonProperty("stations", Order = 12)]
	public List<CraftingStationDef> Stations { get; set; } = new List<CraftingStationDef>();

	[JsonProperty("recipes", Order = 13)]
	public List<RecipeDef> Recipes { get; set; } = new List<RecipeDef>();

	[JsonProperty("pieces", Order = 14)]
	public List<PieceDef> Pieces { get; set; } = new List<PieceDef>();

	[JsonProperty("processors", Order = 15)]
	public List<ProcessorDef> Processors { get; set; } = new List<ProcessorDef>();

	[JsonProperty("processes", Order = 16)]
	public List<ProcessDef> Processes { get; set; } = new List<ProcessDef>();
}
public sealed class AtlasIcon
{
	[JsonProperty("kind", Order = 0)]
	public string Kind { get; set; } = "atlas";

	[JsonProperty("x", Order = 1)]
	public int X { get; set; }

	[JsonProperty("y", Order = 2)]
	public int Y { get; set; }

	[JsonProperty("w", Order = 3)]
	public int W { get; set; }

	[JsonProperty("h", Order = 4)]
	public int H { get; set; }
}
public sealed class CraftingStationDef
{
	[JsonProperty("prefab", Order = 0)]
	public string Prefab { get; set; }

	[JsonProperty("name", Order = 1)]
	public string Name { get; set; }

	[JsonProperty("maxLevel", Order = 2)]
	public int MaxLevel { get; set; } = 1;

	[JsonProperty("stackableExtension", Order = 3)]
	public bool StackableExtension { get; set; }

	[JsonProperty(/*Could not decode attribute arguments.*/)]
	public bool? Upgrader { get; set; }

	[JsonProperty("icon", Order = 5)]
	public AtlasIcon Icon { get; set; }
}
public static class RecipeFormula
{
	public const string Vanilla = "vanilla";

	public const string Custom = "custom";
}
public sealed class RecipeDef
{
	[JsonProperty("id", Order = 0)]
	public string Id { get; set; }

	[JsonProperty("result", Order = 1)]
	public string Result { get; set; }

	[JsonProperty("amount", Order = 2)]
	public int Amount { get; set; } = 1;

	[JsonProperty("enabled", Order = 3)]
	public bool Enabled { get; set; } = true;

	[JsonProperty("station", Order = 4)]
	public string Station { get; set; }

	[JsonProperty("repairStation", Order = 5)]
	public string RepairStation { get; set; }

	[JsonProperty("minStationLevel", Order = 6)]
	public int MinStationLevel { get; set; }

	[JsonProperty("upgradeOnly", Order = 7)]
	public bool UpgradeOnly { get; set; }

	[JsonProperty("anyOneOf", Order = 8)]
	public bool AnyOneOf { get; set; }

	[JsonProperty("qualityResultAmountMultiplier", Order = 9)]
	public double QualityResultAmountMultiplier { get; set; } = 1.0;

	[JsonProperty("formula", Order = 10)]
	public string Formula { get; set; } = "vanilla";

	[JsonProperty("resources", Order = 11)]
	public List<RecipeResource> Resources { get; set; } = new List<RecipeResource>();
}
public sealed class RecipeResource
{
	[JsonProperty("prefab", Order = 0)]
	public string Prefab { get; set; }

	[JsonProperty("amount", Order = 1)]
	public int Amount { get; set; }

	[JsonProperty("amountPerLevel", Order = 2)]
	public int AmountPerLevel { get; set; }

	[JsonProperty("extraAmountOnlyOneIngredient", Order = 3)]
	public int ExtraAmountOnlyOneIngredient { get; set; }

	[JsonProperty("recover", Order = 4)]
	public bool Recover { get; set; }

	[JsonProperty(/*Could not decode attribute arguments.*/)]
	public bool? UpgraderResource { get; set; }
}
public sealed class PieceDef
{
	[JsonProperty("prefab", Order = 0)]
	public string Prefab { get; set; }

	[JsonProperty("name", Order = 1)]
	public string Name { get; set; }

	[JsonProperty("category", Order = 2)]
	public string Category { get; set; }

	[JsonProperty("tool", Order = 3)]
	public string Tool { get; set; }

	[JsonProperty("station", Order = 4)]
	public string Station { get; set; }

	[JsonProperty("icon", Order = 5)]
	public AtlasIcon Icon { get; set; }

	[JsonProperty("resources", Order = 6)]
	public List<PieceResource> Resources { get; set; } = new List<PieceResource>();
}
public sealed class PieceResource
{
	[JsonProperty("prefab", Order = 0)]
	public string Prefab { get; set; }

	[JsonProperty("amount", Order = 1)]
	public int Amount { get; set; }

	[JsonProperty("recover", Order = 2)]
	public bool Recover { get; set; }
}
public static class ProcessorType
{
	public const string Smelter = "smelter";

	public const string Fermenter = "fermenter";

	public const string Cooking = "cooking";

	public const string Incinerator = "incinerator";
}
public sealed class ProcessorDef
{
	[JsonProperty("prefab", Order = 0)]
	public string Prefab { get; set; }

	[JsonProperty("type", Order = 1)]
	public string Type { get; set; }

	[JsonProperty("fuel", Order = 2)]
	public string Fuel { get; set; }

	[JsonProperty("fuelPerProduct", Order = 3)]
	public int? FuelPerProduct { get; set; }

	[JsonProperty("fuelSecondsPerUnit", Order = 4)]
	public double? FuelSecondsPerUnit { get; set; }

	[JsonProperty("windPowered", Order = 5)]
	public bool WindPowered { get; set; }

	[JsonProperty("requiresRoof", Order = 6)]
	public bool RequiresRoof { get; set; }

	[JsonProperty("maxInput", Order = 7)]
	public int? MaxInput { get; set; }

	[JsonProperty("burntResult", Order = 8)]
	public string BurntResult { get; set; }

	[JsonProperty("defaultResult", Order = 9)]
	public ProcessorDefaultResult DefaultResult { get; set; }
}
public sealed class ProcessorDefaultResult
{
	[JsonProperty("prefab", Order = 0)]
	public string Prefab { get; set; }

	[JsonProperty("perInputs", Order = 1)]
	public int PerInputs { get; set; }
}
public sealed class ProcessDef
{
	[JsonProperty("processor", Order = 0)]
	public string Processor { get; set; }

	[JsonProperty("inputs", Order = 1)]
	public List<PrefabAmount> Inputs { get; set; } = new List<PrefabAmount>();

	[JsonProperty("anyOneOf", Order = 2)]
	public bool AnyOneOf { get; set; }

	[JsonProperty("output", Order = 3)]
	public PrefabAmount Output { get; set; }

	[JsonProperty("seconds", Order = 4)]
	public double? Seconds { get; set; }

	[JsonProperty("priority", Order = 5)]
	public int Priority { get; set; }
}
public sealed class PrefabAmount
{
	[JsonProperty("prefab", Order = 0)]
	public string Prefab { get; set; }

	[JsonProperty("amount", Order = 1)]
	public int Amount { get; set; }
}
public static class Png
{
	private static readonly byte[] Signature = new byte[8] { 137, 80, 78, 71, 13, 10, 26, 10 };

	public static byte[] EncodeRgba(byte[] rgba, int width, int height)
	{
		if (width <= 0 || height <= 0)
		{
			throw new ArgumentOutOfRangeException("width");
		}
		if (rgba.Length != (long)width * (long)height * 4)
		{
			throw new ArgumentException("rgba length must be width*height*4");
		}
		int num = width * 4;
		byte[] array = new byte[(num + 1) * height];
		for (int i = 0; i < height; i++)
		{
			int num2 = i * num;
			int num3 = i * (num + 1);
			array[num3] = 2;
			if (i == 0)
			{
				Buffer.BlockCopy(rgba, num2, array, num3 + 1, num);
				continue;
			}
			for (int j = 0; j < num; j++)
			{
				array[num3 + 1 + j] = (byte)(rgba[num2 + j] - rgba[num2 - num + j]);
			}
		}
		byte[] data = ZlibCompress(array);
		using MemoryStream memoryStream = new MemoryStream();
		memoryStream.Write(Signature, 0, Signature.Length);
		byte[] array2 = new byte[13];
		WriteBE(array2, 0, (uint)width);
		WriteBE(array2, 4, (uint)height);
		array2[8] = 8;
		array2[9] = 6;
		array2[10] = 0;
		array2[11] = 0;
		array2[12] = 0;
		WriteChunk(memoryStream, "IHDR", array2);
		WriteChunk(memoryStream, "IDAT", data);
		WriteChunk(memoryStream, "IEND", new byte[0]);
		return memoryStream.ToArray();
	}

	public static bool TryReadSize(byte[] png, out int width, out int height)
	{
		width = (height = 0);
		if (png == null || png.Length < 33)
		{
			return false;
		}
		for (int i = 0; i < 8; i++)
		{
			if (png[i] != Signature[i])
			{
				return false;
			}
		}
		if (ReadBE(png, 8) != 13)
		{
			return false;
		}
		if (png[12] != 73 || png[13] != 72 || png[14] != 68 || png[15] != 82)
		{
			return false;
		}
		uint num = ReadBE(png, 16);
		uint num2 = ReadBE(png, 20);
		if (num == 0 || num2 == 0 || num > int.MaxValue || num2 > int.MaxValue)
		{
			return false;
		}
		width = (int)num;
		height = (int)num2;
		return true;
	}

	public static BinaryRef MakeRef(byte[] png)
	{
		if (!TryReadSize(png, out var width, out var height))
		{
			throw new InvalidDataException("not a PNG");
		}
		return new BinaryRef
		{
			Sha256 = Sha256Hex(png),
			ByteLength = png.Length,
			ContentType = "image/png",
			Width = width,
			Height = height
		};
	}

	public static string Sha256Hex(byte[] data)
	{
		using SHA256 sHA = SHA256.Create();
		byte[] array = sHA.ComputeHash(data);
		StringBuilder stringBuilder = new StringBuilder(64);
		byte[] array2 = array;
		foreach (byte b in array2)
		{
			stringBuilder.Append(b.ToString("x2"));
		}
		return stringBuilder.ToString();
	}

	private static byte[] ZlibCompress(byte[] data)
	{
		using MemoryStream memoryStream = new MemoryStream();
		memoryStream.WriteByte(120);
		memoryStream.WriteByte(156);
		using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionLevel.Optimal, leaveOpen: true))
		{
			deflateStream.Write(data, 0, data.Length);
		}
		uint num = 1u;
		uint num2 = 0u;
		foreach (byte b in data)
		{
			num = (num + b) % 65521;
			num2 = (num2 + num) % 65521;
		}
		byte[] array = new byte[4];
		WriteBE(array, 0, (num2 << 16) | num);
		memoryStream.Write(array, 0, 4);
		return memoryStream.ToArray();
	}

	private static void WriteChunk(Stream s, string type, byte[] data)
	{
		byte[] array = new byte[4];
		WriteBE(array, 0, (uint)data.Length);
		s.Write(array, 0, 4);
		byte[] bytes = Encoding.ASCII.GetBytes(type);
		s.Write(bytes, 0, 4);
		s.Write(data, 0, data.Length);
		uint crc = Crc32.Update(uint.MaxValue, bytes, 0, 4);
		crc = Crc32.Update(crc, data, 0, data.Length) ^ 0xFFFFFFFFu;
		byte[] array2 = new byte[4];
		WriteBE(array2, 0, crc);
		s.Write(array2, 0, 4);
	}

	private static void WriteBE(byte[] b, int o, uint v)
	{
		b[o] = (byte)(v >> 24);
		b[o + 1] = (byte)(v >> 16);
		b[o + 2] = (byte)(v >> 8);
		b[o + 3] = (byte)v;
	}

	private static uint ReadBE(byte[] b, int o)
	{
		return (uint)((b[o] << 24) | (b[o + 1] << 16) | (b[o + 2] << 8) | b[o + 3]);
	}
}
public sealed class ValidationIssue
{
	public string Path { get; }

	public string Code { get; }

	public string Message { get; }

	public ValidationIssue(string path, string code, string message)
	{
		Path = path;
		Code = code;
		Message = message;
	}

	public override string ToString()
	{
		return ((Path.Length == 0) ? "<root>" : Path) + ": " + Code + " (" + Message + ")";
	}
}
public static class Validator
{
	private sealed class AtlasCheck
	{
		public bool HasAtlas;

		public bool Any;

		public int Width;

		public int Height;
	}

	private static readonly Regex ServerId = new Regex("^[A-Za-z0-9_-]{1,64}\\z", RegexOptions.CultureInvariant);

	private static readonly Regex GameVersion = new Regex("^\\d{1,4}(\\.\\d{1,5}){1,3}\\z", RegexOptions.CultureInvariant);

	private static readonly Regex SemVer = new Regex("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?\\z", RegexOptions.CultureInvariant);

	private static readonly Regex IsoDateTime = new Regex("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d+)?)?Z\\z", RegexOptions.CultureInvariant);

	private static readonly Regex SteamId = new Regex("^[1-9]\\d{16}\\z", RegexOptions.CultureInvariant);

	private static readonly Regex Int64 = new Regex("^(0|-?[1-9]\\d{0,18})\\z", RegexOptions.CultureInvariant);

	private static readonly Regex Prefab = new Regex("^[A-Za-z0-9_.-]{1,96}\\z", RegexOptions.CultureInvariant);

	private static readonly Regex Sha256 = new Regex("^[0-9a-f]{64}\\z", RegexOptions.CultureInvariant);

	private static readonly Regex SkillId = new Regex("^-?[A-Za-z0-9_]{1,64}\\z", RegexOptions.CultureInvariant);

	private static readonly Regex PinType = new Regex("^[A-Za-z0-9_]{1,32}\\z", RegexOptions.CultureInvariant);

	private static readonly Regex Category = new Regex("^[A-Za-z0-9_]{1,48}\\z", RegexOptions.CultureInvariant);

	private static readonly Regex Locale = new Regex("^[A-Za-z_-]{2,32}\\z", RegexOptions.CultureInvariant);

	private static readonly Regex IconFile = new Regex("^[A-Za-z0-9_.-]{1,100}\\.png\\z", RegexOptions.CultureInvariant);

	private static readonly Regex BitmapData = new Regex("^H4sI[A-Za-z0-9+/]*={0,2}\\z", RegexOptions.CultureInvariant);

	private static readonly BigInteger SteamMin = BigInteger.Parse("76561197960265728", CultureInfo.InvariantCulture);

	private static readonly BigInteger I64Min = BigInteger.Parse("-9223372036854775808", CultureInfo.InvariantCulture);

	private static readonly BigInteger I64Max = BigInteger.Parse("9223372036854775807", CultureInfo.InvariantCulture);

	private static readonly string[] HeaderKeys = new string[6] { "schemaVersion", "serverId", "capturedAt", "gameVersion", "modVersion", "kind" };

	private static readonly string[] ContainerPrivacy = new string[3] { "public", "private", "group" };

	private static readonly string[] ProcessorTypes = new string[4] { "smelter", "fermenter", "cooking", "incinerator" };

	private static readonly string[] Formulas = new string[2] { "vanilla", "custom" };

	public static List<ValidationIssue> Validate(LinkMessage message)
	{
		return Validate((JToken)(object)MessageJson.ToToken(message));
	}

	public static List<ValidationIssue> Validate(string json)
	{
		try
		{
			return Validate(MessageJson.Parse(json));
		}
		catch (Exception ex)
		{
			return new List<ValidationIssue>
			{
				new ValidationIssue("", "invalid_type", "not JSON: " + ex.Message)
			};
		}
	}

	public static List<ValidationIssue> Validate(JToken root)
	{
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_0049: Invalid comparison between Unknown and I4
		List<ValidationIssue> list = new List<ValidationIssue>();
		JObject val = (JObject)(object)((root is JObject) ? root : null);
		if (val == null)
		{
			list.Add(new ValidationIssue("", "invalid_type", "message must be an object"));
			return list;
		}
		JToken obj = val["kind"];
		JToken obj2 = ((obj is JValue) ? obj : null);
		string text = ((obj2 != null && (int)obj2.Type == 8) ? ((string)val["kind"]) : null);
		if (text != "character" && text != "map" && text != "items" && text != "containers" && text != "recipes")
		{
			list.Add(new ValidationIssue("kind", "invalid_union", "kind must be character, map, items, containers or recipes"));
			return list;
		}
		switch (text)
		{
		case "character":
			Character(val, list);
			break;
		case "map":
			Map(val, list);
			break;
		case "items":
			Items(val, list);
			break;
		case "containers":
			Containers(val, list);
			break;
		case "recipes":
			Recipes(val, list);
			break;
		}
		return list;
	}

	private static void Header(JObject o, List<ValidationIssue> issues)
	{
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: Invalid comparison between Unknown and I4
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Invalid comparison between Unknown and I4
		JToken val = o["schemaVersion"];
		if (val == null || ((int)val.Type != 6 && (int)val.Type != 7) || (double)val != 1.0)
		{
			issues.Add(new ValidationIssue("schemaVersion", "invalid_value", "schemaVersion must be 1"));
		}
		Str(o, "serverId", "", issues, 0, null, ServerId);
		if (Str(o, "capturedAt", "", issues, 1, null, null, out var value))
		{
			if (!IsoDateTime.IsMatch(value))
			{
				issues.Add(new ValidationIssue("capturedAt", "invalid_format", "ISO-8601 UTC with Z"));
			}
			if (value.Length > 24)
			{
				issues.Add(new ValidationIssue("capturedAt", "too_big", "at most 3 fractional digits"));
			}
		}
		Str(o, "gameVersion", "", issues, 0, null, GameVersion);
		if (Str(o, "modVersion", "", issues, 1, null, null, out var value2))
		{
			if (value2.Length > 64)
			{
				issues.Add(new ValidationIssue("modVersion", "too_big", "at most 64 chars"));
			}
			else if (!SemVer.IsMatch(value2))
			{
				issues.Add(new ValidationIssue("modVersion", "invalid_format", "SemVer 2.0.0"));
			}
		}
	}

	private static void Character(JObject o, List<ValidationIssue> issues)
	{
		//IL_0501: Unknown result type (might be due to invalid IL or missing references)
		//IL_051d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0523: Invalid comparison between Unknown and I4
		Keys(o, "", issues, HeaderKeys, "character", "equipment", "inventory", "skills", "trophies", "knownItems", "knownRecipes");
		Header(o, issues);
		if (Obj(o, "character", "", issues, out var o2))
		{
			Keys(o2, "character", issues, "steamId", "playerId", "name", "worldUid", "worldName");
			SteamId64(o2, "steamId", "character", issues);
			Int64Str(o2, "playerId", "character", issues);
			Str(o2, "name", "character", issues, 1, 32, null);
			Int64Str(o2, "worldUid", "character", issues);
			Str(o2, "worldName", "character", issues, 1, 64, null);
		}
		if (Arr(o, "equipment", "", issues, 0, 32, out var a))
		{
			HashSet<string> hashSet = new HashSet<string>();
			for (int i = 0; i < ((JContainer)a).Count; i++)
			{
				string text = "equipment." + i;
				if (ObjAt(a[i], text, issues, out var o3))
				{
					Keys(o3, text, issues, "slot", "item");
					if (Enum(o3, "slot", text, issues, EquipSlot.All, out var value) && !hashSet.Add(value))
					{
						issues.Add(new ValidationIssue(text, "custom", "duplicate equipment entry"));
					}
					if (Obj(o3, "item", text, issues, out var o4))
					{
						ItemStack(o4, text + ".item", issues);
					}
				}
			}
		}
		if (Obj(o, "inventory", "", issues, out var o5))
		{
			Keys(o5, "inventory", issues, "grid", "items");
			int value2 = 0;
			int value3 = 0;
			bool flag = false;
			JArray a2 = null;
			if (Obj(o5, "grid", "inventory", issues, out var o6))
			{
				Keys(o6, "inventory.grid", issues, "width", "height", "extraRows");
				bool num = Int(o6, "width", "inventory.grid", issues, 1L, 32L, out value2);
				bool flag2 = Int(o6, "height", "inventory.grid", issues, 1L, 32L, out value3);
				flag = num && flag2;
				if (Arr(o6, "extraRows", "inventory.grid", issues, 0, 32, out a2))
				{
					for (int j = 0; j < ((JContainer)a2).Count; j++)
					{
						string path = "inventory.grid.extraRows." + j;
						if (!ObjAt(a2[j], path, issues, out var o7))
						{
							a2 = null;
							continue;
						}
						Keys(o7, path, issues, "y", "purpose");
						Int(o7, "y", path, issues, 0L, 31L, out var _);
						Enum(o7, "purpose", path, issues, RowPurpose.All, out var _);
					}
				}
			}
			if (Arr(o5, "items", "inventory", issues, 0, 512, out var a3))
			{
				HashSet<string> hashSet2 = new HashSet<string>();
				for (int k = 0; k < ((JContainer)a3).Count; k++)
				{
					string text2 = "inventory.items." + k;
					if (!ObjAt(a3[k], text2, issues, out var o8))
					{
						continue;
					}
					Keys(o8, text2, issues, "x", "y", "equipped", "item");
					int value6;
					bool flag3 = Int(o8, "x", text2, issues, 0L, 31L, out value6);
					int value7;
					bool flag4 = Int(o8, "y", text2, issues, 0L, 31L, out value7);
					Bool(o8, "equipped", text2, issues);
					if (Obj(o8, "item", text2, issues, out var o9))
					{
						ItemStack(o9, text2 + ".item", issues);
					}
					if (flag && flag3 && flag4)
					{
						if (value6 >= value2 || value7 >= value3)
						{
							issues.Add(new ValidationIssue(text2, "custom", "slot outside grid"));
						}
						else if (!hashSet2.Add(value6 + "," + value7))
						{
							issues.Add(new ValidationIssue(text2, "custom", "duplicate slot"));
						}
					}
				}
			}
			if (flag && a2 != null)
			{
				HashSet<int> hashSet3 = new HashSet<int>();
				bool flag5 = false;
				for (int l = 0; l < ((JContainer)a2).Count; l++)
				{
					JToken obj = ((JObject)a2[l])["y"];
					JValue val = (JValue)(object)((obj is JValue) ? obj : null);
					if (val == null || (int)((JToken)val).Type != 6)
					{
						flag5 = true;
						continue;
					}
					int num2 = (int)(JToken)(object)val;
					string text3 = ((num2 < 4) ? "row is a vanilla row" : ((num2 >= value3) ? "row outside grid" : ((!hashSet3.Add(num2)) ? "duplicate row" : null)));
					if (text3 != null)
					{
						flag5 = true;
						issues.Add(new ValidationIssue("inventory.grid.extraRows." + l, "custom", text3));
					}
				}
				int num3 = Math.Max(0, value3 - 4);
				if (!flag5 && ((JContainer)a2).Count != num3)
				{
					issues.Add(new ValidationIssue("inventory.grid.extraRows", "custom", $"every row past the vanilla four must be listed ({num3})"));
				}
			}
		}
		if (Arr(o, "skills", "", issues, 0, 128, out var a4))
		{
			HashSet<string> hashSet4 = new HashSet<string>();
			for (int m = 0; m < ((JContainer)a4).Count; m++)
			{
				string path2 = "skills." + m;
				if (ObjAt(a4[m], path2, issues, out var o10))
				{
					Keys(o10, path2, issues, "id", "level", "accumulator");
					if (Str(o10, "id", path2, issues, 0, null, SkillId, out var value8) && !hashSet4.Add(value8))
					{
						issues.Add(new ValidationIssue(path2, "custom", "duplicate skills entry"));
					}
					Num(o10, "level", path2, issues, 0.0, 100.0);
					Num(o10, "accumulator", path2, issues, 0.0, 1000000.0);
				}
			}
		}
		PrefabList(o, "trophies", "", issues, 1024);
		PrefabList(o, "knownItems", "", issues, 8192);
		PrefabList(o, "knownRecipes", "", issues, 8192);
	}

	private static void ItemStack(JObject item, string path, List<ValidationIssue> issues)
	{
		Keys(item, path, issues, "prefab", "stack", "quality", "variant", "durability", "maxDurability");
		Str(item, "prefab", path, issues, 0, null, Prefab);
		Int(item, "stack", path, issues, 1L, 100000L, out var value);
		Int(item, "quality", path, issues, 1L, 10L, out value);
		Int(item, "variant", path, issues, 0L, 63L, out value);
		bool flag = NullableNum(item, "durability", path, issues, 0.0, 1000000.0);
		bool flag2 = NullableNum(item, "maxDurability", path, issues, 0.0, 1000000.0);
		if (item["durability"] != null && item["maxDurability"] != null && flag != flag2)
		{
			issues.Add(new ValidationIssue(path + ".maxDurability", "custom", "durability and maxDurability are null together"));
		}
	}

	private static void Map(JObject o, List<ValidationIssue> issues)
	{
		//IL_0194: Unknown result type (might be due to invalid IL or missing references)
		//IL_019b: Invalid comparison between Unknown and I4
		//IL_0332: Unknown result type (might be due to invalid IL or missing references)
		//IL_0339: Invalid comparison between Unknown and I4
		Keys(o, "", issues, HeaderKeys, "character", "grid", "explored", "sharedExplored", "pins", "baseMap");
		Header(o, issues);
		if (Obj(o, "character", "", issues, out var o2))
		{
			Keys(o2, "character", issues, "steamId", "playerId", "worldUid");
			SteamId64(o2, "steamId", "character", issues);
			Int64Str(o2, "playerId", "character", issues);
			Int64Str(o2, "worldUid", "character", issues);
		}
		int value = 0;
		bool flag = false;
		if (Obj(o, "grid", "", issues, out var o3))
		{
			Keys(o3, "grid", issues, "textureSize", "pixelSizeMetres");
			flag = Int(o3, "textureSize", "grid", issues, 64L, 4096L, out value);
			Num(o3, "pixelSizeMetres", "grid", issues, double.Epsilon, 256.0);
		}
		if (Obj(o, "explored", "", issues, out var o4))
		{
			Bitmap(o4, "explored", issues, flag ? value : (-1));
		}
		JToken val = o["sharedExplored"];
		JObject o5;
		if (val == null)
		{
			issues.Add(new ValidationIssue("sharedExplored", "invalid_type", "required (nullable)"));
		}
		else if ((int)val.Type != 10 && ObjAt(val, "sharedExplored", issues, out o5))
		{
			Bitmap(o5, "sharedExplored", issues, flag ? value : (-1));
		}
		if (Arr(o, "pins", "", issues, 0, 2000, out var a))
		{
			for (int i = 0; i < ((JContainer)a).Count; i++)
			{
				string path = "pins." + i;
				if (ObjAt(a[i], path, issues, out var o6))
				{
					Keys(o6, path, issues, "type", "label", "x", "z", "checked", "shared");
					Str(o6, "type", path, issues, 0, null, PinType);
					Str(o6, "label", path, issues, 0, 64, null);
					Num(o6, "x", path, issues, -32768.0, 32768.0);
					Num(o6, "z", path, issues, -32768.0, 32768.0);
					Bool(o6, "checked", path, issues);
					Bool(o6, "shared", path, issues);
				}
			}
		}
		JToken val2 = o["baseMap"];
		JObject o7;
		int w = default(int);
		int h = default(int);
		if (val2 == null)
		{
			issues.Add(new ValidationIssue("baseMap", "invalid_type", "required (nullable)"));
		}
		else if ((int)val2.Type != 10 && ObjAt(val2, "baseMap", issues, out o7) && BinaryRef(o7, "baseMap", issues, out w, out h) && flag && (w != value || h != value))
		{
			issues.Add(new ValidationIssue("baseMap", "custom", "base map size must equal grid.textureSize"));
		}
	}

	private static void Bitmap(JObject b, string path, List<ValidationIssue> issues, int textureSize)
	{
		Keys(b, path, issues, "encoding", "width", "height", "data");
		Literal(b, "encoding", path, issues, "gzip+base64-bitset-lsb0");
		int value;
		bool flag = Int(b, "width", path, issues, 64L, 4096L, out value);
		int value2;
		bool flag2 = Int(b, "height", path, issues, 64L, 4096L, out value2);
		if (Str(b, "data", path, issues, 0, null, null, out var value3))
		{
			string path2 = path + ".data";
			if (value3.Length < 28)
			{
				issues.Add(new ValidationIssue(path2, "too_small", "at least 28 chars"));
			}
			if (value3.Length > 3000000)
			{
				issues.Add(new ValidationIssue(path2, "too_big", "at most 3000000 chars"));
			}
			if (!BitmapData.IsMatch(value3))
			{
				issues.Add(new ValidationIssue(path2, "invalid_format", "one gzip member (H4sI…), standard base64"));
			}
			if (value3.Length % 4 != 0)
			{
				issues.Add(new ValidationIssue(path2, "custom", "base64 length must be a multiple of 4"));
			}
			if (flag && flag2 && value3.Length > Caps.BitmapMaxDataChars(value, value2))
			{
				issues.Add(new ValidationIssue(path2, "custom", $"compressed bitmap exceeds {Caps.BitmapMaxDataChars(value, value2)} chars for {value}×{value2}"));
			}
		}
		if (flag && flag2 && textureSize > 0 && (value != textureSize || value2 != textureSize))
		{
			issues.Add(new ValidationIssue(path, "custom", "bitmap size must equal grid.textureSize"));
		}
	}

	private static bool BinaryRef(JObject b, string path, List<ValidationIssue> issues, out int w, out int h)
	{
		Keys(b, path, issues, "sha256", "byteLength", "contentType", "width", "height");
		Str(b, "sha256", path, issues, 0, null, Sha256);
		Int(b, "byteLength", path, issues, 1L, 16777216L, out var _);
		Literal(b, "contentType", path, issues, "image/png");
		bool num = Int(b, "width", path, issues, 1L, 16384L, out w);
		bool flag = Int(b, "height", path, issues, 1L, 16384L, out h);
		return num && flag;
	}

	private static void Items(JObject o, List<ValidationIssue> issues)
	{
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0091: Invalid comparison between Unknown and I4
		//IL_0495: Unknown result type (might be due to invalid IL or missing references)
		//IL_049c: Invalid comparison between Unknown and I4
		//IL_0262: Unknown result type (might be due to invalid IL or missing references)
		//IL_0269: Invalid comparison between Unknown and I4
		//IL_02a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ab: Invalid comparison between Unknown and I4
		Keys(o, "", issues, HeaderKeys, "locale", "atlas", "items");
		Header(o, issues);
		Str(o, "locale", "", issues, 0, null, Locale);
		bool flag = false;
		int w = 0;
		int h = 0;
		JToken val = o["atlas"];
		JObject o2;
		if (val == null)
		{
			issues.Add(new ValidationIssue("atlas", "invalid_type", "required (nullable)"));
		}
		else if ((int)val.Type != 10 && ObjAt(val, "atlas", issues, out o2))
		{
			flag = BinaryRef(o2, "atlas", issues, out w, out h);
		}
		bool flag2 = false;
		if (Arr(o, "items", "", issues, 1, 10000, out var a))
		{
			HashSet<string> hashSet = new HashSet<string>();
			for (int i = 0; i < ((JContainer)a).Count; i++)
			{
				string text = "items." + i;
				if (!ObjAt(a[i], text, issues, out var o3))
				{
					continue;
				}
				Keys(o3, text, issues, "prefab", "name", "category", "maxStack", "maxQuality", "variants", "icon");
				if (Str(o3, "prefab", text, issues, 0, null, Prefab, out var value) && !hashSet.Add(value))
				{
					issues.Add(new ValidationIssue(text + ".prefab", "custom", "duplicate prefab"));
				}
				Str(o3, "name", text, issues, 1, 96, null);
				Str(o3, "category", text, issues, 0, null, Category);
				Int(o3, "maxStack", text, issues, 1L, 100000L, out var value2);
				Int(o3, "maxQuality", text, issues, 1L, 10L, out value2);
				Int(o3, "variants", text, issues, 0L, 64L, out value2);
				JToken val2 = o3["icon"];
				if (val2 == null)
				{
					issues.Add(new ValidationIssue(text + ".icon", "invalid_type", "required (nullable)"));
				}
				else
				{
					if ((int)val2.Type == 10)
					{
						continue;
					}
					string text2 = text + ".icon";
					if (!ObjAt(val2, text2, issues, out var o4))
					{
						continue;
					}
					JToken obj = o4["kind"];
					JToken obj2 = ((obj is JValue) ? obj : null);
					string text3 = ((obj2 != null && (int)obj2.Type == 8) ? ((string)o4["kind"]) : null);
					if (text3 == "atlas")
					{
						flag2 = true;
						Keys(o4, text2, issues, "kind", "x", "y", "w", "h");
						int value3;
						bool flag3 = Int(o4, "x", text2, issues, 0L, 16383L, out value3);
						int value4;
						bool flag4 = Int(o4, "y", text2, issues, 0L, 16383L, out value4);
						int value5;
						bool flag5 = Int(o4, "w", text2, issues, 1L, 512L, out value5);
						int value6;
						bool flag6 = Int(o4, "h", text2, issues, 1L, 512L, out value6);
						if (flag && flag3 && flag4 && flag5 && flag6 && (value3 + value5 > w || value4 + value6 > h))
						{
							issues.Add(new ValidationIssue(text2, "custom", "icon outside atlas"));
						}
					}
					else if (text3 == "file")
					{
						Keys(o4, text2, issues, "kind", "file", "blob");
						Str(o4, "file", text2, issues, 0, null, IconFile);
						JToken val3 = o4["blob"];
						if (val3 != null && ObjAt(val3, text2 + ".blob", issues, out var o5))
						{
							BinaryRef(o5, text2 + ".blob", issues, out value2, out var _);
						}
					}
					else
					{
						issues.Add(new ValidationIssue(text2 + ".kind", "invalid_union", "icon kind must be atlas or file"));
					}
				}
			}
		}
		if (val != null && (int)val.Type == 10 && flag2)
		{
			issues.Add(new ValidationIssue("atlas", "custom", "atlas icon without an atlas"));
		}
	}

	private static void Containers(JObject o, List<ValidationIssue> issues)
	{
		Keys(o, "", issues, HeaderKeys, "worldUid", "containers", "omittedShared", "omittedPrivate");
		Header(o, issues);
		Int64Str(o, "worldUid", "", issues);
		int value10;
		if (Arr(o, "containers", "", issues, 0, 4096, out var a))
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			int num = 0;
			for (int i = 0; i < ((JContainer)a).Count; i++)
			{
				string text = "containers." + i;
				if (!ObjAt(a[i], text, issues, out var o2))
				{
					continue;
				}
				Keys(o2, text, issues, "prefab", "x", "y", "z", "creatorPlayerId", "privacy", "vehicle", "slots", "usedSlots", "unreadable", "omittedEntries", "items");
				string value;
				bool flag = Str(o2, "prefab", text, issues, 0, null, Prefab, out value);
				double value2;
				bool flag2 = Num(o2, "x", text, issues, -32768.0, 32768.0, out value2);
				double value3;
				bool flag3 = Num(o2, "y", text, issues, -32768.0, 32768.0, out value3);
				double value4;
				bool flag4 = Num(o2, "z", text, issues, -32768.0, 32768.0, out value4);
				string value5;
				bool flag5 = Int64Str(o2, "creatorPlayerId", text, issues, out value5);
				if (flag5 && value5 == "0")
				{
					issues.Add(new ValidationIssue(text + ".creatorPlayerId", "custom", "creatorPlayerId must not be 0"));
					flag5 = false;
				}
				Enum(o2, "privacy", text, issues, ContainerPrivacy, out var _);
				Bool(o2, "vehicle", text, issues);
				int value7;
				bool flag6 = Int(o2, "slots", text, issues, 1L, 4096L, out value7);
				int value8;
				bool flag7 = Int(o2, "usedSlots", text, issues, 0L, 4096L, out value8);
				bool value9;
				bool flag8 = Bool(o2, "unreadable", text, issues, out value9);
				Int(o2, "omittedEntries", text, issues, 0L, 100000000L, out value10);
				if (Arr(o2, "items", text, issues, 0, 256, out var a2))
				{
					num += ((JContainer)a2).Count;
					HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal);
					for (int j = 0; j < ((JContainer)a2).Count; j++)
					{
						string path = text + ".items." + j;
						if (ObjAt(a2[j], path, issues, out var o3))
						{
							Keys(o3, path, issues, "prefab", "quality", "stack");
							string value11;
							bool num2 = Str(o3, "prefab", path, issues, 0, null, Prefab, out value11);
							int value12;
							bool flag9 = Int(o3, "quality", path, issues, 1L, 10L, out value12);
							Int(o3, "stack", path, issues, 1L, 1000000000L, out value10);
							if (num2 && flag9 && !hashSet2.Add(value11 + "@" + value12.ToString(CultureInfo.InvariantCulture)))
							{
								issues.Add(new ValidationIssue(path, "custom", "duplicate (prefab, quality) stack"));
							}
						}
					}
					if (flag8 && value9 && (((JContainer)a2).Count > 0 || (flag7 && value8 != 0)))
					{
						issues.Add(new ValidationIssue(text + ".items", "custom", "an unreadable container carries no items"));
					}
				}
				if (flag6 && flag7 && value8 > value7)
				{
					issues.Add(new ValidationIssue(text + ".usedSlots", "custom", "usedSlots exceeds slots"));
				}
				if (flag && flag2 && flag3 && flag4 && flag5 && !hashSet.Add(value + "|" + Coord(value2) + "|" + Coord(value3) + "|" + Coord(value4) + "|" + value5))
				{
					issues.Add(new ValidationIssue(text, "custom", "duplicate container (prefab, position, creator)"));
				}
			}
			if (num > 50000)
			{
				issues.Add(new ValidationIssue("containers", "too_big", $"items summed over all containers exceed {50000}"));
			}
		}
		Int(o, "omittedShared", "", issues, 0L, 1000000L, out value10);
		Int(o, "omittedPrivate", "", issues, 0L, 1000000L, out value10);
	}

	private static void Recipes(JObject o, List<ValidationIssue> issues)
	{
		//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b1: Invalid comparison between Unknown and I4
		//IL_0b7e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0b85: Invalid comparison between Unknown and I4
		//IL_07f0: Unknown result type (might be due to invalid IL or missing references)
		//IL_07f7: Invalid comparison between Unknown and I4
		Keys(o, "", issues, HeaderKeys, "locale", "atlas", "stations", "recipes", "pieces", "processors", "processes");
		Header(o, issues);
		Str(o, "locale", "", issues, 0, null, Locale);
		bool hasAtlas = false;
		int w = 0;
		int h = 0;
		JToken val = o["atlas"];
		JObject o2;
		if (val == null)
		{
			issues.Add(new ValidationIssue("atlas", "invalid_type", "required (nullable)"));
		}
		else if ((int)val.Type != 10 && ObjAt(val, "atlas", issues, out o2))
		{
			hasAtlas = BinaryRef(o2, "atlas", issues, out w, out h);
		}
		AtlasCheck atlasCheck = new AtlasCheck
		{
			HasAtlas = hasAtlas,
			Width = w,
			Height = h
		};
		int num = 0;
		HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
		int value2;
		if (Arr(o, "stations", "", issues, 0, 256, out var a))
		{
			for (int i = 0; i < ((JContainer)a).Count; i++)
			{
				string text = "stations." + i;
				if (ObjAt(a[i], text, issues, out var o3))
				{
					Keys(o3, text, issues, "prefab", "name", "maxLevel", "stackableExtension", "upgrader", "icon");
					if (Str(o3, "prefab", text, issues, 0, null, Prefab, out var value) && !hashSet.Add(value))
					{
						issues.Add(new ValidationIssue(text + ".prefab", "custom", "duplicate stations prefab"));
					}
					Str(o3, "name", text, issues, 1, 96, null);
					Int(o3, "maxLevel", text, issues, 1L, 20L, out value2);
					Bool(o3, "stackableExtension", text, issues);
					OptionalBool(o3, "upgrader", text, issues);
					AtlasIcon(o3, text, issues, atlasCheck);
				}
			}
		}
		string value4;
		if (Arr(o, "recipes", "", issues, 0, 4096, out var a2))
		{
			HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal);
			for (int j = 0; j < ((JContainer)a2).Count; j++)
			{
				string text2 = "recipes." + j;
				if (ObjAt(a2[j], text2, issues, out var o4))
				{
					Keys(o4, text2, issues, "id", "result", "amount", "enabled", "station", "repairStation", "minStationLevel", "upgradeOnly", "anyOneOf", "qualityResultAmountMultiplier", "formula", "resources");
					if (Str(o4, "id", text2, issues, 0, null, Prefab, out var value3) && !hashSet2.Add(value3))
					{
						issues.Add(new ValidationIssue(text2 + ".id", "custom", "duplicate recipe id"));
					}
					Str(o4, "result", text2, issues, 0, null, Prefab);
					Int(o4, "amount", text2, issues, 1L, 10000L, out value2);
					Bool(o4, "enabled", text2, issues);
					StationRef(o4, "station", text2, issues, hashSet);
					StationRef(o4, "repairStation", text2, issues, hashSet);
					Int(o4, "minStationLevel", text2, issues, 0L, 20L, out value2);
					Bool(o4, "upgradeOnly", text2, issues);
					Bool(o4, "anyOneOf", text2, issues);
					Num(o4, "qualityResultAmountMultiplier", text2, issues, 0.0, 100.0);
					Enum(o4, "formula", text2, issues, Formulas, out value4);
					num += Resources(o4, text2, issues, recipe: true);
				}
			}
		}
		if (Arr(o, "pieces", "", issues, 0, 4096, out var a3))
		{
			HashSet<string> hashSet3 = new HashSet<string>(StringComparer.Ordinal);
			for (int k = 0; k < ((JContainer)a3).Count; k++)
			{
				string text3 = "pieces." + k;
				if (ObjAt(a3[k], text3, issues, out var o5))
				{
					Keys(o5, text3, issues, "prefab", "name", "category", "tool", "station", "icon", "resources");
					if (Str(o5, "prefab", text3, issues, 0, null, Prefab, out var value5) && !hashSet3.Add(value5))
					{
						issues.Add(new ValidationIssue(text3 + ".prefab", "custom", "duplicate pieces prefab"));
					}
					Str(o5, "name", text3, issues, 1, 96, null);
					Str(o5, "category", text3, issues, 0, null, Category);
					Str(o5, "tool", text3, issues, 0, null, Prefab);
					StationRef(o5, "station", text3, issues, hashSet);
					AtlasIcon(o5, text3, issues, atlasCheck);
					num += Resources(o5, text3, issues, recipe: false);
				}
			}
		}
		HashSet<string> hashSet4 = new HashSet<string>(StringComparer.Ordinal);
		if (Arr(o, "processors", "", issues, 0, 256, out var a4))
		{
			for (int l = 0; l < ((JContainer)a4).Count; l++)
			{
				string text4 = "processors." + l;
				if (ObjAt(a4[l], text4, issues, out var o6))
				{
					Keys(o6, text4, issues, "prefab", "type", "fuel", "fuelPerProduct", "fuelSecondsPerUnit", "windPowered", "requiresRoof", "maxInput", "burntResult", "defaultResult");
					if (Str(o6, "prefab", text4, issues, 0, null, Prefab, out var value6) && !hashSet4.Add(value6))
					{
						issues.Add(new ValidationIssue(text4 + ".prefab", "custom", "duplicate processors prefab"));
					}
					Enum(o6, "type", text4, issues, ProcessorTypes, out value4);
					NullablePrefab(o6, "fuel", text4, issues, out value4);
					NullableInt(o6, "fuelPerProduct", text4, issues, 0L, 1000L);
					NullableNum(o6, "fuelSecondsPerUnit", text4, issues, 0.001, 86400.0);
					Bool(o6, "windPowered", text4, issues);
					Bool(o6, "requiresRoof", text4, issues);
					NullableInt(o6, "maxInput", text4, issues, 1L, 10000L);
					NullablePrefab(o6, "burntResult", text4, issues, out value4);
					JToken val2 = o6["defaultResult"];
					JObject o7;
					if (val2 == null)
					{
						issues.Add(new ValidationIssue(text4 + ".defaultResult", "invalid_type", "required (nullable)"));
					}
					else if ((int)val2.Type != 10 && ObjAt(val2, text4 + ".defaultResult", issues, out o7))
					{
						Keys(o7, text4 + ".defaultResult", issues, "prefab", "perInputs");
						Str(o7, "prefab", text4 + ".defaultResult", issues, 0, null, Prefab);
						Int(o7, "perInputs", text4 + ".defaultResult", issues, 1L, 10000L, out value2);
					}
				}
			}
		}
		if (Arr(o, "processes", "", issues, 0, 2048, out var a5))
		{
			for (int m = 0; m < ((JContainer)a5).Count; m++)
			{
				string text5 = "processes." + m;
				if (!ObjAt(a5[m], text5, issues, out var o8))
				{
					continue;
				}
				Keys(o8, text5, issues, "processor", "inputs", "anyOneOf", "output", "seconds", "priority");
				if (Str(o8, "processor", text5, issues, 0, null, Prefab, out var value7) && !hashSet4.Contains(value7))
				{
					issues.Add(new ValidationIssue(text5 + ".processor", "custom", "unknown processor"));
				}
				if (Arr(o8, "inputs", text5, issues, 1, 8, out var a6))
				{
					num += ((JContainer)a6).Count;
					HashSet<string> hashSet5 = new HashSet<string>(StringComparer.Ordinal);
					for (int n = 0; n < ((JContainer)a6).Count; n++)
					{
						string text6 = text5 + ".inputs." + n;
						if (ObjAt(a6[n], text6, issues, out var o9))
						{
							Keys(o9, text6, issues, "prefab", "amount");
							if (Str(o9, "prefab", text6, issues, 0, null, Prefab, out var value8) && !hashSet5.Add(value8))
							{
								issues.Add(new ValidationIssue(text6 + ".prefab", "custom", "duplicate input prefab"));
							}
							Int(o9, "amount", text6, issues, 1L, 10000L, out value2);
						}
					}
				}
				Bool(o8, "anyOneOf", text5, issues);
				if (Obj(o8, "output", text5, issues, out var o10))
				{
					Keys(o10, text5 + ".output", issues, "prefab", "amount");
					Str(o10, "prefab", text5 + ".output", issues, 0, null, Prefab);
					Int(o10, "amount", text5 + ".output", issues, 1L, 10000L, out value2);
				}
				NullableNum(o8, "seconds", text5, issues, 0.0, 604800.0);
				Int(o8, "priority", text5, issues, -10000L, 10000L, out value2);
			}
		}
		if (atlasCheck.Any && val != null && (int)val.Type == 10)
		{
			issues.Add(new ValidationIssue("atlas", "custom", "atlas icon without an atlas"));
		}
		if (num > 12000)
		{
			issues.Add(new ValidationIssue("", "too_big", $"recipe resources, piece resources and process inputs exceed {12000} in total"));
		}
	}

	private static void AtlasIcon(JObject parent, string path, List<ValidationIssue> issues, AtlasCheck atlas)
	{
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_003a: Invalid comparison between Unknown and I4
		string path2 = Join(path, "icon");
		JToken val = parent["icon"];
		JObject o;
		if (val == null)
		{
			issues.Add(new ValidationIssue(path2, "invalid_type", "required (nullable)"));
		}
		else if ((int)val.Type != 10 && ObjAt(val, path2, issues, out o))
		{
			atlas.Any = true;
			Keys(o, path2, issues, "kind", "x", "y", "w", "h");
			Literal(o, "kind", path2, issues, "atlas");
			int value;
			bool flag = Int(o, "x", path2, issues, 0L, 16383L, out value);
			int value2;
			bool flag2 = Int(o, "y", path2, issues, 0L, 16383L, out value2);
			int value3;
			bool flag3 = Int(o, "w", path2, issues, 1L, 512L, out value3);
			int value4;
			bool flag4 = Int(o, "h", path2, issues, 1L, 512L, out value4);
			if (atlas.HasAtlas && flag && flag2 && flag3 && flag4 && (value + value3 > atlas.Width || value2 + value4 > atlas.Height))
			{
				issues.Add(new ValidationIssue(path2, "custom", "icon outside atlas"));
			}
		}
	}

	private static int Resources(JObject parent, string path, List<ValidationIssue> issues, bool recipe)
	{
		if (!Arr(parent, "resources", path, issues, 0, 16, out var a))
		{
			return 0;
		}
		HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
		for (int i = 0; i < ((JContainer)a).Count; i++)
		{
			string text = path + ".resources." + i;
			if (ObjAt(a[i], text, issues, out var o))
			{
				if (recipe)
				{
					Keys(o, text, issues, "prefab", "amount", "amountPerLevel", "extraAmountOnlyOneIngredient", "recover", "upgraderResource");
				}
				else
				{
					Keys(o, text, issues, "prefab", "amount", "recover");
				}
				if (Str(o, "prefab", text, issues, 0, null, Prefab, out var value) && !hashSet.Add(value))
				{
					issues.Add(new ValidationIssue(text + ".prefab", "custom", "duplicate resources prefab"));
				}
				Int(o, "amount", text, issues, 0L, 10000L, out var value2);
				if (recipe)
				{
					Int(o, "amountPerLevel", text, issues, 0L, 10000L, out value2);
					Int(o, "extraAmountOnlyOneIngredient", text, issues, 0L, 10000L, out value2);
				}
				Bool(o, "recover", text, issues);
				if (recipe)
				{
					OptionalBool(o, "upgraderResource", text, issues);
				}
			}
		}
		return ((JContainer)a).Count;
	}

	private static void StationRef(JObject parent, string key, string path, List<ValidationIssue> issues, HashSet<string> stations)
	{
		if (NullablePrefab(parent, key, path, issues, out var value) && value != null && !stations.Contains(value))
		{
			issues.Add(new ValidationIssue(Join(path, key), "custom", "unknown station"));
		}
	}

	private static string Coord(double v)
	{
		return ((v == 0.0) ? 0.0 : v).ToString("R", CultureInfo.InvariantCulture);
	}

	private static string Join(string path, string key)
	{
		if (path.Length != 0)
		{
			return path + "." + key;
		}
		return key;
	}

	private static void Keys(JObject o, string path, List<ValidationIssue> issues, string[] first, params string[] rest)
	{
		string[] array = new string[first.Length + rest.Length];
		first.CopyTo(array, 0);
		rest.CopyTo(array, first.Length);
		Keys(o, path, issues, array);
	}

	private static void Keys(JObject o, string path, List<ValidationIssue> issues, params string[] allowed)
	{
		HashSet<string> hashSet = new HashSet<string>(allowed, StringComparer.Ordinal);
		List<string> list = null;
		foreach (JProperty item in o.Properties())
		{
			if (!hashSet.Contains(item.Name))
			{
				(list ?? (list = new List<string>())).Add(item.Name);
			}
		}
		if (list != null)
		{
			issues.Add(new ValidationIssue(path, "unrecognized_keys", "unknown key(s): " + string.Join(", ", list)));
		}
	}

	private static bool ObjAt(JToken t, string path, List<ValidationIssue> issues, out JObject o)
	{
		o = (JObject)(object)((t is JObject) ? t : null);
		if (o != null)
		{
			return true;
		}
		issues.Add(new ValidationIssue(path, "invalid_type", "expected object"));
		return false;
	}

	private static bool Obj(JObject parent, string key, string path, List<ValidationIssue> issues, out JObject o)
	{
		o = null;
		JToken val = parent[key];
		if (val == null)
		{
			issues.Add(new ValidationIssue(Join(path, key), "invalid_type", "required"));
			return false;
		}
		return ObjAt(val, Join(path, key), issues, out o);
	}

	private static bool Arr(JObject parent, string key, string path, List<ValidationIssue> issues, int min, int max, out JArray a)
	{
		a = null;
		string path2 = Join(path, key);
		JToken val = parent[key];
		if (val == null)
		{
			issues.Add(new ValidationIssue(path2, "invalid_type", "required"));
			return false;
		}
		a = (JArray)(object)((val is JArray) ? val : null);
		if (a == null)
		{
			issues.Add(new ValidationIssue(path2, "invalid_type", "expected array"));
			return false;
		}
		if (((JContainer)a).Count < min)
		{
			issues.Add(new ValidationIssue(path2, "too_small", $"at least {min} entries"));
		}
		if (((JContainer)a).Count > max)
		{
			issues.Add(new ValidationIssue(path2, "too_big", $"at most {max} entries"));
		}
		return true;
	}

	private static bool Str(JObject parent, string key, string path, List<ValidationIssue> issues, int min, int? max, Regex pattern)
	{
		string value;
		return Str(parent, key, path, issues, min, max, pattern, out value);
	}

	private static bool Str(JObject parent, string key, string path, List<ValidationIssue> issues, int min, int? max, Regex pattern, out string value)
	{
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Invalid comparison between Unknown and I4
		value = null;
		string path2 = Join(path, key);
		JToken val = parent[key];
		if (val == null)
		{
			issues.Add(new ValidationIssue(path2, "invalid_type", "required"));
			return false;
		}
		if ((int)val.Type != 8)
		{
			issues.Add(new ValidationIssue(path2, "invalid_type", "expected string"));
			return false;
		}
		value = (string)val;
		bool flag = true;
		if (value.Length < min)
		{
			issues.Add(new ValidationIssue(path2, "too_small", $"at least {min} chars"));
			flag = false;
		}
		if (max.HasValue && value.Length > max.Value)
		{
			issues.Add(new ValidationIssue(path2, "too_big", $"at most {max} chars"));
			flag = false;
		}
		if (flag && pattern != null && !pattern.IsMatch(value))
		{
			issues.Add(new ValidationIssue(path2, "invalid_format", "pattern " + pattern));
			flag = false;
		}
		return flag;
	}

	private static bool Enum(JObject parent, string key, string path, List<ValidationIssue> issues, string[] values, out string value)
	{
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Invalid comparison between Unknown and I4
		JToken val = parent[key];
		value = ((val != null && (int)val.Type == 8) ? ((string)val) : null);
		if (value != null && Array.IndexOf(values, value) >= 0)
		{
			return true;
		}
		issues.Add(new ValidationIssue(Join(path, key), "invalid_value", "one of " + string.Join("|", values)));
		return false;
	}

	private static bool Literal(JObject parent, string key, string path, List<ValidationIssue> issues, string expected)
	{
		string value;
		return Enum(parent, key, path, issues, new string[1] { expected }, out value);
	}

	private static bool Bool(JObject parent, string key, string path, List<ValidationIssue> issues)
	{
		bool value;
		return Bool(parent, key, path, issues, out value);
	}

	private static bool Bool(JObject parent, string key, string path, List<ValidationIssue> issues, out bool value)
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Invalid comparison between Unknown and I4
		value = false;
		JToken val = parent[key];
		if (val == null)
		{
			issues.Add(new ValidationIssue(Join(path, key), "invalid_type", "required"));
			return false;
		}
		if ((int)val.Type != 9)
		{
			issues.Add(new ValidationIssue(Join(path, key), "invalid_type", "expected boolean"));
			return false;
		}
		value = (bool)val;
		return true;
	}

	private static void OptionalBool(JObject parent, string key, string path, List<ValidationIssue> issues)
	{
		if (parent[key] != null)
		{
			Bool(parent, key, path, issues);
		}
	}

	private static bool NullablePrefab(JObject parent, string key, string path, List<ValidationIssue> issues, out string value)
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Invalid comparison between Unknown and I4
		value = null;
		JToken val = parent[key];
		if (val == null)
		{
			issues.Add(new ValidationIssue(Join(path, key), "invalid_type", "required (nullable)"));
			return false;
		}
		if ((int)val.Type == 10)
		{
			return true;
		}
		return Str(parent, key, path, issues, 0, null, Prefab, out value);
	}

	private static void NullableInt(JObject parent, string key, string path, List<ValidationIssue> issues, long min, long max)
	{
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Invalid comparison between Unknown and I4
		JToken val = parent[key];
		if (val == null)
		{
			issues.Add(new ValidationIssue(Join(path, key), "invalid_type", "required (nullable)"));
		}
		else if ((int)val.Type != 10)
		{
			Int(parent, key, path, issues, min, max, out var _);
		}
	}

	private static bool NumberToken(JToken t, out double v)
	{
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Invalid comparison between Unknown and I4
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Invalid comparison between Unknown and I4
		v = 0.0;
		if (t == null)
		{
			return false;
		}
		if ((int)t.Type == 6)
		{
			v = (long)t;
			return true;
		}
		if ((int)t.Type == 7)
		{
			v = (double)t;
			if (!double.IsNaN(v))
			{
				return !double.IsInfinity(v);
			}
			return false;
		}
		return false;
	}

	private static bool Int(JObject parent, string key, string path, List<ValidationIssue> issues, long min, long max, out int value)
	{
		value = 0;
		string path2 = Join(path, key);
		JToken val = parent[key];
		if (val == null)
		{
			issues.Add(new ValidationIssue(path2, "invalid_type", "required"));
			return false;
		}
		if (!NumberToken(val, out var v))
		{
			issues.Add(new ValidationIssue(path2, "invalid_type", "expected number"));
			return false;
		}
		if (Math.Floor(v) != v)
		{
			issues.Add(new ValidationIssue(path2, "invalid_type", "expected integer"));
			return false;
		}
		if (v < (double)min)
		{
			issues.Add(new ValidationIssue(path2, "too_small", $">= {min}"));
			return false;
		}
		if (v > (double)max)
		{
			issues.Add(new ValidationIssue(path2, "too_big", $"<= {max}"));
			return false;
		}
		value = (int)v;
		return true;
	}

	private static bool Num(JObject parent, string key, string path, List<ValidationIssue> issues, double min, double max)
	{
		double value;
		return Num(parent, key, path, issues, min, max, out value);
	}

	private static bool Num(JObject parent, string key, string path, List<ValidationIssue> issues, double min, double max, out double value)
	{
		value = 0.0;
		string path2 = Join(path, key);
		JToken val = parent[key];
		if (val == null)
		{
			issues.Add(new ValidationIssue(path2, "invalid_type", "required"));
			return false;
		}
		if (!NumberToken(val, out var v))
		{
			issues.Add(new ValidationIssue(path2, "invalid_type", "expected number"));
			return false;
		}
		if (v < min)
		{
			issues.Add(new ValidationIssue(path2, "too_small", $">= {min}"));
			return false;
		}
		if (v > max)
		{
			issues.Add(new ValidationIssue(path2, "too_big", $"<= {max}"));
			return false;
		}
		value = v;
		return true;
	}

	private static bool NullableNum(JObject parent, string key, string path, List<ValidationIssue> issues, double min, double max)
	{
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Invalid comparison between Unknown and I4
		JToken val = parent[key];
		if (val == null)
		{
			issues.Add(new ValidationIssue(Join(path, key), "invalid_type", "required (nullable)"));
			return false;
		}
		if ((int)val.Type == 10)
		{
			return true;
		}
		Num(parent, key, path, issues, min, max);
		return false;
	}

	private static void SteamId64(JObject parent, string key, string path, List<ValidationIssue> issues)
	{
		if (Str(parent, key, path, issues, 1, null, SteamId, out var value) && BigInteger.Parse(value, CultureInfo.InvariantCulture) < SteamMin)
		{
			issues.Add(new ValidationIssue(Join(path, key), "custom", "SteamID64 must be >= 76561197960265728"));
		}
	}

	private static void Int64Str(JObject parent, string key, string path, List<ValidationIssue> issues)
	{
		Int64Str(parent, key, path, issues, out var _);
	}

	private static bool Int64Str(JObject parent, string key, string path, List<ValidationIssue> issues, out string value)
	{
		if (!Str(parent, key, path, issues, 1, null, Int64, out value))
		{
			return false;
		}
		BigInteger bigInteger = BigInteger.Parse(value, CultureInfo.InvariantCulture);
		if (bigInteger >= I64Min && bigInteger <= I64Max)
		{
			return true;
		}
		issues.Add(new ValidationIssue(Join(path, key), "custom", "outside the signed 64-bit range"));
		return false;
	}

	private static void PrefabList(JObject parent, string key, string path, List<ValidationIssue> issues, int max)
	{
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: Invalid comparison between Unknown and I4
		if (!Arr(parent, key, path, issues, 0, max, out var a))
		{
			return;
		}
		for (int i = 0; i < ((JContainer)a).Count; i++)
		{
			string path2 = Join(path, key) + "." + i;
			if ((int)a[i].Type != 8)
			{
				issues.Add(new ValidationIssue(path2, "invalid_type", "expected string"));
			}
			else if (!Prefab.IsMatch((string)a[i]))
			{
				issues.Add(new ValidationIssue(path2, "invalid_format", "prefab id"));
			}
		}
	}
}

plugins/RlValheimLink/RlValheimLink.Core.dll

Decompiled 13 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RlValheimLink.Contract;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("RlValheimLink.Core")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("RL Valheim Link server-half logic with no Unity or game references: message enrichment, the disk spool with latest-wins dedupe, the upload state machine and the Raid Ledger HTTP client. Unit-tested on plain .NET and driven end to end against tools/mock-ingest.")]
[assembly: AssemblyFileVersion("0.4.1.0")]
[assembly: AssemblyInformationalVersion("0.4.1+ff1cdb186f76b7d41bda6fb07c33795e02baf157")]
[assembly: AssemblyProduct("RlValheimLink.Core")]
[assembly: AssemblyTitle("RlValheimLink.Core")]
[assembly: AssemblyVersion("0.4.1.0")]
namespace RlValheimLink.Core;

public sealed class ScannedStack
{
	public string Prefab;

	public int Quality;

	public int Stack;
}
public sealed class ScannedContainer
{
	public string Prefab;

	public double X;

	public double Y;

	public double Z;

	public long Creator;

	public string Privacy = "public";

	public bool Vehicle;

	public int Width;

	public int Height;

	public int MaxRow = -1;

	public bool Unreadable;

	public List<ScannedStack> Stacks = new List<ScannedStack>();
}
public sealed class ContainersHeader
{
	public string ServerId;

	public string GameVersion;

	public string ModVersion;

	public string WorldUid;

	public DateTime CapturedAtUtc;
}
public sealed class ContainersReport
{
	public int Scanned;

	public int Creator0;

	public int Containers;

	public int Vehicles;

	public int Private;

	public int Unreadable;

	public int Stacks;

	public int Entries;

	public int Merged;

	public int SlotsClamped;

	public int ContainersOverEntries;

	public int EntriesOmitted;

	public int InvalidStacks;

	public int OmittedShared;

	public int OmittedPrivate;

	public bool CapsApplied
	{
		get
		{
			if (SlotsClamped <= 0 && ContainersOverEntries <= 0)
			{
				return OmittedShared + OmittedPrivate > 0;
			}
			return true;
		}
	}
}
public static class ContainersBuilder
{
	private sealed class Acc
	{
		public string Prefab;

		public string CreatorText;

		public string Privacy;

		public double X;

		public double Y;

		public double Z;

		public long Creator;

		public long Slots;

		public int Parts;

		public int Used;

		public bool Vehicle;

		public bool AllUnreadable;

		public readonly Dictionary<string, (string prefab, int quality, long stack)> Sums = new Dictionary<string, (string, int, long)>(StringComparer.Ordinal);
	}

	public static ContainersMessage Build(ContainersHeader header, IEnumerable<ScannedContainer> scanned, out ContainersReport report)
	{
		//IL_036e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0373: Unknown result type (might be due to invalid IL or missing references)
		//IL_0380: Unknown result type (might be due to invalid IL or missing references)
		//IL_038d: Unknown result type (might be due to invalid IL or missing references)
		//IL_039a: Unknown result type (might be due to invalid IL or missing references)
		//IL_03a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_03b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_03c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_03ce: Unknown result type (might be due to invalid IL or missing references)
		//IL_03dd: Expected O, but got Unknown
		//IL_0457: Unknown result type (might be due to invalid IL or missing references)
		//IL_045c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0469: Unknown result type (might be due to invalid IL or missing references)
		//IL_0476: Unknown result type (might be due to invalid IL or missing references)
		//IL_0493: Expected O, but got Unknown
		//IL_066d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0672: Unknown result type (might be due to invalid IL or missing references)
		//IL_067e: Unknown result type (might be due to invalid IL or missing references)
		//IL_068f: Unknown result type (might be due to invalid IL or missing references)
		//IL_069b: Unknown result type (might be due to invalid IL or missing references)
		//IL_06a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_06b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_06ba: Unknown result type (might be due to invalid IL or missing references)
		//IL_06d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_06ea: Expected O, but got Unknown
		report = new ContainersReport();
		Dictionary<string, Acc> dictionary = new Dictionary<string, Acc>(StringComparer.Ordinal);
		List<Acc> list = new List<Acc>();
		foreach (ScannedContainer item2 in scanned)
		{
			if (item2 == null)
			{
				continue;
			}
			report.Scanned++;
			if (item2.Creator == 0L)
			{
				report.Creator0++;
				continue;
			}
			string text = Names.Prefab(item2.Prefab);
			if (text == null)
			{
				report.InvalidStacks += item2.Stacks?.Count ?? 0;
				continue;
			}
			double num = Round(item2.X);
			double num2 = Round(item2.Y);
			double num3 = Round(item2.Z);
			string text2 = item2.Creator.ToString(CultureInfo.InvariantCulture);
			string key = text + "|" + Coord(num) + "|" + Coord(num2) + "|" + Coord(num3) + "|" + text2;
			if (!dictionary.TryGetValue(key, out var value))
			{
				value = (dictionary[key] = new Acc
				{
					Prefab = text,
					X = num,
					Y = num2,
					Z = num3,
					Creator = item2.Creator,
					CreatorText = text2,
					Privacy = Privacy(item2.Privacy),
					AllUnreadable = true
				});
				list.Add(value);
			}
			else
			{
				report.Merged++;
			}
			value.Parts++;
			value.Vehicle |= item2.Vehicle;
			if (Privacy(item2.Privacy) == "private")
			{
				value.Privacy = "private";
			}
			value.Slots += SlotsOf(item2);
			if (item2.Unreadable)
			{
				continue;
			}
			value.AllUnreadable = false;
			foreach (ScannedStack item3 in item2.Stacks ?? new List<ScannedStack>())
			{
				string text3 = ((item3 == null) ? null : Names.Prefab(item3.Prefab));
				if (text3 == null || item3.Stack <= 0)
				{
					report.InvalidStacks++;
					continue;
				}
				value.Used++;
				int item = Names.Clamp(item3.Quality, 1, 10);
				string key2 = text3 + "@" + item.ToString(CultureInfo.InvariantCulture);
				value.Sums.TryGetValue(key2, out (string, int, long) value2);
				value.Sums[key2] = (value2.Item1 ?? text3, item, value2.Item3 + item3.Stack);
			}
		}
		List<ContainerSnapshot> list2 = new List<ContainerSnapshot>(list.Count);
		List<bool> list3 = new List<bool>(list.Count);
		list.Sort(CompareAcc);
		foreach (Acc item4 in list)
		{
			ContainerSnapshot val = new ContainerSnapshot
			{
				Prefab = item4.Prefab,
				X = item4.X,
				Y = item4.Y,
				Z = item4.Z,
				CreatorPlayerId = item4.CreatorText,
				Privacy = item4.Privacy,
				Vehicle = item4.Vehicle,
				Unreadable = item4.AllUnreadable
			};
			long num4 = item4.Slots;
			if (num4 > 4096)
			{
				num4 = 4096L;
				report.SlotsClamped++;
			}
			val.Slots = (int)Math.Max(1L, num4);
			if (!val.Unreadable)
			{
				List<ContainerStack> list4 = new List<ContainerStack>(item4.Sums.Count);
				foreach (var value3 in item4.Sums.Values)
				{
					list4.Add(new ContainerStack
					{
						Prefab = value3.prefab,
						Quality = value3.quality,
						Stack = Names.Clamp(value3.stack, 1, 1000000000)
					});
				}
				if (list4.Count > 256)
				{
					list4.Sort((ContainerStack p, ContainerStack q) => (q.Stack == p.Stack) ? CompareStack(p, q) : q.Stack.CompareTo(p.Stack));
					val.OmittedEntries = list4.Count - 256;
					report.ContainersOverEntries++;
					report.EntriesOmitted += val.OmittedEntries;
					list4.RemoveRange(256, list4.Count - 256);
				}
				list4.Sort(CompareStack);
				val.Items = list4;
				val.UsedSlots = Math.Min(item4.Used, val.Slots);
			}
			list2.Add(val);
			list3.Add(item4.Vehicle);
		}
		int num5 = 0;
		foreach (ContainerSnapshot item5 in list2)
		{
			num5 += item5.Items.Count;
		}
		while (list2.Count > 4096 || num5 > 50000)
		{
			int num6 = list3.LastIndexOf(item: true);
			if (num6 < 0)
			{
				num6 = list2.Count - 1;
			}
			ContainerSnapshot val2 = list2[num6];
			num5 -= val2.Items.Count;
			if (val2.Privacy == "private")
			{
				report.OmittedPrivate++;
			}
			else
			{
				report.OmittedShared++;
			}
			list2.RemoveAt(num6);
			list3.RemoveAt(num6);
		}
		ContainersMessage result = new ContainersMessage
		{
			ServerId = header.ServerId,
			CapturedAt = MessageJson.Timestamp(header.CapturedAtUtc),
			GameVersion = header.GameVersion,
			ModVersion = header.ModVersion,
			WorldUid = header.WorldUid,
			Containers = list2,
			OmittedShared = Math.Min(report.OmittedShared, 1000000),
			OmittedPrivate = Math.Min(report.OmittedPrivate, 1000000)
		};
		foreach (ContainerSnapshot item6 in list2)
		{
			report.Containers++;
			if (item6.Vehicle)
			{
				report.Vehicles++;
			}
			if (item6.Privacy == "private")
			{
				report.Private++;
			}
			if (item6.Unreadable)
			{
				report.Unreadable++;
			}
			report.Stacks += item6.UsedSlots;
			report.Entries += item6.Items.Count;
		}
		return result;
	}

	public static string SpoolKey(string worldUid)
	{
		return "containers|" + worldUid;
	}

	private static long SlotsOf(ScannedContainer c)
	{
		long num = Math.Max(1, c.Width);
		long num2 = Math.Max(1, c.Height);
		return Math.Max(num * num2, num * ((long)c.MaxRow + 1L));
	}

	private static string Privacy(string p)
	{
		if (!(p == "private") && !(p == "group"))
		{
			return "public";
		}
		return p;
	}

	public static double Round(double v)
	{
		if (double.IsNaN(v) || double.IsInfinity(v))
		{
			return 0.0;
		}
		double num = Math.Round(v * 10.0, MidpointRounding.AwayFromZero) / 10.0;
		if (num > 32768.0)
		{
			num = 32768.0;
		}
		if (num < -32768.0)
		{
			num = -32768.0;
		}
		if (num != 0.0)
		{
			return num;
		}
		return 0.0;
	}

	private static string Coord(double v)
	{
		return v.ToString("R", CultureInfo.InvariantCulture);
	}

	private static int CompareStack(ContainerStack a, ContainerStack b)
	{
		int num = string.CompareOrdinal(a.Prefab, b.Prefab);
		if (num == 0)
		{
			return a.Quality.CompareTo(b.Quality);
		}
		return num;
	}

	private static int CompareAcc(Acc a, Acc b)
	{
		int num = string.CompareOrdinal(a.Prefab, b.Prefab);
		if (num == 0)
		{
			num = a.X.CompareTo(b.X);
		}
		if (num == 0)
		{
			num = a.Z.CompareTo(b.Z);
		}
		if (num == 0)
		{
			num = a.Y.CompareTo(b.Y);
		}
		if (num == 0)
		{
			num = a.Creator.CompareTo(b.Creator);
		}
		return num;
	}
}
public sealed class EnrichContext
{
	public string ServerId;

	public string SocketSteamId;

	public bool TrustClientSteamId;

	public long? ZdoPlayerId;

	public string ServerModVersion;

	public DateTime NowUtc = DateTime.UtcNow;

	public int ClockToleranceSeconds = 60;

	public BinaryRef BaseMap;
}
public sealed class EnrichResult
{
	public bool Ok;

	public string Reason;

	public string Kind;

	public string Key;

	public string CapturedAt;

	public string SteamId;

	public string PlayerId;

	public List<string> BlobRefs = new List<string>();

	public List<string> Notes = new List<string>();

	public byte[] Bytes;
}
public static class Enricher
{
	public const string PlaceholderSteamId = "76561197960265728";

	public static EnrichResult Enrich(JObject msg, EnrichContext ctx)
	{
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: Invalid comparison between Unknown and I4
		//IL_0386: Unknown result type (might be due to invalid IL or missing references)
		//IL_038c: Invalid comparison between Unknown and I4
		EnrichResult enrichResult = new EnrichResult();
		if (msg == null)
		{
			enrichResult.Reason = "not a JSON object";
			return enrichResult;
		}
		JToken obj = msg["kind"];
		JToken obj2 = ((obj is JValue) ? obj : null);
		string text = (enrichResult.Kind = ((obj2 != null && (int)obj2.Type == 8) ? ((string)msg["kind"]) : null));
		if (text != "character" && text != "map" && text != "items")
		{
			enrichResult.Reason = "unknown kind";
			return enrichResult;
		}
		msg["serverId"] = JToken.op_Implicit(ctx.ServerId ?? "");
		JToken obj3 = msg["modVersion"];
		string text2 = (string)((obj3 is JValue) ? obj3 : null);
		if (!string.IsNullOrEmpty(ctx.ServerModVersion) && text2 != ctx.ServerModVersion)
		{
			enrichResult.Reason = "modVersion " + text2 + " != server " + ctx.ServerModVersion;
			return enrichResult;
		}
		JToken obj4 = msg["capturedAt"];
		string text3 = (string)((obj4 is JValue) ? obj4 : null);
		DateTime dateTime = default(DateTime);
		if (!MessageJson.TryParseTimestamp(text3 ?? "", ref dateTime) || Math.Abs((dateTime - ctx.NowUtc).TotalSeconds) > (double)ctx.ClockToleranceSeconds)
		{
			text3 = MessageJson.Timestamp(ctx.NowUtc);
			msg["capturedAt"] = JToken.op_Implicit(text3);
			enrichResult.Notes.Add("capturedAt replaced by the server clock (client skew)");
		}
		enrichResult.CapturedAt = text3;
		int num;
		if (text == "character" || text == "map")
		{
			JToken obj5 = msg["character"];
			JObject val = (JObject)(object)((obj5 is JObject) ? obj5 : null);
			if (val == null)
			{
				enrichResult.Reason = "character missing";
				return enrichResult;
			}
			JToken obj6 = val["steamId"];
			string text4 = (string)((obj6 is JValue) ? obj6 : null);
			string text5;
			if (ctx.SocketSteamId != null)
			{
				text5 = ctx.SocketSteamId;
			}
			else
			{
				if (!ctx.TrustClientSteamId || string.IsNullOrEmpty(text4) || !(text4 != "76561197960265728"))
				{
					enrichResult.Reason = "peer identity is not a SteamID64 and TrustClientSteamId is off";
					return enrichResult;
				}
				text5 = text4;
				enrichResult.Notes.Add("steamId taken from the client's claim (non-Steam peer, TrustClientSteamId)");
			}
			if (text4 != null && text4 != text5 && text4 != "76561197960265728")
			{
				enrichResult.Notes.Add("client's steamId claim differed from the socket identity and was replaced");
			}
			val["steamId"] = JToken.op_Implicit(text5);
			enrichResult.SteamId = text5;
			JToken obj7 = val["playerId"];
			string text6 = (string)((obj7 is JValue) ? obj7 : null);
			if (ctx.ZdoPlayerId.HasValue && text6 != ctx.ZdoPlayerId.Value.ToString(CultureInfo.InvariantCulture))
			{
				enrichResult.Reason = $"playerId claim {text6} != the peer's character {ctx.ZdoPlayerId.Value}";
				return enrichResult;
			}
			enrichResult.PlayerId = text6;
			if (!(text == "character"))
			{
				JToken obj8 = val["worldUid"];
				string text7 = (string)((obj8 is JValue) ? obj8 : null);
				enrichResult.Key = "map|" + text5 + "|" + text6 + "|" + text7;
				JToken obj9 = msg["grid"];
				JObject val2 = (JObject)(object)((obj9 is JObject) ? obj9 : null);
				if (val2 != null)
				{
					JToken obj10 = val2["textureSize"];
					JValue val3 = (JValue)(object)((obj10 is JValue) ? obj10 : null);
					if (val3 != null && (int)((JToken)val3).Type == 6)
					{
						num = (int)(JToken)(object)val3;
						goto IL_0398;
					}
				}
				num = -1;
				goto IL_0398;
			}
			enrichResult.Key = "character|" + text5 + "|" + text6;
		}
		else
		{
			ref string key = ref enrichResult.Key;
			JToken obj11 = msg["gameVersion"];
			key = "items|" + (string)((obj11 is JValue) ? obj11 : null);
		}
		goto IL_041f;
		IL_0398:
		int num2 = num;
		if (ctx.BaseMap != null && ctx.BaseMap.Width == num2 && ctx.BaseMap.Height == num2)
		{
			msg["baseMap"] = (JToken)(object)JObject.FromObject((object)ctx.BaseMap);
			enrichResult.Notes.Add("baseMap attached");
		}
		else
		{
			msg["baseMap"] = (JToken)(object)JValue.CreateNull();
		}
		goto IL_041f;
		IL_041f:
		List<ValidationIssue> list = Validator.Validate((JToken)(object)msg);
		if (list.Count > 0)
		{
			enrichResult.Reason = "contract: " + string.Join("; ", list.ConvertAll((ValidationIssue i) => ((object)i).ToString()).ToArray());
			return enrichResult;
		}
		CollectRefs(msg, enrichResult.BlobRefs);
		enrichResult.Bytes = MessageJson.ToBytes((JToken)(object)msg);
		enrichResult.Ok = true;
		return enrichResult;
	}

	public static EnrichResult Seal(LinkMessage message, string serverId)
	{
		EnrichResult enrichResult = new EnrichResult
		{
			Kind = ((message != null) ? message.Kind : null)
		};
		ContainersMessage val = (ContainersMessage)(object)((message is ContainersMessage) ? message : null);
		if (val != null)
		{
			enrichResult.Key = ContainersBuilder.SpoolKey(val.WorldUid);
		}
		else
		{
			if (!(message is RecipesMessage))
			{
				enrichResult.Reason = "not a server-built kind";
				return enrichResult;
			}
			enrichResult.Key = RecipesBuilder.SpoolKey(message.GameVersion);
		}
		message.ServerId = serverId ?? "";
		enrichResult.CapturedAt = message.CapturedAt;
		JObject val2 = MessageJson.ToToken(message);
		List<ValidationIssue> list = Validator.Validate((JToken)(object)val2);
		if (list.Count > 0)
		{
			enrichResult.Reason = "contract: " + string.Join("; ", list.ConvertAll((ValidationIssue i) => ((object)i).ToString()).ToArray());
			return enrichResult;
		}
		CollectRefs(val2, enrichResult.BlobRefs);
		enrichResult.Bytes = MessageJson.ToBytes((JToken)(object)val2);
		enrichResult.Ok = true;
		return enrichResult;
	}

	public static void CollectRefs(JObject msg, List<string> into)
	{
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Invalid comparison between Unknown and I4
		//IL_0108: Unknown result type (might be due to invalid IL or missing references)
		//IL_010e: Invalid comparison between Unknown and I4
		string[] array = new string[2] { "baseMap", "atlas" };
		foreach (string text in array)
		{
			JToken obj = msg[text];
			JObject val = (JObject)(object)((obj is JObject) ? obj : null);
			if (val != null)
			{
				JToken obj2 = val["sha256"];
				JValue val2 = (JValue)(object)((obj2 is JValue) ? obj2 : null);
				if (val2 != null && (int)((JToken)val2).Type == 8)
				{
					into.Add((string)(JToken)(object)val2);
				}
			}
		}
		JToken obj3 = msg["items"];
		JArray val3 = (JArray)(object)((obj3 is JArray) ? obj3 : null);
		if (val3 == null)
		{
			return;
		}
		foreach (JToken item in val3)
		{
			JObject val4 = (JObject)(object)((item is JObject) ? item : null);
			if (val4 == null)
			{
				continue;
			}
			JToken obj4 = val4["icon"];
			JObject val5 = (JObject)(object)((obj4 is JObject) ? obj4 : null);
			if (val5 == null || !((string)(JToken)/*isinst with value type is only supported in some contexts*/ == "file"))
			{
				continue;
			}
			JToken obj5 = val5["blob"];
			JObject val6 = (JObject)(object)((obj5 is JObject) ? obj5 : null);
			if (val6 != null)
			{
				JToken obj6 = val6["sha256"];
				JValue val7 = (JValue)(object)((obj6 is JValue) ? obj6 : null);
				if (val7 != null && (int)((JToken)val7).Type == 8)
				{
					into.Add((string)(JToken)(object)val7);
				}
			}
		}
	}
}
public interface ILinkLog
{
	void Debug(string message);

	void Info(string message);

	void Warn(string message);

	void Error(string message);
}
public static class Names
{
	private static readonly Regex PrefabBad = new Regex("[^A-Za-z0-9_.-]", RegexOptions.CultureInvariant);

	private static readonly Regex IdBad = new Regex("[^A-Za-z0-9_]", RegexOptions.CultureInvariant);

	private static readonly Regex PrefabOk = new Regex("^[A-Za-z0-9_.-]{1,96}\\z", RegexOptions.CultureInvariant);

	public static string Prefab(string raw)
	{
		if (string.IsNullOrEmpty(raw))
		{
			return null;
		}
		string text = raw.Trim();
		if (text.EndsWith("(Clone)", StringComparison.Ordinal))
		{
			text = text.Substring(0, text.Length - 7);
		}
		text = PrefabBad.Replace(text, "_");
		if (text.Length > 96)
		{
			text = text.Substring(0, 96);
		}
		if (text.Length != 0)
		{
			return text;
		}
		return null;
	}

	public static bool IsPrefab(string s)
	{
		if (s != null)
		{
			return PrefabOk.IsMatch(s);
		}
		return false;
	}

	public static string Identifier(string raw, int max)
	{
		if (string.IsNullOrEmpty(raw))
		{
			return null;
		}
		string text = IdBad.Replace(raw, "_");
		if (text.Length > max)
		{
			text = text.Substring(0, max);
		}
		if (text.Length != 0)
		{
			return text;
		}
		return null;
	}

	public static string Clip(string s, int max)
	{
		if (s == null)
		{
			return "";
		}
		if (s.Length <= max)
		{
			return s;
		}
		int num = max;
		if (char.IsHighSurrogate(s[num - 1]))
		{
			num--;
		}
		return s.Substring(0, num);
	}

	public static int Clamp(long v, int min, int max)
	{
		if (v >= min)
		{
			if (v <= max)
			{
				return (int)v;
			}
			return max;
		}
		return min;
	}

	public static double Clamp(double v, double min, double max)
	{
		if (!double.IsNaN(v))
		{
			if (!(v < min))
			{
				if (!(v > max))
				{
					return v;
				}
				return max;
			}
			return min;
		}
		return min;
	}
}
public sealed class PieceIconMap
{
	public const int MaxIcons = 8192;

	private static readonly Regex GameVersionPattern = new Regex("^\\d{1,4}(\\.\\d{1,5}){1,3}\\z", RegexOptions.CultureInvariant);

	private static readonly Regex Sha256 = new Regex("^[0-9a-f]{64}\\z", RegexOptions.CultureInvariant);

	public string GameVersion;

	public BinaryRef Atlas;

	public SortedDictionary<string, int[]> Icons = new SortedDictionary<string, int[]>(StringComparer.Ordinal);

	public string ServerModSet;

	public int Rejected;

	public string Sha12
	{
		get
		{
			BinaryRef atlas = Atlas;
			if (((atlas != null) ? atlas.Sha256 : null) != null)
			{
				return Atlas.Sha256.Substring(0, Math.Min(12, Atlas.Sha256.Length));
			}
			return "?";
		}
	}

	public static bool TryParse(string json, out PieceIconMap map, out string error)
	{
		//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b5: 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_00ea: 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_011f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0143: Expected O, but got Unknown
		map = null;
		error = null;
		JObject val;
		try
		{
			JToken obj = MessageJson.Parse(json);
			val = (JObject)(object)((obj is JObject) ? obj : null);
		}
		catch (Exception ex)
		{
			error = "not JSON: " + ex.Message;
			return false;
		}
		if (val == null)
		{
			error = "not a JSON object";
			return false;
		}
		PieceIconMap pieceIconMap = new PieceIconMap
		{
			GameVersion = Str(val["gameVersion"]),
			ServerModSet = Str(val["serverModSet"])
		};
		if (pieceIconMap.GameVersion == null || !GameVersionPattern.IsMatch(pieceIconMap.GameVersion))
		{
			error = "gameVersion missing or malformed";
			return false;
		}
		JToken obj2 = val["atlas"];
		JObject val2 = (JObject)(object)((obj2 is JObject) ? obj2 : null);
		if (val2 == null)
		{
			error = "atlas missing";
			return false;
		}
		pieceIconMap.Atlas = new BinaryRef
		{
			Sha256 = Str(val2["sha256"]),
			ByteLength = Int(val2["byteLength"]).GetValueOrDefault(),
			ContentType = Str(val2["contentType"]),
			Width = Int(val2["width"]).GetValueOrDefault(),
			Height = Int(val2["height"]).GetValueOrDefault()
		};
		if (pieceIconMap.Atlas.Sha256 == null || !Sha256.IsMatch(pieceIconMap.Atlas.Sha256))
		{
			error = "atlas.sha256 is not 64 lowercase hex";
			return false;
		}
		if (pieceIconMap.Atlas.ContentType != "image/png")
		{
			error = "atlas.contentType is not image/png";
			return false;
		}
		if (pieceIconMap.Atlas.ByteLength < 1 || pieceIconMap.Atlas.ByteLength > 16777216)
		{
			error = "atlas.byteLength out of range";
			return false;
		}
		if (pieceIconMap.Atlas.Width < 1 || pieceIconMap.Atlas.Width > 16384 || pieceIconMap.Atlas.Height < 1 || pieceIconMap.Atlas.Height > 16384)
		{
			error = "atlas size out of range";
			return false;
		}
		JToken obj3 = val["icons"];
		JObject val3 = (JObject)(object)((obj3 is JObject) ? obj3 : null);
		if (val3 == null)
		{
			error = "icons missing";
			return false;
		}
		if (((JContainer)val3).Count > 8192)
		{
			error = $"more than {8192} icons";
			return false;
		}
		foreach (JProperty item in val3.Properties())
		{
			JToken value = item.Value;
			JArray val4 = (JArray)(object)((value is JArray) ? value : null);
			int?[] array = ((val4 == null || ((JContainer)val4).Count != 4) ? null : new int?[4]
			{
				Int(val4[0]),
				Int(val4[1]),
				Int(val4[2]),
				Int(val4[3])
			});
			if (!Names.IsPrefab(item.Name) || array == null || !array[0].HasValue || !array[1].HasValue || !array[2].HasValue || !array[3].HasValue || !(array[0] >= 0) || !(array[0] <= 16383) || !(array[1] >= 0) || !(array[1] <= 16383) || !(array[2] >= 1) || !(array[2] <= 512) || !(array[3] >= 1) || !(array[3] <= 512) || !(array[0] + array[2] <= pieceIconMap.Atlas.Width) || !(array[1] + array[3] <= pieceIconMap.Atlas.Height))
			{
				pieceIconMap.Rejected++;
				continue;
			}
			pieceIconMap.Icons[item.Name] = new int[4]
			{
				array[0].Value,
				array[1].Value,
				array[2].Value,
				array[3].Value
			};
		}
		if (pieceIconMap.Icons.Count == 0)
		{
			error = "no usable icon";
			return false;
		}
		map = pieceIconMap;
		return true;
	}

	public static bool TryParse(byte[] utf8, out PieceIconMap map, out string error)
	{
		try
		{
			return TryParse(MessageJson.Utf8NoBom.GetString(utf8), out map, out error);
		}
		catch (Exception ex)
		{
			map = null;
			error = "not UTF-8: " + ex.Message;
			return false;
		}
	}

	public string ToJson()
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Expected O, but got Unknown
		//IL_006e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0078: Expected O, but got Unknown
		//IL_0091: Unknown result type (might be due to invalid IL or missing references)
		//IL_0096: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cf: Expected O, but got Unknown
		JObject val = new JObject();
		foreach (KeyValuePair<string, int[]> icon in Icons)
		{
			val[icon.Key] = (JToken)new JArray(new object[4]
			{
				icon.Value[0],
				icon.Value[1],
				icon.Value[2],
				icon.Value[3]
			});
		}
		JObject val2 = new JObject
		{
			["gameVersion"] = JToken.op_Implicit(GameVersion),
			["atlas"] = (JToken)(object)JObject.FromObject((object)Atlas),
			["icons"] = (JToken)(object)val
		};
		if (ServerModSet != null)
		{
			val2["serverModSet"] = JToken.op_Implicit(ServerModSet);
		}
		return ((JToken)val2).ToString((Formatting)0, Array.Empty<JsonConverter>());
	}

	public static int Apply(RecipesMessage msg, PieceIconMap map, out int candidates)
	{
		//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
		//IL_0105: Unknown result type (might be due to invalid IL or missing references)
		//IL_0116: Unknown result type (might be due to invalid IL or missing references)
		//IL_012c: Expected O, but got Unknown
		candidates = msg.Stations.Count + msg.Pieces.Count;
		if (map == null || map.Atlas == null || map.Icons.Count == 0)
		{
			return 0;
		}
		int num = 0;
		foreach (CraftingStationDef station in msg.Stations)
		{
			if (Lookup(map, station.Prefab, out var icon))
			{
				station.Icon = icon;
				num++;
			}
		}
		foreach (PieceDef piece in msg.Pieces)
		{
			if (Lookup(map, piece.Prefab, out var icon2))
			{
				piece.Icon = icon2;
				num++;
			}
		}
		if (num > 0)
		{
			msg.Atlas = new BinaryRef
			{
				Sha256 = map.Atlas.Sha256,
				ByteLength = map.Atlas.ByteLength,
				ContentType = "image/png",
				Width = map.Atlas.Width,
				Height = map.Atlas.Height
			};
		}
		return num;
	}

	private static bool Lookup(PieceIconMap map, string prefab, out AtlasIcon icon)
	{
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_0043: Expected O, but got Unknown
		icon = null;
		if (prefab == null || !map.Icons.TryGetValue(prefab, out var value))
		{
			return false;
		}
		icon = new AtlasIcon
		{
			X = value[0],
			Y = value[1],
			W = value[2],
			H = value[3]
		};
		return true;
	}

	private static string Str(JToken t)
	{
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Invalid comparison between Unknown and I4
		JValue val = (JValue)(object)((t is JValue) ? t : null);
		if (val == null || (int)((JToken)val).Type != 8)
		{
			return null;
		}
		return (string)(JToken)(object)val;
	}

	private static int? Int(JToken t)
	{
		JValue val = (JValue)(object)((t is JValue) ? t : null);
		if (val == null || !(val.Value is long num) || num < int.MinValue || num > int.MaxValue)
		{
			return null;
		}
		return (int)num;
	}
}
public sealed class UploadResult
{
	public int StatusCode;

	public string Code;

	public string Message;

	public int RetryAfterSeconds;

	public string Issues;

	public bool TransportError => StatusCode == 0;

	public bool Success
	{
		get
		{
			if (StatusCode >= 200)
			{
				return StatusCode < 300;
			}
			return false;
		}
	}

	public override string ToString()
	{
		if (StatusCode != 0)
		{
			return string.Format("{0} {1}{2}{3}", StatusCode, Code, string.IsNullOrEmpty(Message) ? "" : (" " + Message), string.IsNullOrEmpty(Issues) ? "" : (" [" + Issues + "]"));
		}
		return "transport error: " + Message;
	}
}
public sealed class ClientSettings
{
	public string BaseUrl;

	public string MessagesPath = "/api/plugins/valheim/link/messages/{kind}";

	public string BlobsPath = "/api/plugins/valheim/link/blobs/{sha256}";

	public bool Gzip = true;

	public TimeSpan MessageTimeout = TimeSpan.FromSeconds(30.0);

	public TimeSpan BlobTimeout = TimeSpan.FromSeconds(120.0);

	public string UserAgent = "RLValheimLink";
}
public sealed class RaidLedgerClient : IDisposable
{
	private readonly HttpClient _http;

	private readonly ClientSettings _s;

	private readonly Func<string> _token;

	public RaidLedgerClient(ClientSettings settings, Func<string> token, HttpMessageHandler handler = null)
	{
		_s = settings;
		_token = token;
		try
		{
			ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
		}
		catch (NotSupportedException)
		{
		}
		_http = ((handler == null) ? new HttpClient() : new HttpClient(handler));
		_http.Timeout = Timeout.InfiniteTimeSpan;
		_http.DefaultRequestHeaders.UserAgent.ParseAdd(_s.UserAgent);
	}

	public string MessageUrl(string kind)
	{
		return _s.BaseUrl.TrimEnd(new char[1] { '/' }) + _s.MessagesPath.Replace("{kind}", kind);
	}

	public string BlobUrl(string sha256)
	{
		return _s.BaseUrl.TrimEnd(new char[1] { '/' }) + _s.BlobsPath.Replace("{sha256}", sha256);
	}

	public Task<UploadResult> PostMessageAsync(string kind, byte[] json, CancellationToken ct = default(CancellationToken))
	{
		HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, MessageUrl(kind));
		byte[] content = json;
		if (_s.Gzip)
		{
			using MemoryStream memoryStream = new MemoryStream();
			using (GZipStream gZipStream = new GZipStream(memoryStream, CompressionLevel.Optimal, leaveOpen: true))
			{
				gZipStream.Write(json, 0, json.Length);
			}
			content = memoryStream.ToArray();
		}
		ByteArrayContent byteArrayContent = new ByteArrayContent(content);
		byteArrayContent.Headers.ContentType = new MediaTypeHeaderValue("application/json")
		{
			CharSet = "utf-8"
		};
		if (_s.Gzip)
		{
			byteArrayContent.Headers.ContentEncoding.Add("gzip");
		}
		httpRequestMessage.Content = byteArrayContent;
		return SendAsync(httpRequestMessage, _s.MessageTimeout, ct);
	}

	public Task<UploadResult> PutBlobAsync(string sha256, byte[] png, CancellationToken ct = default(CancellationToken))
	{
		HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Put, BlobUrl(sha256));
		ByteArrayContent byteArrayContent = new ByteArrayContent(png);
		byteArrayContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
		httpRequestMessage.Content = byteArrayContent;
		return SendAsync(httpRequestMessage, _s.BlobTimeout, ct);
	}

	private async Task<UploadResult> SendAsync(HttpRequestMessage req, TimeSpan timeout, CancellationToken ct)
	{
		string parameter = _token() ?? "";
		req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", parameter);
		using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(new CancellationToken[1] { ct });
		cts.CancelAfter(timeout);
		try
		{
			using HttpResponseMessage res = await _http.SendAsync(req, HttpCompletionOption.ResponseContentRead, cts.Token).ConfigureAwait(continueOnCapturedContext: false);
			UploadResult r = new UploadResult
			{
				StatusCode = (int)res.StatusCode
			};
			if (res.Headers.RetryAfter != null && res.Headers.RetryAfter.Delta.HasValue)
			{
				r.RetryAfterSeconds = (int)Math.Ceiling(res.Headers.RetryAfter.Delta.Value.TotalSeconds);
			}
			string text = ((res.Content != null) ? (await res.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false)) : "");
			ParseBody(text, r);
			return r;
		}
		catch (OperationCanceledException) when (!ct.IsCancellationRequested)
		{
			return new UploadResult
			{
				StatusCode = 0,
				Code = "timeout",
				Message = $"no response within {timeout.TotalSeconds:0}s"
			};
		}
		catch (Exception e)
		{
			return new UploadResult
			{
				StatusCode = 0,
				Code = "network",
				Message = Truncate(Innermost(e).Message, 200)
			};
		}
	}

	private static void ParseBody(string text, UploadResult r)
	{
		if (string.IsNullOrWhiteSpace(text))
		{
			r.Code = (r.Success ? "ok" : "no_body");
			return;
		}
		try
		{
			JObject val = JObject.Parse(text);
			ref string code = ref r.Code;
			JToken obj = val["status"];
			code = ((string)((obj is JValue) ? obj : null)) ?? ((string)(JToken)/*isinst with value type is only supported in some contexts*/) ?? ((string)(JToken)/*isinst with value type is only supported in some contexts*/) ?? (r.Success ? "ok" : "unknown");
			ref string message = ref r.Message;
			JToken obj2 = val["message"];
			message = Truncate((string)((obj2 is JValue) ? obj2 : null), 200);
			JToken obj3 = val["issues"];
			JArray val2 = (JArray)(object)((obj3 is JArray) ? obj3 : null);
			if (val2 != null)
			{
				List<string> list = new List<string>();
				foreach (JToken item in val2)
				{
					JObject val3 = (JObject)(object)((item is JObject) ? item : null);
					if (val3 != null)
					{
						list.Add((string)val3["path"] + ":" + (string)val3["code"]);
					}
				}
				if (list.Count > 0)
				{
					r.Issues = string.Join(", ", list.ToArray());
				}
			}
			JToken obj4 = val["missingBlobs"];
			JArray val4 = (JArray)(object)((obj4 is JArray) ? obj4 : null);
			if (val4 != null && ((JContainer)val4).Count > 0)
			{
				r.Message += $" missingBlobs={((JContainer)val4).Count}";
			}
		}
		catch (Exception)
		{
			r.Code = (r.Success ? "ok" : "unparseable");
			r.Message = Truncate(text, 120);
		}
	}

	private static Exception Innermost(Exception e)
	{
		while (e.InnerException != null)
		{
			e = e.InnerException;
		}
		return e;
	}

	private static string Truncate(string s, int max)
	{
		if (s != null)
		{
			if (s.Length > max)
			{
				return s.Substring(0, max) + "…";
			}
			return s;
		}
		return null;
	}

	public void Dispose()
	{
		_http.Dispose();
	}
}
public sealed class RecipesReport
{
	public int Stations;

	public int Recipes;

	public int Pieces;

	public int Processors;

	public int Processes;

	public int Entries;

	public int CustomFormula;

	public int RenamedIds;

	public int MergedResources;

	public int DanglingStations;

	public int SplitProcesses;

	public int DroppedRecipes;

	public int DroppedPieces;

	public int DroppedProcessors;

	public int DroppedProcesses;

	public int DroppedStations;

	public int DroppedResources;

	public int DroppedInputs;

	public bool Dropped => DroppedRecipes + DroppedPieces + DroppedProcessors + DroppedProcesses + DroppedStations + DroppedResources + DroppedInputs > 0;
}
public static class RecipesBuilder
{
	public static RecipesMessage Normalise(RecipesMessage m, out RecipesReport report)
	{
		//IL_0861: Unknown result type (might be due to invalid IL or missing references)
		//IL_0866: Unknown result type (might be due to invalid IL or missing references)
		//IL_086e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0891: Expected O, but got Unknown
		//IL_09a9: Unknown result type (might be due to invalid IL or missing references)
		//IL_09ae: Unknown result type (might be due to invalid IL or missing references)
		//IL_09b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_09be: Unknown result type (might be due to invalid IL or missing references)
		//IL_09cb: Unknown result type (might be due to invalid IL or missing references)
		//IL_09d8: Unknown result type (might be due to invalid IL or missing references)
		//IL_09e5: Unknown result type (might be due to invalid IL or missing references)
		//IL_09f4: Expected O, but got Unknown
		report = new RecipesReport();
		RecipesReport recipesReport = report;
		string text = Names.Identifier(m.Locale, 32);
		m.Locale = ((text != null && text.Length >= 2 && text.IndexOfAny("0123456789".ToCharArray()) < 0) ? text : "English");
		List<CraftingStationDef> list = new List<CraftingStationDef>();
		HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
		foreach (CraftingStationDef item in m.Stations ?? new List<CraftingStationDef>())
		{
			string text2 = ((item == null) ? null : Names.Prefab(item.Prefab));
			if (text2 == null || !hashSet.Add(text2))
			{
				recipesReport.DroppedStations++;
				continue;
			}
			item.Prefab = text2;
			item.Name = Name(item.Name, text2);
			item.MaxLevel = Names.Clamp(item.MaxLevel, 1, 20);
			list.Add(item);
		}
		list.Sort((CraftingStationDef a, CraftingStationDef b) => string.CompareOrdinal(a.Prefab, b.Prefab));
		Cap(list, 256, ref recipesReport.DroppedStations);
		hashSet = new HashSet<string>(list.Select((CraftingStationDef s) => s.Prefab), StringComparer.Ordinal);
		List<RecipeDef> list2 = new List<RecipeDef>();
		HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal);
		List<RecipeDef> list3 = new List<RecipeDef>();
		foreach (RecipeDef item2 in m.Recipes ?? new List<RecipeDef>())
		{
			if (item2 != null)
			{
				item2.Result = Names.Prefab(item2.Result);
				if (item2.Result == null)
				{
					recipesReport.DroppedRecipes++;
				}
				else if (Names.IsPrefab(item2.Id) && hashSet2.Add(item2.Id))
				{
					list2.Add(item2);
				}
				else
				{
					list3.Add(item2);
				}
			}
		}
		foreach (RecipeDef item3 in list3)
		{
			string result = item3.Result;
			int num = 1;
			string text4;
			do
			{
				string text3 = "." + num.ToString(CultureInfo.InvariantCulture);
				text4 = ((result.Length + text3.Length > 96) ? result.Substring(0, 96 - text3.Length) : result) + text3;
				num++;
			}
			while (!hashSet2.Add(text4));
			item3.Id = text4;
			recipesReport.RenamedIds++;
			list2.Add(item3);
		}
		foreach (RecipeDef item4 in list2)
		{
			item4.Amount = Names.Clamp(item4.Amount, 1, 10000);
			item4.Station = StationRef(item4.Station, hashSet, recipesReport);
			item4.RepairStation = StationRef(item4.RepairStation, hashSet, recipesReport);
			item4.MinStationLevel = Names.Clamp(item4.MinStationLevel, 0, 20);
			item4.QualityResultAmountMultiplier = Names.Clamp(item4.QualityResultAmountMultiplier, 0.0, 100.0);
			if (item4.Formula != "custom")
			{
				item4.Formula = "vanilla";
			}
			item4.Resources = RecipeResources(item4.Resources, recipesReport);
		}
		list2.Sort((RecipeDef a, RecipeDef b) => string.CompareOrdinal(a.Id, b.Id));
		Cap(list2, 4096, ref recipesReport.DroppedRecipes);
		List<PieceDef> list4 = new List<PieceDef>();
		HashSet<string> hashSet3 = new HashSet<string>(StringComparer.Ordinal);
		foreach (PieceDef item5 in m.Pieces ?? new List<PieceDef>())
		{
			string text5 = ((item5 == null) ? null : Names.Prefab(item5.Prefab));
			string text6 = ((item5 == null) ? null : Names.Prefab(item5.Tool));
			if (text5 == null || text6 == null || !hashSet3.Add(text5))
			{
				recipesReport.DroppedPieces++;
				continue;
			}
			item5.Prefab = text5;
			item5.Tool = text6;
			item5.Name = Name(item5.Name, text5);
			item5.Category = Names.Identifier(item5.Category, 48) ?? "Misc";
			item5.Station = StationRef(item5.Station, hashSet, recipesReport);
			item5.Resources = PieceResources(item5.Resources, recipesReport);
			list4.Add(item5);
		}
		list4.Sort((PieceDef a, PieceDef b) => string.CompareOrdinal(a.Prefab, b.Prefab));
		Cap(list4, 4096, ref recipesReport.DroppedPieces);
		Dictionary<string, ProcessorDef> dictionary = new Dictionary<string, ProcessorDef>(StringComparer.Ordinal);
		foreach (ProcessorDef item6 in m.Processors ?? new List<ProcessorDef>())
		{
			string text7 = ((item6 == null) ? null : Names.Prefab(item6.Prefab));
			if (text7 == null || dictionary.ContainsKey(text7) || !IsProcessorType(item6.Type))
			{
				recipesReport.DroppedProcessors++;
				continue;
			}
			item6.Prefab = text7;
			item6.Fuel = Names.Prefab(item6.Fuel);
			item6.FuelPerProduct = (item6.FuelPerProduct.HasValue ? new int?(Names.Clamp(item6.FuelPerProduct.Value, 0, 1000)) : ((int?)null));
			item6.FuelSecondsPerUnit = ((item6.FuelSecondsPerUnit.HasValue && !double.IsNaN(item6.FuelSecondsPerUnit.Value)) ? new double?(Names.Clamp(item6.FuelSecondsPerUnit.Value, 0.001, 86400.0)) : ((double?)null));
			item6.MaxInput = ((item6.MaxInput.HasValue && item6.MaxInput.Value >= 1) ? new int?(Math.Min(item6.MaxInput.Value, 10000)) : ((int?)null));
			item6.BurntResult = Names.Prefab(item6.BurntResult);
			if (item6.DefaultResult != null)
			{
				item6.DefaultResult.Prefab = Names.Prefab(item6.DefaultResult.Prefab);
				item6.DefaultResult = ((item6.DefaultResult.Prefab == null) ? null : item6.DefaultResult);
				if (item6.DefaultResult != null)
				{
					item6.DefaultResult.PerInputs = Names.Clamp(item6.DefaultResult.PerInputs, 1, 10000);
				}
			}
			dictionary[text7] = item6;
		}
		List<ProcessDef> list5 = new List<ProcessDef>();
		HashSet<string> hashSet4 = new HashSet<string>(StringComparer.Ordinal);
		foreach (ProcessDef item7 in m.Processes ?? new List<ProcessDef>())
		{
			string text8 = ((item7 == null) ? null : Names.Prefab(item7.Processor));
			string text9 = ((((item7 != null) ? item7.Output : null) == null) ? null : Names.Prefab(item7.Output.Prefab));
			if (text8 == null || text9 == null || !dictionary.ContainsKey(text8))
			{
				recipesReport.DroppedProcesses++;
				continue;
			}
			List<PrefabAmount> list6 = Inputs(item7.Inputs);
			if (list6.Count == 0)
			{
				recipesReport.DroppedProcesses++;
				continue;
			}
			item7.Processor = text8;
			item7.Output = new PrefabAmount
			{
				Prefab = text9,
				Amount = Names.Clamp(item7.Output.Amount, 1, 10000)
			};
			item7.Seconds = ((item7.Seconds.HasValue && !double.IsNaN(item7.Seconds.Value)) ? new double?(Names.Clamp(item7.Seconds.Value, 0.0, 604800.0)) : ((double?)null));
			item7.Priority = Names.Clamp(item7.Priority, -10000, 10000);
			List<List<PrefabAmount>> list7 = new List<List<PrefabAmount>>();
			if (list6.Count <= 8)
			{
				list7.Add(list6);
			}
			else
			{
				if (!item7.AnyOneOf)
				{
					recipesReport.DroppedProcesses++;
					continue;
				}
				for (int num2 = 0; num2 < list6.Count; num2 += 8)
				{
					list7.Add(list6.GetRange(num2, Math.Min(8, list6.Count - num2)));
				}
				recipesReport.SplitProcesses++;
			}
			foreach (List<PrefabAmount> item8 in list7)
			{
				ProcessDef val = new ProcessDef
				{
					Processor = text8,
					Inputs = item8,
					AnyOneOf = item7.AnyOneOf,
					Output = item7.Output,
					Seconds = item7.Seconds,
					Priority = item7.Priority
				};
				if (hashSet4.Add(ProcessKey(val)))
				{
					list5.Add(val);
				}
			}
		}
		list5.Sort(CompareProcess);
		Cap(list5, 2048, ref recipesReport.DroppedProcesses);
		HashSet<string> used = new HashSet<string>(list5.Select((ProcessDef p) => p.Processor), StringComparer.Ordinal);
		List<ProcessorDef> list8 = dictionary.Values.Where((ProcessorDef p) => used.Contains(p.Prefab)).ToList();
		recipesReport.DroppedProcessors += dictionary.Count - list8.Count;
		list8.Sort((ProcessorDef a, ProcessorDef b) => string.CompareOrdinal(a.Prefab, b.Prefab));
		if (list8.Count > 256)
		{
			Cap(list8, 256, ref recipesReport.DroppedProcessors);
			HashSet<string> kept = new HashSet<string>(list8.Select((ProcessorDef p) => p.Prefab), StringComparer.Ordinal);
			recipesReport.DroppedProcesses += list5.RemoveAll((ProcessDef p) => !kept.Contains(p.Processor));
		}
		int num3 = list2.Sum((RecipeDef x) => x.Resources.Count) + list4.Sum((PieceDef x) => x.Resources.Count) + list5.Sum((ProcessDef x) => x.Inputs.Count);
		while (num3 > 12000)
		{
			if (list4.Count > 0)
			{
				num3 -= list4[list4.Count - 1].Resources.Count;
				list4.RemoveAt(list4.Count - 1);
				recipesReport.DroppedPieces++;
			}
			else if (list2.Count > 0)
			{
				num3 -= list2[list2.Count - 1].Resources.Count;
				list2.RemoveAt(list2.Count - 1);
				recipesReport.DroppedRecipes++;
			}
			else
			{
				num3 -= list5[list5.Count - 1].Inputs.Count;
				list5.RemoveAt(list5.Count - 1);
				recipesReport.DroppedProcesses++;
			}
		}
		m.Stations = list;
		m.Recipes = list2;
		m.Pieces = list4;
		m.Processors = list8;
		m.Processes = list5;
		if (m.Atlas == null)
		{
			foreach (CraftingStationDef item9 in list)
			{
				item9.Icon = null;
			}
			foreach (PieceDef item10 in list4)
			{
				item10.Icon = null;
			}
		}
		recipesReport.Stations = list.Count;
		recipesReport.Recipes = list2.Count;
		recipesReport.Pieces = list4.Count;
		recipesReport.Processors = list8.Count;
		recipesReport.Processes = list5.Count;
		recipesReport.Entries = num3;
		recipesReport.CustomFormula = list2.Count((RecipeDef x) => x.Formula == "custom");
		return m;
	}

	public static string SpoolKey(string gameVersion)
	{
		return "recipes|" + gameVersion;
	}

	private static string Name(string raw, string prefab)
	{
		string text = (raw ?? "").Trim();
		if (text.Length == 0 || text.StartsWith("$", StringComparison.Ordinal) || text.StartsWith("[", StringComparison.Ordinal))
		{
			text = prefab;
		}
		return Names.Clip(text, 96);
	}

	private static string StationRef(string raw, HashSet<string> stations, RecipesReport r)
	{
		string text = Names.Prefab(raw);
		if (text == null)
		{
			return null;
		}
		if (stations.Contains(text))
		{
			return text;
		}
		r.DanglingStations++;
		return null;
	}

	private static List<RecipeResource> RecipeResources(List<RecipeResource> raw, RecipesReport r)
	{
		List<RecipeResource> list = new List<RecipeResource>();
		Dictionary<string, RecipeResource> dictionary = new Dictionary<string, RecipeResource>(StringComparer.Ordinal);
		foreach (RecipeResource item in raw ?? new List<RecipeResource>())
		{
			string text = ((item == null) ? null : Names.Prefab(item.Prefab));
			RecipeResource value;
			if (text == null)
			{
				r.DroppedResources++;
			}
			else if (dictionary.TryGetValue(text, out value))
			{
				value.Amount = Names.Clamp((long)value.Amount + (long)item.Amount, 0, 10000);
				value.AmountPerLevel = Names.Clamp((long)value.AmountPerLevel + (long)item.AmountPerLevel, 0, 10000);
				value.ExtraAmountOnlyOneIngredient = Names.Clamp((long)value.ExtraAmountOnlyOneIngredient + (long)item.ExtraAmountOnlyOneIngredient, 0, 10000);
				RecipeResource obj = value;
				obj.Recover |= item.Recover;
				if (item.UpgraderResource.HasValue)
				{
					value.UpgraderResource = (value.UpgraderResource == true) | item.UpgraderResource.Value;
				}
				r.MergedResources++;
			}
			else
			{
				item.Prefab = text;
				item.Amount = Names.Clamp(item.Amount, 0, 10000);
				item.AmountPerLevel = Names.Clamp(item.AmountPerLevel, 0, 10000);
				item.ExtraAmountOnlyOneIngredient = Names.Clamp(item.ExtraAmountOnlyOneIngredient, 0, 10000);
				dictionary[text] = item;
				list.Add(item);
			}
		}
		if (list.Count > 16)
		{
			r.DroppedResources += list.Count - 16;
			list.RemoveRange(16, list.Count - 16);
		}
		return list;
	}

	private static List<PieceResource> PieceResources(List<PieceResource> raw, RecipesReport r)
	{
		List<PieceResource> list = new List<PieceResource>();
		Dictionary<string, PieceResource> dictionary = new Dictionary<string, PieceResource>(StringComparer.Ordinal);
		foreach (PieceResource item in raw ?? new List<PieceResource>())
		{
			string text = ((item == null) ? null : Names.Prefab(item.Prefab));
			PieceResource value;
			if (text == null)
			{
				r.DroppedResources++;
			}
			else if (dictionary.TryGetValue(text, out value))
			{
				value.Amount = Names.Clamp((long)value.Amount + (long)item.Amount, 0, 10000);
				PieceResource obj = value;
				obj.Recover |= item.Recover;
				r.MergedResources++;
			}
			else
			{
				item.Prefab = text;
				item.Amount = Names.Clamp(item.Amount, 0, 10000);
				dictionary[text] = item;
				list.Add(item);
			}
		}
		if (list.Count > 16)
		{
			r.DroppedResources += list.Count - 16;
			list.RemoveRange(16, list.Count - 16);
		}
		return list;
	}

	private static List<PrefabAmount> Inputs(List<PrefabAmount> raw)
	{
		//IL_0075: Unknown result type (might be due to invalid IL or missing references)
		//IL_007a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0082: Unknown result type (might be due to invalid IL or missing references)
		//IL_009c: Expected O, but got Unknown
		List<PrefabAmount> list = new List<PrefabAmount>();
		Dictionary<string, PrefabAmount> dictionary = new Dictionary<string, PrefabAmount>(StringComparer.Ordinal);
		foreach (PrefabAmount item2 in raw ?? new List<PrefabAmount>())
		{
			string text = ((item2 == null) ? null : Names.Prefab(item2.Prefab));
			if (text != null)
			{
				if (dictionary.TryGetValue(text, out var value))
				{
					value.Amount = Names.Clamp((long)value.Amount + (long)item2.Amount, 1, 10000);
					continue;
				}
				PrefabAmount item = (dictionary[text] = new PrefabAmount
				{
					Prefab = text,
					Amount = Names.Clamp(item2.Amount, 1, 10000)
				});
				list.Add(item);
			}
		}
		return list;
	}

	private static bool IsProcessorType(string t)
	{
		switch (t)
		{
		default:
			return t == "incinerator";
		case "smelter":
		case "fermenter":
		case "cooking":
			return true;
		}
	}

	private static string ProcessKey(ProcessDef p)
	{
		return p.Processor + "|" + string.Join("+", p.Inputs.Select((PrefabAmount i) => i.Prefab + "x" + i.Amount.ToString(CultureInfo.InvariantCulture))) + "|" + p.AnyOneOf + "|" + p.Output.Prefab + "x" + p.Output.Amount.ToString(CultureInfo.InvariantCulture) + "|" + p.Priority.ToString(CultureInfo.InvariantCulture);
	}

	private static int CompareProcess(ProcessDef a, ProcessDef b)
	{
		int num = string.CompareOrdinal(a.Processor, b.Processor);
		if (num == 0)
		{
			num = b.Priority.CompareTo(a.Priority);
		}
		if (num == 0)
		{
			num = string.CompareOrdinal(a.Inputs[0].Prefab, b.Inputs[0].Prefab);
		}
		if (num == 0)
		{
			num = string.CompareOrdinal(a.Output.Prefab, b.Output.Prefab);
		}
		if (num == 0)
		{
			num = a.Inputs.Count.CompareTo(b.Inputs.Count);
		}
		return num;
	}

	private static void Cap<T>(List<T> list, int max, ref int dropped)
	{
		if (list.Count > max)
		{
			dropped += list.Count - max;
			list.RemoveRange(max, list.Count - max);
		}
	}
}
public sealed class ScanSchedule
{
	private readonly TimeSpan _interval;

	public DateTime NextAt { get; private set; }

	public TimeSpan Interval => _interval;

	public ScanSchedule(DateTime startUtc, TimeSpan firstDelay, TimeSpan interval)
	{
		if (interval <= TimeSpan.Zero)
		{
			throw new ArgumentOutOfRangeException("interval");
		}
		_interval = interval;
		NextAt = startUtc + firstDelay;
	}

	public bool TryStart(DateTime nowUtc, bool running, out DateTime scheduledAt, out int skipped)
	{
		scheduledAt = default(DateTime);
		skipped = 0;
		if (nowUtc < NextAt)
		{
			return false;
		}
		long ticks = (nowUtc - NextAt).Ticks;
		TimeSpan interval = _interval;
		long num = ticks / interval.Ticks;
		DateTime nextAt = NextAt;
		interval = _interval;
		DateTime dateTime = nextAt + TimeSpan.FromTicks(num * interval.Ticks);
		NextAt = dateTime + _interval;
		skipped = (int)Math.Min(2147483647L, num);
		if (running)
		{
			skipped++;
			return false;
		}
		scheduledAt = dateTime;
		return true;
	}
}
public sealed class PendingMessage
{
	public string Kind;

	public string Key;

	public string CapturedAt;

	public List<string> BlobRefs = new List<string>();

	public string File;

	public DateTime QueuedUtc;
}
public sealed class SpoolBlob
{
	public int ByteLength;

	public int Width;

	public int Height;

	public bool Uploaded;

	public DateTime StoredUtc;
}
public enum PutOutcome
{
	Accepted,
	ReplacedPending,
	DroppedOlder
}
public sealed class Spool
{
	private sealed class Index
	{
		public Dictionary<string, string> LastAccepted = new Dictionary<string, string>();

		public Dictionary<string, string> LastUploaded = new Dictionary<string, string>();

		public Dictionary<string, PendingMessage> Pending = new Dictionary<string, PendingMessage>();

		public Dictionary<string, SpoolBlob> Blobs = new Dictionary<string, SpoolBlob>();

		public Dictionary<string, string> ItemsExports = new Dictionary<string, string>();

		public Dictionary<string, string> RecipesExports = new Dictionary<string, string>();

		public string Target;
	}

	private static readonly Regex Unsafe = new Regex("[^A-Za-z0-9_.-]");

	private readonly string _root;

	private readonly string _pendingDir;

	private readonly string _blobDir;

	private readonly string _indexPath;

	private Index _index = new Index();

	public string Root => _root;

	public int PendingCount => _index.Pending.Count;

	private string PieceIconsPath => Path.Combine(_root, "piece-icons.json");

	public Spool(string root)
	{
		_root = root;
		_pendingDir = Path.Combine(root, "pending");
		_blobDir = Path.Combine(root, "blobs");
		_indexPath = Path.Combine(root, "index.json");
		Directory.CreateDirectory(_pendingDir);
		Directory.CreateDirectory(_blobDir);
		Load();
	}

	private void Load()
	{
		if (!File.Exists(_indexPath))
		{
			return;
		}
		try
		{
			_index = JsonConvert.DeserializeObject<Index>(File.ReadAllText(_indexPath)) ?? new Index();
		}
		catch (Exception)
		{
			_index = new Index();
		}
		List<string> list = new List<string>();
		foreach (KeyValuePair<string, PendingMessage> item in _index.Pending)
		{
			if (!File.Exists(item.Value.File))
			{
				list.Add(item.Key);
			}
		}
		foreach (string item2 in list)
		{
			_index.Pending.Remove(item2);
		}
		list.Clear();
		foreach (KeyValuePair<string, SpoolBlob> blob in _index.Blobs)
		{
			if (!blob.Value.Uploaded && !File.Exists(BlobPath(blob.Key)))
			{
				list.Add(blob.Key);
			}
		}
		foreach (string item3 in list)
		{
			_index.Blobs.Remove(item3);
		}
	}

	private void Save()
	{
		string text = _indexPath + ".tmp";
		File.WriteAllText(text, JsonConvert.SerializeObject((object)_index, (Formatting)1));
		if (File.Exists(_indexPath))
		{
			File.Delete(_indexPath);
		}
		File.Move(text, _indexPath);
	}

	private static int Compare(string a, string b)
	{
		return string.CompareOrdinal(a ?? "", b ?? "");
	}

	public PutOutcome Put(string kind, string key, string capturedAt, byte[] bytes, IEnumerable<string> blobRefs, DateTime nowUtc)
	{
		if (_index.LastAccepted.TryGetValue(key, out var value) && Compare(capturedAt, value) <= 0)
		{
			return PutOutcome.DroppedOlder;
		}
		PendingMessage value2;
		bool flag = _index.Pending.TryGetValue(key, out value2);
		string text = Path.Combine(_pendingDir, Unsafe.Replace(key, "_") + ".json");
		string text2 = text + ".tmp";
		File.WriteAllBytes(text2, bytes);
		if (File.Exists(text))
		{
			File.Delete(text);
		}
		File.Move(text2, text);
		_index.Pending[key] = new PendingMessage
		{
			Kind = kind,
			Key = key,
			CapturedAt = capturedAt,
			BlobRefs = new List<string>(blobRefs),
			File = text,
			QueuedUtc = (flag ? value2.QueuedUtc : nowUtc)
		};
		_index.LastAccepted[key] = capturedAt;
		Save();
		if (!flag)
		{
			return PutOutcome.Accepted;
		}
		return PutOutcome.ReplacedPending;
	}

	public List<PendingMessage> Pending()
	{
		List<PendingMessage> list = new List<PendingMessage>(_index.Pending.Values);
		list.Sort((PendingMessage a, PendingMessage b) => a.QueuedUtc.CompareTo(b.QueuedUtc));
		return list;
	}

	public byte[] Read(PendingMessage m)
	{
		return File.ReadAllBytes(m.File);
	}

	public void MarkUploaded(PendingMessage m)
	{
		if (_index.Pending.TryGetValue(m.Key, out var value) && value.CapturedAt == m.CapturedAt)
		{
			_index.Pending.Remove(m.Key);
			try
			{
				File.Delete(m.File);
			}
			catch (IOException)
			{
			}
		}
		if (!_index.LastUploaded.TryGetValue(m.Key, out var value2) || Compare(m.CapturedAt, value2) > 0)
		{
			_index.LastUploaded[m.Key] = m.CapturedAt;
		}
		Save();
	}

	public void Drop(PendingMessage m)
	{
		if (_index.Pending.TryGetValue(m.Key, out var value) && value.CapturedAt == m.CapturedAt)
		{
			_index.Pending.Remove(m.Key);
			try
			{
				File.Delete(m.File);
			}
			catch (IOException)
			{
			}
			Save();
		}
	}

	public string BlobPath(string sha256)
	{
		return Path.Combine(_blobDir, sha256 + ".png");
	}

	public List<string> PendingBlobs()
	{
		List<KeyValuePair<string, SpoolBlob>> list = new List<KeyValuePair<string, SpoolBlob>>();
		foreach (KeyValuePair<string, SpoolBlob> blob in _index.Blobs)
		{
			if (!blob.Value.Uploaded && File.Exists(BlobPath(blob.Key)))
			{
				list.Add(blob);
			}
		}
		list.Sort((KeyValuePair<string, SpoolBlob> a, KeyValuePair<string, SpoolBlob> b) => a.Value.StoredUtc.CompareTo(b.Value.StoredUtc));
		List<string> list2 = new List<string>(list.Count);
		foreach (KeyValuePair<string, SpoolBlob> item in list)
		{
			list2.Add(item.Key);
		}
		return list2;
	}

	public bool HasBlob(string sha256)
	{
		if (_index.Blobs.TryGetValue(sha256, out var value))
		{
			if (!value.Uploaded)
			{
				return File.Exists(BlobPath(sha256));
			}
			return true;
		}
		return false;
	}

	public bool IsBlobUploaded(string sha256)
	{
		if (_index.Blobs.TryGetValue(sha256, out var value))
		{
			return value.Uploaded;
		}
		return false;
	}

	public SpoolBlob Blob(string sha256)
	{
		if (!_index.Blobs.TryGetValue(sha256, out var value))
		{
			return null;
		}
		return value;
	}

	public void PutBlob(string sha256, byte[] png, int width, int height, DateTime nowUtc)
	{
		if (!_index.Blobs.TryGetValue(sha256, out var value) || !value.Uploaded)
		{
			string text = BlobPath(sha256);
			string text2 = text + ".tmp";
			File.WriteAllBytes(text2, png);
			if (File.Exists(text))
			{
				File.Delete(text);
			}
			File.Move(text2, text);
			_index.Blobs[sha256] = new SpoolBlob
			{
				ByteLength = png.Length,
				Width = width,
				Height = height,
				Uploaded = false,
				StoredUtc = nowUtc
			};
			Save();
		}
	}

	public byte[] ReadBlob(string sha256)
	{
		return File.ReadAllBytes(BlobPath(sha256));
	}

	public void MarkBlobUploaded(string sha256)
	{
		if (_index.Blobs.TryGetValue(sha256, out var value))
		{
			value.Uploaded = true;
			try
			{
				File.Delete(BlobPath(sha256));
			}
			catch (IOException)
			{
			}
			Save();
		}
	}

	public void DropBlob(string sha256)
	{
		_index.Blobs.Remove(sha256);
		try
		{
			File.Delete(BlobPath(sha256));
		}
		catch (IOException)
		{
		}
		Save();
	}

	public bool HasItemsExport(string gameVersion, string modSetHash)
	{
		return _index.ItemsExports.ContainsKey(gameVersion + "|" + modSetHash);
	}

	public void MarkItemsExport(string gameVersion, string modSetHash, string capturedAt)
	{
		_index.ItemsExports[gameVersion + "|" + modSetHash] = capturedAt;
		Save();
	}

	public bool HasRecipesExport(string gameVersion, string modSetHash)
	{
		return _index.RecipesExports.ContainsKey(gameVersion + "|" + modSetHash);
	}

	public void MarkRecipesExport(string gameVersion, string modSetHash, string capturedAt)
	{
		_index.RecipesExports[gameVersion + "|" + modSetHash] = capturedAt;
		Save();
	}

	public void SavePieceIcons(PieceIconMap map)
	{
		string text = PieceIconsPath + ".tmp";
		File.WriteAllText(text, map.ToJson());
		if (File.Exists(PieceIconsPath))
		{
			File.Delete(PieceIconsPath);
		}
		File.Move(text, PieceIconsPath);
	}

	public PieceIconMap LoadPieceIcons()
	{
		try
		{
			PieceIconMap map;
			string error;
			return (File.Exists(PieceIconsPath) && PieceIconMap.TryParse(File.ReadAllText(PieceIconsPath), out map, out error)) ? map : null;
		}
		catch (IOException)
		{
			return null;
		}
	}

	public bool ResetIfTargetChanged(string fingerprint)
	{
		if (_index.Target == fingerprint)
		{
			return false;
		}
		bool flag = _index.Target == null;
		_index.Target = fingerprint;
		if (!flag)
		{
			_index.LastUploaded.Clear();
			_index.ItemsExports.Clear();
			_index.RecipesExports.Clear();
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, SpoolBlob> blob in _index.Blobs)
			{
				if (File.Exists(BlobPath(blob.Key)))
				{
					blob.Value.Uploaded = false;
				}
				else
				{
					list.Add(blob.Key);
				}
			}
			foreach (string item in list)
			{
				_index.Blobs.Remove(item);
			}
		}
		Save();
		return !flag;
	}

	public string LastUploaded(string key)
	{
		if (!_index.LastUploaded.TryGetValue(key, out var value))
		{
			return null;
		}
		return value;
	}

	public string LastAccepted(string key)
	{
		if (!_index.LastAccepted.TryGetValue(key, out var value))
		{
			return null;
		}
		return value;
	}

	public string Summary()
	{
		int num = 0;
		foreach (SpoolBlob value in _index.Blobs.Values)
		{
			if (!value.Uploaded)
			{
				num++;
			}
		}
		return $"pending={_index.Pending.Count} blobsPending={num} uploadedKeys={_index.LastUploaded.Count}";
	}
}
public static class UpgradeFormula
{
	public static bool IsOneDotX(string gameVersion)
	{
		if (string.IsNullOrEmpty(gameVersion))
		{
			return false;
		}
		int num = gameVersion.IndexOf('.');
		if (int.TryParse((num < 0) ? gameVersion : gameVersion.Substring(0, num), NumberStyles.None, CultureInfo.InvariantCulture, out var result))
		{
			return result >= 1;
		}
		return false;
	}

	public static long Amount(string gameVersion, int amount, int amountPerLevel, bool upgraderResource, int q)
	{
		if (q <= 1)
		{
			return amount;
		}
		if (!IsOneDotX(gameVersion))
		{
			return (long)(q - 1) * (long)amountPerLevel;
		}
		if (upgraderResource)
		{
			return amount;
		}
		return (long)amountPerLevel << Math.Min(q - 2, 30);
	}

	public static int StationLevel(int minStationLevel, int q)
	{
		return Math.Max(1, minStationLevel) + (q - 1);
	}
}
public sealed class UploaderSettings
{
	public TimeSpan MinInterval = TimeSpan.FromSeconds(5.0);

	public int MaxMessagesPerMinute = 30;

	public int MaxBlobsPerMinute = 10;

	public int MaxBlobBytes = 10485760;

	public TimeSpan BackoffBase = TimeSpan.FromSeconds(30.0);

	public TimeSpan BackoffMax = TimeSpan.FromMinutes(10.0);

	public TimeSpan HaltRetry = TimeSpan.FromMinutes(10.0);

	public TimeSpan QuotaRetry = TimeSpan.FromMinutes(10.0);
}
public enum UploaderState
{
	Idle,
	Sending,
	Backoff,
	Halted
}
public sealed class Uploader
{
	private readonly Spool _spool;

	private readonly RaidLedgerClient _client;

	private readonly UploaderSettings _s;

	private readonly ILinkLog _log;

	private readonly List<DateTime> _msgWindow = new List<DateTime>();

	private readonly List<DateTime> _blobWindow = new List<DateTime>();

	private Task<UploadResult> _inflight;

	private PendingMessage _inflightMessage;

	private string _inflightBlob;

	private DateTime _inflightStarted;

	private DateTime _lastRequest = DateTime.MinValue;

	private DateTime _resumeAt = DateTime.MinValue;

	private int _failures;

	private UploaderState _state;

	private string _haltReason;

	public UploaderState State => _state;

	public string HaltReason => _haltReason;

	public int Uploaded { get; private set; }

	public int Failed { get; private set; }

	public DateTime ResumeAt => _resumeAt;

	public Uploader(Spool spool, RaidLedgerClient client, UploaderSettings settings, ILinkLog log)
	{
		_spool = spool;
		_client = client;
		_s = settings;
		_log = log;
	}

	public void Resume()
	{
		if (_state == UploaderState.Halted || _state == UploaderState.Backoff)
		{
			_state = UploaderState.Idle;
			_resumeAt = DateTime.MinValue;
		}
	}

	public void Tick(DateTime nowUtc)
	{
		if (_inflight != null)
		{
			if (_inflight.IsCompleted)
			{
				Task<UploadResult> inflight = _inflight;
				_inflight = null;
				UploadResult r = (inflight.IsFaulted ? new UploadResult
				{
					StatusCode = 0,
					Code = "fault",
					Message = inflight.Exception?.GetBaseException().Message
				} : inflight.Result);
				OnResult(r, nowUtc);
			}
		}
		else
		{
			if ((_state == UploaderState.Backoff || _state == UploaderState.Halted) && nowUtc < _resumeAt)
			{
				return;
			}
			if (_state == UploaderState.Halted)
			{
				_log.Info("retrying after halt: " + _haltReason);
			}
			_state = UploaderState.Idle;
			if (nowUtc - _lastRequest < _s.MinInterval)
			{
				return;
			}
			Prune(_msgWindow, nowUtc);
			Prune(_blobWindow, nowUtc);
			if (_blobWindow.Count < _s.MaxBlobsPerMinute)
			{
				foreach (string item in _spool.PendingBlobs())
				{
					SpoolBlob spoolBlob = _spool.Blob(item);
					if (spoolBlob.ByteLength > _s.MaxBlobBytes)
					{
						_log.Warn($"blob {Short(item)} is {spoolBlob.ByteLength} bytes > Upload.MaxBlobBytes {_s.MaxBlobBytes}; dropped");
						_spool.DropBlob(item);
						continue;
					}
					Start(nowUtc, null, item, _client.PutBlobAsync(item, _spool.ReadBlob(item)));
					_blobWindow.Add(nowUtc);
					return;
				}
			}
			foreach (PendingMessage item2 in _spool.Pending())
			{
				if (_msgWindow.Count < _s.MaxMessagesPerMinute)
				{
					byte[] json;
					try
					{
						json = _spool.Read(item2);
					}
					catch (Exception ex)
					{
						_log.Error("cannot read spooled " + item2.Key + ": " + ex.Message + "; dropped");
						_spool.Drop(item2);
						continue;
					}
					Start(nowUtc, item2, null, _client.PostMessageAsync(item2.Kind, json));
					_msgWindow.Add(nowUtc);
				}
				break;
			}
		}
	}

	private void Start(DateTime now, PendingMessage m, string sha, Task<UploadResult> task)
	{
		_inflight = task;
		_inflightMessage = m;
		_inflightBlob = sha;
		_inflightStarted = now;
		_lastRequest = now;
		_state = UploaderState.Sending;
		_log.Debug((m != null) ? ("POST " + m.Kind + " " + m.Key + " capturedAt=" + m.CapturedAt) : ("PUT blob " + Short(sha)));
	}

	private void OnResult(UploadResult r, DateTime now)
	{
		string text = ((_inflightMessage != null) ? (_inflightMessage.Kind + " " + _inflightMessage.Key) : ("blob " + Short(_inflightBlob)));
		long num = (long)(now - _inflightStarted).TotalMilliseconds;
		PendingMessage inflightMessage = _inflightMessage;
		string inflightBlob = _inflightBlob;
		_inflightMessage = null;
		_inflightBlob = null;
		if (r.Success)
		{
			_failures = 0;
			Uploaded++;
			_state = UploaderState.Idle;
			if (inflightMessage != null)
			{
				_spool.MarkUploaded(inflightMessage);
			}
			else
			{
				_spool.MarkBlobUploaded(inflightBlob);
			}
			_log.Info(string.Format("{0} {1} → {2} in {3} ms", (inflightMessage != null) ? "POST" : "PUT", text, r, num));
			return;
		}
		Failed++;
		switch (r.StatusCode)
		{
		case 401:
			Halt(now, "401 " + r.Code + ": the per-server token was rejected (rotated?). Paste the current token from the Raid Ledger admin dialog into RaidLedger.ServerToken.");
			break;
		case 403:
			Halt(now, "403 " + r.Code + ": RaidLedger.ServerId does not match the token's server. Copy the id shown next to the token in the admin dialog.");
			break;
		case 429:
			Backoff(now, TimeSpan.FromSeconds(Math.Max(1, Math.Min(600, (r.RetryAfterSeconds > 0) ? r.RetryAfterSeconds : 60))), "429 rate limited on " + text);
			break;
		case 413:
			if (r.Code == "quota_exceeded")
			{
				_state = UploaderState.Backoff;
				_resumeAt = now + _s.QuotaRetry;
				_log.Warn($"{text} → {r}: this server's storage quota on Raid Ledger is full; retrying in {_s.QuotaRetry.TotalMinutes:0} min (an admin must raise the quota or prune)");
				break;
			}
			goto case 400;
		case 400:
		case 404:
		case 415:
		case 422:
			_state = UploaderState.Idle;
			_log.Error($"{text} → {r}: dropped (a mod bug, a contract drift or a route mismatch — check contract-drift and the RaidLedger.*Path settings)");
			if (inflightMessage != null)
			{
				_spool.Drop(inflightMessage);
			}
			else
			{
				_spool.DropBlob(inflightBlob);
			}
			break;
		case 409:
			_state = UploaderState.Idle;
			_log.Info($"{text} → {r}: treated as done");
			if (inflightMessage != null)
			{
				_spool.MarkUploaded(inflightMessage);
			}
			else
			{
				_spool.MarkBlobUploaded(inflightBlob);
			}
			break;
		default:
		{
			_failures++;
			double value = Math.Min(_s.BackoffMax.TotalSeconds, _s.BackoffBase.TotalSeconds * Math.Pow(2.0, Math.Min(10, _failures - 1)));
			Backoff(now, TimeSpan.FromSeconds(value), $"{text} → {r} (failure #{_failures})");
			break;
		}
		}
	}

	private void Backoff(DateTime now, TimeSpan delay, string why)
	{
		_state = UploaderState.Backoff;
		_resumeAt = now + delay;
		_log.Warn($"{why}; backing off {delay.TotalSeconds:0} s");
	}

	private void Halt(DateTime now, string reason)
	{
		_state = UploaderState.Halted;
		_haltReason = reason;
		_resumeAt = now + _s.HaltRetry;
		_log.Error($"uploads HALTED: {reason} Retrying in {_s.HaltRetry.TotalMinutes:0} min.");
	}

	private static void Prune(List<DateTime> window, DateTime now)
	{
		window.RemoveAll((DateTime t) => (now - t).TotalSeconds > 60.0);
	}

	private static string Short(string sha)
	{
		if (!string.IsNullOrEmpty(sha))
		{
			if (sha.Length <= 12)
			{
				return sha;
			}
			return sha.Substring(0, 12) + "…";
		}
		return "?";
	}
}
public static class WorldAllowlist
{
	public static List<string> Parse(string csv)
	{
		List<string> list = new List<string>();
		if (string.IsNullOrEmpty(csv))
		{
			return list;
		}
		string[] array = csv.Split(new char[1] { ',' });
		for (int i = 0; i < array.Length; i++)
		{
			string text = array[i].Trim();
			if (text.Length > 0)
			{
				list.Add(text);
			}
		}
		return list;
	}

	public static bool Allows(string csv, string worldName)
	{
		List<string> list = Parse(csv);
		if (list.Count == 0)
		{
			return true;
		}
		string b = (worldName ?? "").Trim();
		foreach (string item in list)
		{
			if (string.Equals(item, b, StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
		}
		return false;
	}
}

plugins/RlValheimLink/RlValheimLink.dll

Decompiled 13 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RlValheimLink.Client;
using RlValheimLink.Contract;
using RlValheimLink.Core;
using RlValheimLink.Net;
using RlValheimLink.Server;
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: AssemblyCompany("Raid Ledger Plugins")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("RL Valheim Link: BepInEx plugin (client + dedicated server) that pushes character, map and item data to Raid Ledger.")]
[assembly: AssemblyFileVersion("0.4.1.0")]
[assembly: AssemblyInformationalVersion("0.4.1+ff1cdb186f76b7d41bda6fb07c33795e02baf157")]
[assembly: AssemblyProduct("RlValheimLink")]
[assembly: AssemblyTitle("RlValheimLink")]
[assembly: AssemblyVersion("0.4.1.0")]
namespace RlValheimLink
{
	internal static class ConsoleCommands
	{
		private static bool _registered;

		public static void Register()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			if (_registered)
			{
				return;
			}
			_registered = true;
			try
			{
				ConsoleEvent val = new ConsoleEvent(Run);
				ParameterInfo[] parameters;
				ConstructorInfo constructorInfo = FindCtor(out parameters);
				if (constructorInfo == null)
				{
					Log.Warn("console command not registered: no ConsoleCommand(string, string, ConsoleEvent, …) constructor found");
					return;
				}
				object[] array = new object[parameters.Length];
				for (int i = 0; i < parameters.Length; i++)
				{
					Type parameterType = parameters[i].ParameterType;
					switch (i)
					{
					case 0:
						array[i] = "rlvl";
						continue;
					case 1:
						array[i] = "RL Valheim Link: rlvl status | rlvl export now | rlvl export items | rlvl export icons";
						continue;
					}
					if (parameterType == typeof(ConsoleEvent))
					{
						array[i] = val;
					}
					else if (parameterType == typeof(bool))
					{
						array[i] = false;
					}
					else
					{
						array[i] = null;
					}
				}
				constructorInfo.Invoke(array);
			}
			catch (Exception ex)
			{
				Log.Warn("console command not registered: " + ex.Message);
			}
		}

		private static ConstructorInfo FindCtor(out ParameterInfo[] parameters)
		{
			ConstructorInfo[] constructors = typeof(ConsoleCommand).GetConstructors();
			foreach (ConstructorInfo constructorInfo in constructors)
			{
				ParameterInfo[] parameters2 = constructorInfo.GetParameters();
				if (parameters2.Length >= 3 && parameters2[0].ParameterType == typeof(string) && parameters2[1].ParameterType == typeof(string) && parameters2[2].ParameterType == typeof(ConsoleEvent))
				{
					parameters = parameters2;
					return constructorInfo;
				}
			}
			parameters = null;
			return null;
		}

		private static void Run(ConsoleEventArgs args)
		{
			string obj = ((args.Args.Length > 1) ? args.Args[1].ToLowerInvariant() : "status");
			string text = ((args.Args.Length > 2) ? args.Args[2].ToLowerInvariant() : "");
			string text2 = obj;
			if (!(text2 == "status"))
			{
				if (text2 == "export")
				{
					switch (text)
					{
					case "now":
						if (!Roles.IsClient)
						{
							args.Context.AddString("RLVL: not a client");
							return;
						}
						args.Context.AddString("RLVL: capturing character + map now");
						ClientHalf.OnProfileSaved(setLogoutPoint: false);
						return;
					case "items":
						if (!Roles.IsClient)
						{
							args.Context.AddString("RLVL: not a client");
							return;
						}
						args.Context.AddString("RLVL: items export started (see the log)");
						ItemsCapture.Start("console");
						return;
					case "icons":
						if (!Roles.IsClient)
						{
							args.Context.AddString("RLVL: not a client");
							return;
						}
						args.Context.AddString("RLVL: piece icon export started (see the log); the server uses it only from a client Server.ItemsExportBy allows");
						PieceIconsCapture.Start("console");
						return;
					}
				}
				args.Context.AddString("usage: rlvl status | rlvl export now | rlvl export items | rlvl export icons");
			}
			else
			{
				args.Context.AddString(string.Format("RLVL v{0}: server={1} client={2} optOut={3} queued={4} itemsExport={5} pieceIcons={6}", "0.4.1", Roles.IsServer, Roles.IsClient, PluginConfig.OptOut.Value, LinkChannel.QueuedTransfers, ItemsCapture.IsRunning ? "running" : "idle", PieceIconsCapture.IsRunning ? "running" : "idle"));
				if (Roles.IsServer)
				{
					args.Context.AddString("RLVL server: " + ServerHalf.Status());
				}
			}
		}
	}
	internal static class GameAccess
	{
		public sealed class ZdoKeySet
		{
			public string Via;

			public List<KeyValuePair<int, float>> Floats = new List<KeyValuePair<int, float>>();

			public List<KeyValuePair<int, Vector3>> Vec3s = new List<KeyValuePair<int, Vector3>>();

			public List<KeyValuePair<int, Quaternion>> Quats = new List<KeyValuePair<int, Quaternion>>();

			public List<KeyValuePair<int, int>> Ints = new List<KeyValuePair<int, int>>();

			public List<KeyValuePair<int, long>> Longs = new List<KeyValuePair<int, long>>();

			public List<KeyValuePair<int, string>> Strings = new List<KeyValuePair<int, string>>();

			public List<KeyValuePair<int, byte[]>> ByteArrays = new List<KeyValuePair<int, byte[]>>();
		}

		public static readonly FieldRef<Humanoid, ItemData> HelmetItem = AccessTools.FieldRefAccess<Humanoid, ItemData>("m_helmetItem");

		public static readonly FieldRef<Humanoid, ItemData> ChestItem = AccessTools.FieldRefAccess<Humanoid, ItemData>("m_chestItem");

		public static readonly FieldRef<Humanoid, ItemData> LegItem = AccessTools.FieldRefAccess<Humanoid, ItemData>("m_legItem");

		public static readonly FieldRef<Humanoid, ItemData> ShoulderItem = AccessTools.FieldRefAccess<Humanoid, ItemData>("m_shoulderItem");

		public static readonly FieldRef<Humanoid, ItemData> UtilityItem = AccessTools.FieldRefAccess<Humanoid, ItemData>("m_utilityItem");

		public static readonly FieldRef<Humanoid, ItemData> TrinketItem = AccessTools.FieldRefAccess<Humanoid, ItemData>("m_trinketItem");

		public static readonly FieldRef<Humanoid, ItemData> RightItem = AccessTools.FieldRefAccess<Humanoid, ItemData>("m_rightItem");

		public static readonly FieldRef<Humanoid, ItemData> LeftItem = AccessTools.FieldRefAccess<Humanoid, ItemData>("m_leftItem");

		public static readonly FieldRef<Humanoid, ItemData> AmmoItem = AccessTools.FieldRefAccess<Humanoid, ItemData>("m_ammoItem");

		public static readonly FieldRef<Player, HashSet<string>> Trophies = AccessTools.FieldRefAccess<Player, HashSet<string>>("m_trophies");

		public static readonly FieldRef<Player, HashSet<string>> KnownMaterial = AccessTools.FieldRefAccess<Player, HashSet<string>>("m_knownMaterial");

		public static readonly FieldRef<Player, HashSet<string>> KnownRecipes = AccessTools.FieldRefAccess<Player, HashSet<string>>("m_knownRecipes");

		public static readonly FieldRef<Skills, Dictionary<SkillType, Skill>> SkillData = AccessTools.FieldRefAccess<Skills, Dictionary<SkillType, Skill>>("m_skillData");

		public static readonly FieldRef<Minimap, BitArray> Explored = AccessTools.FieldRefAccess<Minimap, BitArray>("m_explored");

		public static readonly FieldRef<Minimap, BitArray> ExploredOthers = AccessTools.FieldRefAccess<Minimap, BitArray>("m_exploredOthers");

		public static readonly FieldRef<Minimap, List<PinData>> Pins = AccessTools.FieldRefAccess<Minimap, List<PinData>>("m_pins");

		private const BindingFlags AnyInstance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		private const BindingFlags AnyStatic = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		public static long OwnRoutedId()
		{
			return ZDOMan.GetSessionID();
		}

		public static long ServerRoutedId()
		{
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return 0L;
			}
			if (instance.IsServer())
			{
				return OwnRoutedId();
			}
			return instance.GetServerPeer()?.m_uid ?? 0;
		}

		public static ZdoKeySet ZdoKeys(ZDOID id)
		{
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01de: Unknown result type (might be due to invalid IL or missing references)
			//IL_020c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			ZdoKeySet zdoKeySet = new ZdoKeySet();
			MethodInfo methodInfo = typeof(ZDOExtraData).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "GetData" && m.GetParameters().Length == 9);
			if (methodInfo != null)
			{
				object[] array = new object[9] { id, null, null, null, null, null, null, null, null };
				Invoke(methodInfo, null, array);
				Copy(array[1], zdoKeySet.Floats);
				Copy(array[2], zdoKeySet.Vec3s);
				Copy(array[3], zdoKeySet.Quats);
				Copy(array[4], zdoKeySet.Ints);
				Copy(array[5], zdoKeySet.Longs);
				Copy(array[6], zdoKeySet.Strings);
				Copy(array[7], zdoKeySet.ByteArrays);
				zdoKeySet.Via = "ZDOExtraData.GetData (" + Visibility(methodInfo) + ")";
				return zdoKeySet;
			}
			Copy(CallStatic(typeof(ZDOExtraData), "GetFloats", id), zdoKeySet.Floats);
			Copy(CallStatic(typeof(ZDOExtraData), "GetVec3s", id), zdoKeySet.Vec3s);
			Copy(CallStatic(typeof(ZDOExtraData), "GetQuaternions", id), zdoKeySet.Quats);
			Copy(CallStatic(typeof(ZDOExtraData), "GetInts", id), zdoKeySet.Ints);
			Copy(CallStatic(typeof(ZDOExtraData), "GetLongs", id), zdoKeySet.Longs);
			Copy(CallStatic(typeof(ZDOExtraData), "GetStrings", id), zdoKeySet.Strings);
			Copy(CallStatic(typeof(ZDOExtraData), "GetByteArrays", id), zdoKeySet.ByteArrays);
			zdoKeySet.Via = "ZDOExtraData.Get<Type>s (0.221.x API)";
			return zdoKeySet;
		}

		private static void Copy<T>(object source, List<T> into)
		{
			if (source is IEnumerable<T> collection)
			{
				into.AddRange(collection);
			}
		}

		private static object CallStatic(Type t, string name, params object[] args)
		{
			MethodInfo? methodInfo = t.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo x) => x.Name == name && x.GetParameters().Length == args.Length);
			if (methodInfo == null)
			{
				throw new MissingMethodException(t.Name, name);
			}
			return Invoke(methodInfo, null, args);
		}

		private static object Invoke(MethodBase m, object target, object[] args)
		{
			try
			{
				return (m is ConstructorInfo constructorInfo) ? constructorInfo.Invoke(args) : m.Invoke(target, args);
			}
			catch (TargetInvocationException ex) when (ex.InnerException != null)
			{
				throw ex.InnerException;
			}
		}

		public static int ResidentZdoCount()
		{
			FieldInfo fieldInfo = FindField(typeof(ZDOMan), "m_objectsByID");
			if (ZDOMan.instance == null || !(fieldInfo?.GetValue(ZDOMan.instance) is ICollection collection))
			{
				return -1;
			}
			return collection.Count;
		}

		public static Inventory NewTemporaryInventory()
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected O, but got Unknown
			ConstructorInfo constructor = typeof(Inventory).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(bool) }, null);
			if (!(constructor == null))
			{
				return (Inventory)Invoke(constructor, null, new object[1] { true });
			}
			return null;
		}

		public static void LoadInventory(Inventory inv, ZPackage pkg, bool flag)
		{
			MethodInfo? method = typeof(Inventory).GetMethod("Load", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2]
			{
				typeof(ZPackage),
				typeof(bool)
			}, null);
			if (method == null)
			{
				throw new MissingMethodException("Inventory", "Load(ZPackage, Boolean)");
			}
			Invoke(method, inv, new object[2] { pkg, flag });
		}

		public static Dictionary<int, string> ZdoVarNames()
		{
			Dictionary<int, string> dictionary = new Dictionary<int, string>();
			FieldInfo[] fields = typeof(ZDOVars).GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (FieldInfo fieldInfo in fields)
			{
				if (!(fieldInfo.FieldType != typeof(int)))
				{
					int key = (int)fieldInfo.GetValue(null);
					dictionary[key] = (dictionary.TryGetValue(key, out var value) ? (value + "|" + fieldInfo.Name) : fieldInfo.Name);
				}
			}
			return dictionary;
		}

		public static int? ZdoVar(string field)
		{
			FieldInfo field2 = typeof(ZDOVars).GetField(field, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (!(field2 != null) || !(field2.FieldType == typeof(int)))
			{
				return null;
			}
			return (int)field2.GetValue(null);
		}

		public static string ItemVersionName(int value)
		{
			Type nestedType = typeof(Version).GetNestedType("Item", BindingFlags.Public | BindingFlags.NonPublic);
			if (nestedType == null || !nestedType.IsEnum)
			{
				return null;
			}
			List<string> list = new List<string>();
			string[] names = Enum.GetNames(nestedType);
			foreach (string text in names)
			{
				if (Convert.ToInt32(Enum.Parse(nestedType, text)) == value)
				{
					list.Add(text);
				}
			}
			if (list.Count != 0)
			{
				return string.Join("|", list);
			}
			return null;
		}

		public static bool TryRead(object obj, string member, out object value)
		{
			value = null;
			if (obj == null)
			{
				return false;
			}
			FieldInfo fieldInfo = FindField(obj.GetType(), member);
			if (fieldInfo != null)
			{
				value = fieldInfo.GetValue(fieldInfo.IsStatic ? null : obj);
				return true;
			}
			PropertyInfo propertyInfo = FindProperty(obj.GetType(), member);
			if (propertyInfo == null)
			{
				return false;
			}
			value = propertyInfo.GetValue(obj, null);
			return true;
		}

		public static string MemberVisibility(Type t, string member)
		{
			List<MethodBase> list = ((member == ".ctor") ? t.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Cast<MethodBase>().ToList() : (from m in t.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
				where m.Name == member
				select m).Cast<MethodBase>().ToList());
			if (list.Count > 0)
			{
				return string.Join(",", list.Select((MethodBase m) => "(" + string.Join(",", from p in m.GetParameters()
					select p.ParameterType.Name) + "):" + Visibility(m)));
			}
			FieldInfo fieldInfo = FindField(t, member);
			if (fieldInfo != null)
			{
				return Visibility(fieldInfo);
			}
			PropertyInfo propertyInfo = FindProperty(t, member);
			if (!(propertyInfo == null))
			{
				return Visibility(propertyInfo.GetGetMethod(nonPublic: true));
			}
			return "missing";
		}

		public static MethodInfo ItemDropAwake()
		{
			return typeof(ItemDrop).GetMethod("Awake", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
		}

		private static string Visibility(MemberInfo m)
		{
			if ((object)m != null)
			{
				if (!(m is FieldInfo fieldInfo))
				{
					if (m is MethodBase methodBase)
					{
						return methodBase.IsPublic ? "public" : (methodBase.IsFamily ? "protected" : (methodBase.IsAssembly ? "internal" : "private"));
					}
					return "?";
				}
				return fieldInfo.IsPublic ? "public" : (fieldInfo.IsFamily ? "protected" : (fieldInfo.IsAssembly ? "internal" : "private"));
			}
			return "missing";
		}

		private static FieldInfo FindField(Type t, string name)
		{
			while (t != null)
			{
				FieldInfo field = t.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if (field != null)
				{
					return field;
				}
				t = t.BaseType;
			}
			return null;
		}

		private static PropertyInfo FindProperty(Type t, string name)
		{
			while (t != null)
			{
				PropertyInfo property = t.GetProperty(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (property != null && property.GetIndexParameters().Length == 0 && property.GetGetMethod(nonPublic: true) != null)
				{
					return property;
				}
				t = t.BaseType;
			}
			return null;
		}

		public static string SelfCheck()
		{
			try
			{
				_ = HelmetItem;
				_ = ChestItem;
				_ = LegItem;
				_ = ShoulderItem;
				_ = UtilityItem;
				_ = TrinketItem;
				_ = RightItem;
				_ = LeftItem;
				_ = AmmoItem;
				_ = Trophies;
				_ = KnownMaterial;
				_ = KnownRecipes;
				_ = SkillData;
				_ = Explored;
				_ = ExploredOthers;
				_ = Pins;
				return null;
			}
			catch (Exception ex)
			{
				return ex.GetBaseException().Message;
			}
		}
	}
	internal static class Log
	{
		private static ManualLogSource _log;

		private static readonly List<string> Secrets = new List<string>();

		public static void Init(ManualLogSource log)
		{
			_log = log;
		}

		public static void RedactSecret(string secret)
		{
			if (!string.IsNullOrEmpty(secret) && secret.Length >= 8 && !Secrets.Contains(secret))
			{
				Secrets.Add(secret);
			}
		}

		public static string Redact(string s)
		{
			if (s == null)
			{
				return null;
			}
			foreach (string secret in Secrets)
			{
				if (s.Contains(secret))
				{
					s = s.Replace(secret, "***");
				}
			}
			return s;
		}

		public static void Debug(string msg)
		{
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogDebug((object)("RLVL: " + Redact(msg)));
			}
		}

		public static void Info(string msg)
		{
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogInfo((object)("RLVL: " + Redact(msg)));
			}
		}

		public static void Warn(string msg)
		{
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogWarning((object)("RLVL: " + Redact(msg)));
			}
		}

		public static void Error(string msg)
		{
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogError((object)("RLVL: " + Redact(msg)));
			}
		}

		public static void Error(string msg, Exception e)
		{
			ManualLogSource log = _log;
			if (log != null)
			{
				log.LogError((object)("RLVL: " + Redact(msg + ": " + e)));
			}
		}
	}
	[BepInPlugin("RaidLedger.ValheimLink", "RL Valheim Link", "0.4.1")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "RaidLedger.ValheimLink";

		public const string Name = "RL Valheim Link";

		public const string Version = "0.4.1";

		private Harmony _harmony;

		public static Plugin Instance { get; private set; }

		private void Awake()
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			Instance = this;
			Log.Init(((BaseUnityPlugin)this).Logger);
			PluginConfig.Bind(((BaseUnityPlugin)this).Config);
			Log.RedactSecret(PluginConfig.ServerToken.Value);
			_harmony = new Harmony("RaidLedger.ValheimLink");
			_harmony.PatchAll(typeof(Patches));
			string text = GameAccess.SelfCheck();
			if (text != null)
			{
				Log.Error("game member access check failed (game update changed a field?): " + text);
			}
			Log.Info(string.Format("loaded v{0} (contract v{1}); game member access {2}; waiting for ZNet to decide client/server role", "0.4.1", 1, (text == null) ? "ok" : "BROKEN"));
		}

		private void Update()
		{
			LinkChannel.Tick();
			ClientHalf.Tick();
			ServerHalf.Tick();
			if (Roles.IsServer)
			{
				ChestDump.Tick(DateTime.UtcNow);
			}
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class Patches
	{
		[HarmonyPatch(typeof(ZNet), "Awake")]
		[HarmonyPostfix]
		private static void ZNet_Awake_Postfix(ZNet __instance)
		{
			Roles.OnZNetAwake(__instance);
		}

		[HarmonyPatch(typeof(ZNet), "OnDestroy")]
		[HarmonyPostfix]
		private static void ZNet_OnDestroy_Postfix()
		{
			Roles.OnZNetDestroyed();
		}

		[HarmonyPatch(typeof(Game), "SavePlayerProfile")]
		[HarmonyPostfix]
		private static void Game_SavePlayerProfile_Postfix(bool setLogoutPoint)
		{
			ClientHalf.OnProfileSaved(setLogoutPoint);
		}

		[HarmonyPatch(typeof(Terminal), "InitTerminal")]
		[HarmonyPostfix]
		private static void Terminal_InitTerminal_Postfix()
		{
			ConsoleCommands.Register();
		}
	}
	internal static class Roles
	{
		public static bool IsServer { get; private set; }

		public static bool IsClient { get; private set; }

		public static void OnZNetAwake(ZNet znet)
		{
			try
			{
				IsServer = znet.IsServer();
				IsClient = !znet.IsDedicated();
				LinkChannel.Register(IsServer);
				Log.Info($"ZNet up: server={IsServer} client={IsClient} dedicated={znet.IsDedicated()}");
				if (IsServer)
				{
					ServerHalf.Start();
					if (znet.IsDedicated())
					{
						ChestDump.Start();
					}
				}
				if (IsClient)
				{
					ClientHalf.Start();
				}
			}
			catch (Exception ex)
			{
				Log.Error("role start failed: " + ex);
			}
		}

		public static void OnZNetDestroyed()
		{
			try
			{
				if (IsClient)
				{
					ClientHalf.Stop();
				}
				if (IsServer)
				{
					ChestDump.Stop();
					ServerHalf.Stop();
				}
				LinkChannel.Reset();
			}
			catch (Exception ex)
			{
				Log.Error("role stop failed: " + ex);
			}
			IsServer = (IsClient = false);
		}
	}
	internal static class PluginConfig
	{
		public static ConfigEntry<string> BaseUrl;

		public static ConfigEntry<string> ServerToken;

		public static ConfigEntry<string> ServerId;

		public static ConfigEntry<string> MessagesPath;

		public static ConfigEntry<string> BlobsPath;

		public static ConfigEntry<int> UploadIntervalSeconds;

		public static ConfigEntry<int> UploadMaxMessagesPerMinute;

		public static ConfigEntry<int> UploadMaxBlobsPerMinute;

		public static ConfigEntry<int> UploadMaxBlobBytes;

		public static ConfigEntry<bool> UploadGzip;

		public static ConfigEntry<string> SpoolDirectory;

		public static ConfigEntry<bool> TrustClientSteamId;

		public static ConfigEntry<bool> RequireExactModVersion;

		public static ConfigEntry<string> ItemsExportBy;

		public static ConfigEntry<string> WorldAllowlist;

		public static ConfigEntry<bool> BaseMapEnabled;

		public static ConfigEntry<int> BaseMapRowsPerTick;

		public static ConfigEntry<int> BaseMapTextureSize;

		public static ConfigEntry<float> BaseMapPixelSizeMetres;

		public static ConfigEntry<bool> OptOut;

		public static ConfigEntry<bool> SendCharacter;

		public static ConfigEntry<bool> SendMap;

		public static ConfigEntry<int> MinSecondsBetweenCharacter;

		public static ConfigEntry<int> MinSecondsBetweenMap;

		public static ConfigEntry<int> IconSize;

		public static ConfigEntry<bool> ChestsEnabled;

		public static ConfigEntry<bool> ChestsIncludeVehicles;

		public static ConfigEntry<bool> ChestsDiagnosticDump;

		public static ConfigEntry<int> ChestsIntervalMinutes;

		public static ConfigEntry<int> ChestsStacksPerTick;

		public static ConfigEntry<string> ChestsExcludePrefabs;

		public static ConfigEntry<string> ChestsExtraPrefabs;

		public static ConfigEntry<int> ChestsDumpRuns;

		public static ConfigEntry<int> ChestsDumpRerunMinutes;

		public static ConfigEntry<string> ChestsDumpMaterials;

		public static ConfigEntry<bool> RecipesEnabled;

		public static void Bind(ConfigFile cfg)
		{
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Expected O, but got Unknown
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Expected O, but got Unknown
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Expected O, but got Unknown
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Expected O, but got Unknown
			//IL_020c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0216: Expected O, but got Unknown
			//IL_0276: Unknown result type (might be due to invalid IL or missing references)
			//IL_0280: Expected O, but got Unknown
			//IL_02ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b5: Expected O, but got Unknown
			//IL_02e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ed: Expected O, but got Unknown
			//IL_0365: Unknown result type (might be due to invalid IL or missing references)
			//IL_036f: Expected O, but got Unknown
			//IL_039a: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a4: Expected O, but got Unknown
			//IL_03cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d6: Expected O, but got Unknown
			//IL_0415: Unknown result type (might be due to invalid IL or missing references)
			//IL_041f: Expected O, but got Unknown
			//IL_044a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0454: Expected O, but got Unknown
			//IL_04ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_04f4: Expected O, but got Unknown
			//IL_0518: Unknown result type (might be due to invalid IL or missing references)
			//IL_0522: Expected O, but got Unknown
			BaseUrl = cfg.Bind<string>("RaidLedger", "BaseUrl", "", "Server only. Your Raid Ledger instance's base URL (scheme + host, no trailing slash). Empty = uploads off.");
			ServerToken = cfg.Bind<string>("RaidLedger", "ServerToken", "", "Server only. Per-server bearer token issued by Raid Ledger admin for THIS game server. Never logged.");
			ServerId = cfg.Bind<string>("RaidLedger", "ServerId", "", "Server only. Raid Ledger's id for this game server, issued with the token; goes into every message.");
			MessagesPath = cfg.Bind<string>("RaidLedger", "MessagesPath", "/api/plugins/valheim/link/messages/{kind}", "Server only. POST route for messages; {kind} is replaced by character | map | items | containers | recipes (ROK-1833). Change only to point at a mock on another path.");
			BlobsPath = cfg.Bind<string>("RaidLedger", "BlobsPath", "/api/plugins/valheim/link/blobs/{sha256}", "Server only. PUT route for PNG blobs; {sha256} is replaced (ROK-1833).");
			UploadIntervalSeconds = cfg.Bind<int>("Upload", "IntervalSeconds", 5, new ConfigDescription("Server only. Minimum seconds between HTTP requests to Raid Ledger.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 600), Array.Empty<object>()));
			UploadMaxMessagesPerMinute = cfg.Bind<int>("Upload", "MaxMessagesPerMinute", 30, new ConfigDescription("Server only. Self-imposed message POSTs per minute; Raid Ledger allows 60 per server before 429.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 60), Array.Empty<object>()));
			UploadMaxBlobsPerMinute = cfg.Bind<int>("Upload", "MaxBlobsPerMinute", 10, new ConfigDescription("Server only. Self-imposed blob PUTs per minute; Raid Ledger allows 20 per server before 429.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), Array.Empty<object>()));
			UploadMaxBlobBytes = cfg.Bind<int>("Upload", "MaxBlobBytes", 10485760, new ConfigDescription("Largest PNG the mod will upload (bytes). 10 MiB while Raid Ledger's nginx caps bodies there (ROK-1850); the contract allows 16 MiB. Also bounds the client's atlas.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1048576, 16777216), Array.Empty<object>()));
			UploadGzip = cfg.Bind<bool>("Upload", "Gzip", true, "Server only. Send message bodies with Content-Encoding: gzip.");
			SpoolDirectory = cfg.Bind<string>("Upload", "SpoolDirectory", "", "Server only. Pending uploads + blob cache. Empty = <BepInEx>/RLValheimLink/spool.");
			TrustClientSteamId = cfg.Bind<bool>("Server", "TrustClientSteamId", false, "Server only. Accept the client's steamId claim when the peer's socket identity is not a SteamID64 (crossplay/PlayFab). Off = such peers are skipped.");
			RequireExactModVersion = cfg.Bind<bool>("Server", "RequireExactModVersion", false, "Server only. Reject clients whose mod version differs from the server's. Off (default): any client speaking the same RPC protocol is accepted and its version is logged, so server-side updates never force a modpack update.");
			ItemsExportBy = cfg.Bind<string>("Server", "ItemsExportBy", "admins", new ConfigDescription("Server only. Which clients may produce the items export when asked: admins | anyone | nobody.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "admins", "anyone", "nobody" }), Array.Empty<object>()));
			WorldAllowlist = cfg.Bind<string>("Server", "WorldAllowlist", "", "Server only. Comma-separated world names (case-insensitive) this server links to Raid Ledger; empty = every world. When the loaded world is not listed the link stays idle: no base map, clients are told the world is not linked, nothing is spooled or uploaded. Set it to your production world when the same server is sometimes booted into a test world.");
			BaseMapEnabled = cfg.Bind<bool>("BaseMap", "Enabled", true, "Server only. Render the world's base map PNG once per (world, game version).");
			BaseMapRowsPerTick = cfg.Bind<int>("BaseMap", "RowsPerTick", 4, new ConfigDescription("Server only. Rows rendered per Update tick (time-slicing; 2048 rows at 4/tick ≈ 10 s of ticks).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 256), Array.Empty<object>()));
			BaseMapTextureSize = cfg.Bind<int>("BaseMap", "TextureSize", 2048, new ConfigDescription("Server only. Edge of the rendered base map in pixels; must equal the clients' minimap textureSize (vanilla 2048) or the map is sent without a base map.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(64, 4096), Array.Empty<object>()));
			BaseMapPixelSizeMetres = cfg.Bind<float>("BaseMap", "PixelSizeMetres", 12f, new ConfigDescription("Server only. World metres per base-map pixel; must equal the clients' minimap pixel size (vanilla 12).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 256f), Array.Empty<object>()));
			OptOut = cfg.Bind<bool>("Client", "OptOut", false, "Client only. PER-PLAYER OPT-OUT: when true this game client sends nothing to the server (no character, map or items data).");
			SendCharacter = cfg.Bind<bool>("Client", "SendCharacter", true, "Client only. Send the character summary on save/logout/quit.");
			SendMap = cfg.Bind<bool>("Client", "SendMap", true, "Client only. Send explored map + pins on save/logout/quit when they changed.");
			MinSecondsBetweenCharacter = cfg.Bind<int>("Client", "MinSecondsBetweenCharacter", 30, new ConfigDescription("Client only. Throttle for character sends.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(5, 3600), Array.Empty<object>()));
			MinSecondsBetweenMap = cfg.Bind<int>("Client", "MinSecondsBetweenMap", 300, new ConfigDescription("Client only. Throttle for map sends.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(30, 86400), Array.Empty<object>()));
			IconSize = cfg.Bind<int>("Items", "IconSize", 64, new ConfigDescription("Client only. Icon edge in pixels for the items atlas (reduced automatically if the PNG would exceed 16 MiB).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(16, 128), Array.Empty<object>()));
			ChestsEnabled = cfg.Bind<bool>("Chests", "Enabled", true, "Dedicated server only. Scan every player-built container (chests, carts, ships) and send its contents to Raid Ledger as a `containers` snapshot: 2 minutes after start, then every IntervalMinutes. On by default; set false to switch it off. Needs the [RaidLedger] settings and a world in [Server] WorldAllowlist.");
			ChestsIntervalMinutes = cfg.Bind<int>("Chests", "IntervalMinutes", 10, new ConfigDescription("Dedicated server only. Minutes between chest scans, on a fixed grid from the first scan. Every scan is sent, even when nothing changed, so the site's \"scanned N min ago\" is honest.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(5, 120), Array.Empty<object>()));
			ChestsStacksPerTick = cfg.Bind<int>("Chests", "StacksPerTick", 200, new ConfigDescription("Dedicated server only. Item stacks decoded per server tick while a scan runs (time-slicing; 0.004 ms per stack measured on a production world).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(20, 5000), Array.Empty<object>()));
			ChestsIncludeVehicles = cfg.Bind<bool>("Chests", "IncludeVehicles", true, "Dedicated server only. Also scan carts and ships (flagged as vehicles; the site can hide them).");
			ChestsExcludePrefabs = cfg.Bind<string>("Chests", "ExcludePrefabs", "incinerator", "Dedicated server only. Comma-separated container prefabs never scanned. Default: the obliterator, whose contents are about to be destroyed.");
			ChestsExtraPrefabs = cfg.Bind<string>("Chests", "ExtraPrefabs", "", "Dedicated server only. Comma-separated container prefabs to scan although no build tool lists them (a modded chest placed some other way). Player-built only: a container without a creator is never sent.");
			ChestsDiagnosticDump = cfg.Bind<bool>("Chests", "DiagnosticDump", false, "Dedicated server only. The 0.2.x chest-inventory spike's diagnostic (docs/design-chests.md §7): 2 minutes after every start, log what the server can read from every container (lines start 'RLVL: chestdump'). Logs only. Off by default now that the chest scan ships; replaces Dump, which is ignored.");
			ChestsDumpRuns = cfg.Bind<int>("Chests", "DumpRuns", 2, new ConfigDescription("Dedicated server only. How many dumps to run after each start when DiagnosticDump is on; every dump after the first also reports what changed since the previous one.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 6), Array.Empty<object>()));
			ChestsDumpRerunMinutes = cfg.Bind<int>("Chests", "DumpRerunMinutes", 10, new ConfigDescription("Dedicated server only. Minutes between dumps (from the end of one to the start of the next).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 120), Array.Empty<object>()));
			ChestsDumpMaterials = cfg.Bind<string>("Chests", "DumpMaterials", "Wood,Stone,Resin", "Dedicated server only. Comma-separated item prefab names (up to 5) whose totals over all player-built containers each dump prints, to compare with what you see in the chests.");
			RecipesEnabled = cfg.Bind<bool>("Recipes", "Enabled", true, "Dedicated server only. Send the server's crafting recipes, build pieces, crafting stations and processing steps (smelter, kiln, fermenter, cooking, obliterator…) to Raid Ledger as a `recipes` snapshot, once per game version and server mod set, about 90 s after start. On by default; set false to switch it off.");
		}
	}
	internal static class RecipeSources
	{
		public sealed class BuildPiece
		{
			public string Tool;

			public GameObject Prefab;

			public Piece Piece;
		}

		public static List<BuildPiece> BuildPieces()
		{
			List<BuildPiece> list = new List<BuildPiece>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (GameObject item in ObjectDB.instance.m_items)
			{
				ItemDrop val = (((Object)(object)item != (Object)null) ? item.GetComponent<ItemDrop>() : null);
				PieceTable val2 = (((Object)(object)val != (Object)null && val.m_itemData != null && val.m_itemData.m_shared != null) ? val.m_itemData.m_shared.m_buildPieces : null);
				if ((Object)(object)val2 == (Object)null || val2.m_pieces == null)
				{
					continue;
				}
				foreach (GameObject piece in val2.m_pieces)
				{
					Piece val3 = (((Object)(object)piece != (Object)null) ? piece.GetComponent<Piece>() : null);
					if (!((Object)(object)val3 == (Object)null) && hashSet.Add(((Object)piece).name))
					{
						list.Add(new BuildPiece
						{
							Tool = ((Object)item).name,
							Prefab = piece,
							Piece = val3
						});
					}
				}
			}
			return list;
		}

		public static string PiecePrefab(GameObject pieceGo)
		{
			if (!((Object)(object)pieceGo == (Object)null))
			{
				return Names.Prefab(((Object)pieceGo).name);
			}
			return null;
		}

		public static string StationPrefab(CraftingStation cs)
		{
			if (!((Object)(object)cs == (Object)null))
			{
				return Names.Prefab(((Object)((Component)((Component)cs).transform.root).gameObject).name);
			}
			return null;
		}

		public static CraftingStation RootStation(CraftingStation cs)
		{
			if ((Object)(object)cs == (Object)null)
			{
				return null;
			}
			CraftingStation component = ((Component)((Component)cs).transform.root).GetComponent<CraftingStation>();
			if (!((Object)(object)component != (Object)null))
			{
				return cs;
			}
			return component;
		}

		public static Dictionary<string, CraftingStation> Stations(IEnumerable<BuildPiece> pieces)
		{
			Dictionary<string, CraftingStation> stations = new Dictionary<string, CraftingStation>(StringComparer.Ordinal);
			foreach (GameObject prefab in ZNetScene.instance.m_prefabs)
			{
				CraftingStation val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<CraftingStation>() : null);
				if ((Object)(object)val != (Object)null)
				{
					Add(val);
				}
			}
			foreach (Recipe recipe in ObjectDB.instance.m_recipes)
			{
				if (!((Object)(object)recipe == (Object)null))
				{
					if ((Object)(object)recipe.m_craftingStation != (Object)null)
					{
						Add(recipe.m_craftingStation);
					}
					if ((Object)(object)recipe.m_repairStation != (Object)null)
					{
						Add(recipe.m_repairStation);
					}
				}
			}
			foreach (BuildPiece piece in pieces)
			{
				if ((Object)(object)piece.Piece.m_craftingStation != (Object)null)
				{
					Add(piece.Piece.m_craftingStation);
				}
			}
			return stations;
			void Add(CraftingStation cs)
			{
				string text = StationPrefab(cs);
				if (text != null && !stations.ContainsKey(text))
				{
					stations[text] = RootStation(cs);
				}
			}
		}

		public static List<KeyValuePair<string, Sprite>> Icons()
		{
			List<BuildPiece> list = BuildPieces();
			List<KeyValuePair<string, Sprite>> list2 = new List<KeyValuePair<string, Sprite>>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (BuildPiece item in list)
			{
				string text = PiecePrefab(item.Prefab);
				if (text != null && (Object)(object)item.Piece.m_icon != (Object)null && hashSet.Add(text))
				{
					list2.Add(new KeyValuePair<string, Sprite>(text, item.Piece.m_icon));
				}
			}
			foreach (KeyValuePair<string, CraftingStation> item2 in Stations(list))
			{
				if ((Object)(object)item2.Value != (Object)null && (Object)(object)item2.Value.m_icon != (Object)null && hashSet.Add(item2.Key))
				{
					list2.Add(new KeyValuePair<string, Sprite>(item2.Key, item2.Value.m_icon));
				}
			}
			list2.Sort((KeyValuePair<string, Sprite> a, KeyValuePair<string, Sprite> b) => string.CompareOrdinal(a.Key, b.Key));
			return list2;
		}
	}
}
namespace RlValheimLink.Server
{
	internal sealed class BaseMapRenderer
	{
		private readonly string _cacheDir;

		private readonly int _size;

		private readonly float _pixel;

		private readonly int _rowsPerTick;

		private byte[] _rgba;

		private int _nextRow;

		private Thread _encoder;

		private byte[] _png;

		private Exception _encodeError;

		private string _cacheKey;

		private Stopwatch _sw;

		private const float WaterLevel = 30f;

		public BinaryRef Ref { get; private set; }

		public byte[] Png { get; private set; }

		public bool Done => Ref != null;

		public bool Busy
		{
			get
			{
				if (_rgba == null)
				{
					return _encoder != null;
				}
				return true;
			}
		}

		public BaseMapRenderer(string cacheDir, int size, float pixelSizeMetres, int rowsPerTick)
		{
			_cacheDir = cacheDir;
			_size = size;
			_pixel = pixelSizeMetres;
			_rowsPerTick = Math.Max(1, rowsPerTick);
			Directory.CreateDirectory(cacheDir);
		}

		public bool Begin(string worldUid, string gameVersion)
		{
			_cacheKey = $"{worldUid}-{gameVersion}-{_size}";
			string path = Path.Combine(_cacheDir, _cacheKey + ".png");
			if (File.Exists(path))
			{
				try
				{
					byte[] array = File.ReadAllBytes(path);
					int num = default(int);
					int num2 = default(int);
					if (Png.TryReadSize(array, ref num, ref num2) && num == _size && num2 == _size)
					{
						Png = array;
						Ref = Png.MakeRef(array);
						Log.Info($"base map: cached {_size}² PNG ({array.Length} bytes) for world {worldUid} @ {gameVersion}");
						return true;
					}
				}
				catch (Exception ex)
				{
					Log.Warn("base map cache unreadable: " + ex.Message);
				}
			}
			if (WorldGenerator.instance == null)
			{
				Log.Warn("base map: WorldGenerator not ready; will retry");
				_cacheKey = null;
				return false;
			}
			_rgba = new byte[_size * _size * 4];
			_nextRow = 0;
			_sw = Stopwatch.StartNew();
			Log.Info($"base map: rendering {_size}² at {_pixel} m/px, {_rowsPerTick} rows per tick");
			return false;
		}

		public bool Tick()
		{
			if (Ref != null)
			{
				return false;
			}
			if (_cacheKey == null)
			{
				return false;
			}
			if (_rgba != null)
			{
				WorldGenerator instance = WorldGenerator.instance;
				if (instance == null)
				{
					return false;
				}
				int num = Math.Min(_size, _nextRow + _rowsPerTick);
				for (int i = _nextRow; i < num; i++)
				{
					RenderRow(instance, i);
				}
				_nextRow = num;
				if (_nextRow < _size)
				{
					return false;
				}
				byte[] rgba = _rgba;
				_rgba = null;
				Log.Info($"base map: sampled in {_sw.ElapsedMilliseconds} ms; encoding PNG");
				_encoder = new Thread((ThreadStart)delegate
				{
					try
					{
						_png = Png.EncodeRgba(rgba, _size, _size);
					}
					catch (Exception encodeError)
					{
						_encodeError = encodeError;
					}
				})
				{
					IsBackground = true,
					Name = "RLVL-basemap-png"
				};
				_encoder.Start();
				return false;
			}
			if (_encoder != null)
			{
				if (_encoder.IsAlive)
				{
					return false;
				}
				_encoder = null;
				if (_encodeError != null)
				{
					Log.Error("base map: PNG encode failed", _encodeError);
					_cacheKey = null;
					return false;
				}
				Png = _png;
				_png = null;
				Ref = Png.MakeRef(Png);
				try
				{
					File.WriteAllBytes(Path.Combine(_cacheDir, _cacheKey + ".png"), Png);
				}
				catch (Exception ex)
				{
					Log.Warn("base map: cache write failed: " + ex.Message);
				}
				Log.Info($"base map: ready, {Png.Length} bytes, sha256={Ref.Sha256.Substring(0, 12)}… in {_sw.ElapsedMilliseconds} ms");
				return true;
			}
			return false;
		}

		private void RenderRow(WorldGenerator gen, int py)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			float num = (float)(py - _size / 2) * _pixel;
			int num2 = _size - 1 - py;
			for (int i = 0; i < _size; i++)
			{
				float num3 = (float)(i - _size / 2) * _pixel;
				Biome biome = gen.GetBiome(num3, num, 0.02f, false);
				float height = gen.GetHeight(num3, num);
				Color32 val = Shade(biome, height);
				int num4 = (num2 * _size + i) * 4;
				_rgba[num4] = val.r;
				_rgba[num4 + 1] = val.g;
				_rgba[num4 + 2] = val.b;
				_rgba[num4 + 3] = byte.MaxValue;
			}
		}

		private static Color32 Shade(Biome biome, float height)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Invalid comparison between Unknown and I4
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Invalid comparison between Unknown and I4
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected I4, but got Unknown
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Invalid comparison between Unknown and I4
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Invalid comparison between Unknown and I4
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Invalid comparison between Unknown and I4
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Invalid comparison between Unknown and I4
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Invalid comparison between Unknown and I4
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Invalid comparison between Unknown and I4
			//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01db: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0216: Unknown result type (might be due to invalid IL or missing references)
			//IL_0238: Unknown result type (might be due to invalid IL or missing references)
			Color val = default(Color);
			if ((int)biome <= 16)
			{
				switch (biome - 1)
				{
				case 0:
					goto IL_0063;
				case 1:
					goto IL_0099;
				case 3:
					goto IL_00b4;
				case 2:
					goto IL_0147;
				}
				if ((int)biome != 8)
				{
					if ((int)biome != 16)
					{
						goto IL_0147;
					}
					((Color)(ref val))..ctor(0.9f, 0.8f, 0.5f);
				}
				else
				{
					((Color)(ref val))..ctor(0.4f, 0.55f, 0.25f);
				}
			}
			else if ((int)biome <= 64)
			{
				if ((int)biome != 32)
				{
					if ((int)biome != 64)
					{
						goto IL_0147;
					}
					((Color)(ref val))..ctor(0.85f, 0.9f, 1f);
				}
				else
				{
					((Color)(ref val))..ctor(0.8f, 0.3f, 0.25f);
				}
			}
			else if ((int)biome != 256)
			{
				if ((int)biome != 512)
				{
					goto IL_0147;
				}
				((Color)(ref val))..ctor(0.5f, 0.5f, 0.55f);
			}
			else
			{
				((Color)(ref val))..ctor(0.1f, 0.3f, 0.55f);
			}
			goto IL_015d;
			IL_0099:
			((Color)(ref val))..ctor(0.6f, 0.5f, 0.4f);
			goto IL_015d;
			IL_00b4:
			((Color)(ref val))..ctor(1f, 1f, 1f);
			goto IL_015d;
			IL_0147:
			((Color)(ref val))..ctor(0.35f, 0.35f, 0.35f);
			goto IL_015d;
			IL_0063:
			((Color)(ref val))..ctor(0.575f, 0.75f, 0.35f);
			goto IL_015d;
			IL_015d:
			if (height < 30f)
			{
				float num = Mathf.Clamp01((30f - height) / 60f);
				Color val2 = default(Color);
				((Color)(ref val2))..ctor(0.12f, 0.32f, 0.6f);
				val = Color.Lerp(new Color(0.25f, 0.5f, 0.7f), val2, num);
			}
			else
			{
				float num2 = Mathf.Clamp01((height - 30f) / 160f);
				val = Color.Lerp(val * 0.85f, val * 1.15f, num2);
			}
			return new Color32((byte)Mathf.Clamp(Mathf.RoundToInt(val.r * 255f), 0, 255), (byte)Mathf.Clamp(Mathf.RoundToInt(val.g * 255f), 0, 255), (byte)Mathf.Clamp(Mathf.RoundToInt(val.b * 255f), 0, 255), byte.MaxValue);
		}
	}
	internal static class ChestDump
	{
		private sealed class PrefabInfo
		{
			public string Name;

			public int Hash;

			public int Width;

			public int Height;

			public PrivacySetting Privacy;

			public bool Buildable;

			public bool Vehicle;

			public readonly List<ZDO> Found = new List<ZDO>();

			public readonly List<ZDOID> Ids = new List<ZDOID>();

			public readonly HashSet<ZDOID> Seen = new HashSet<ZDOID>();

			public int Index;

			public int Calls;

			public bool Done;

			public int Raw;

			public int Duplicates;

			public int Gone;

			public int Creator0;

			public int Counted;

			public int Empty;

			public int Unreadable;

			public int Stacks;

			public int Dropped;

			public int HeaderMismatch;

			public int RowsBeyondPrefab;

			public int MaxRow = -1;

			public double DiscoveryMs;

			public double DecodeMs;
		}

		private sealed class Decoded
		{
			public string Source;

			public int HeaderVersion = int.MinValue;

			public int HeaderCount = -1;

			public List<ItemData> Items;

			public string Error;

			public double Ms;

			public int MaxRow = -1;

			public bool HeaderPlausible
			{
				get
				{
					if (HeaderVersion >= 100 && HeaderVersion <= 200)
					{
						return HeaderCount >= 0;
					}
					return false;
				}
			}
		}

		private sealed class Loader
		{
			public string Name;

			public Func<int, int, Inventory> Make;

			public Action<Inventory, ZPackage> Load;
		}

		private sealed class Snapshot
		{
			public string Prefab;

			public Vector3 Pos;

			public long Creator;

			public bool Private;

			public ZDOID Id;

			public bool Unreadable;

			public readonly SortedDictionary<string, int> Stacks = new SortedDictionary<string, int>(StringComparer.Ordinal);
		}

		private const int FirstDelaySeconds = 120;

		private const int StacksPerTick = 200;

		private const int TallRows = 64;

		private const int MaxProbes = 6;

		private const int MaxListed = 10;

		private const int MaxCallsPerPrefab = 100000;

		private static readonly CultureInfo Inv = CultureInfo.InvariantCulture;

		private static IEnumerator<object> _run;

		private static int _runNumber;

		private static int _runsTotal;

		private static DateTime _nextRunAt = DateTime.MaxValue;

		private static Dictionary<string, Snapshot> _previous;

		private static long[] _previousMaterials;

		private static DateTime _previousAt;

		private static string _chosenLoader;

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

		private static int _itemsHash;

		private static int _itemDataHash;

		private static int _itemDropAwakes;

		public static void Start()
		{
			_run = null;
			_runNumber = 0;
			_previous = null;
			_previousMaterials = null;
			_chosenLoader = null;
			if (!PluginConfig.ChestsDiagnosticDump.Value)
			{
				_nextRunAt = DateTime.MaxValue;
				return;
			}
			_runsTotal = PluginConfig.ChestsDumpRuns.Value;
			_nextRunAt = DateTime.UtcNow.AddSeconds(120.0);
			Log.Info(string.Format("chestdump: ON (Chests.DiagnosticDump) — {0} run(s), the first in {1} s, then {2} min apart; logs only, uploads nothing; materials {3}", _runsTotal, 120, PluginConfig.ChestsDumpRerunMinutes.Value, string.Join(",", Materials())));
		}

		public static void Stop()
		{
			_run = null;
			_nextRunAt = DateTime.MaxValue;
		}

		public static void Tick(DateTime now)
		{
			if (_run != null)
			{
				bool flag;
				try
				{
					flag = _run.MoveNext();
				}
				catch (Exception e)
				{
					Log.Error($"chestdump[r{_runNumber}] aborted", e);
					flag = false;
				}
				if (!flag)
				{
					_run = null;
					if (_runNumber < _runsTotal)
					{
						_nextRunAt = now.AddMinutes(PluginConfig.ChestsDumpRerunMinutes.Value);
						Log.Info($"chestdump: run {_runNumber + 1} of {_runsTotal} in {PluginConfig.ChestsDumpRerunMinutes.Value} min (reports what changed since this run)");
					}
					else
					{
						_nextRunAt = DateTime.MaxValue;
						Log.Info($"chestdump: finished ({_runsTotal} run(s)); it runs again after the next start while [Chests] DiagnosticDump = true");
					}
				}
			}
			else if (!(now < _nextRunAt) && ZDOMan.instance != null && !((Object)(object)ZNetScene.instance == (Object)null) && !((Object)(object)ObjectDB.instance == (Object)null))
			{
				_runNumber++;
				_run = Run(_runNumber).GetEnumerator();
			}
		}

		private static IEnumerable<object> Run(int run)
		{
			string r = "r" + run;
			Stopwatch total = Stopwatch.StartNew();
			Stopwatch stopwatch = Stopwatch.StartNew();
			int ticks = 0;
			List<PrefabInfo> prefabs = null;
			Safe(r, "start", delegate
			{
				Header(r, run);
			});
			Safe(r + ".c5", "prefab scan", delegate
			{
				prefabs = ContainerPrefabs(r, run == 1);
			});
			if (prefabs == null || prefabs.Count == 0)
			{
				Log.Warn("chestdump[" + r + "] no prefab with a Container in ZNetScene; nothing to do");
				yield break;
			}
			if (run == 1)
			{
				Safe(r + ".c5", "station scan", delegate
				{
					Stations(r);
				});
				Safe(r + ".c7", "1.0.20 fields", delegate
				{
					NewFields(r);
				});
			}
			Log.Info("chestdump[" + r + "] setup tick " + Ms(stopwatch.Elapsed.TotalMilliseconds) + " ms");
			yield return null;
			ticks++;
			int discoveryTicks = 0;
			double discoveryMaxTick = 0.0;
			Stopwatch discovery = Stopwatch.StartNew();
			while (prefabs.Any((PrefabInfo prefabInfo) => !prefabInfo.Done))
			{
				if (ZDOMan.instance == null)
				{
					Log.Warn("chestdump[" + r + "] ZDOMan went away; stopping");
					yield break;
				}
				Stopwatch stopwatch2 = Stopwatch.StartNew();
				foreach (PrefabInfo p in prefabs)
				{
					if (p.Done)
					{
						continue;
					}
					Stopwatch stopwatch3 = Stopwatch.StartNew();
					if (!Safe(r + ".c3", "discovery " + p.Name, delegate
					{
						p.Done = ZDOMan.instance.GetAllZDOsWithPrefabIterative(p.Name, p.Found, ref p.Index);
					}))
					{
						p.Done = true;
					}
					p.DiscoveryMs += stopwatch3.Elapsed.TotalMilliseconds;
					foreach (ZDO item in p.Found)
					{
						if (item != null)
						{
							p.Raw++;
							if (p.Seen.Add(item.m_uid))
							{
								p.Ids.Add(item.m_uid);
							}
							else
							{
								p.Duplicates++;
							}
						}
					}
					p.Found.Clear();
					if (++p.Calls >= 100000 && !p.Done)
					{
						p.Done = true;
						Log.Warn($"chestdump[{r}.c3] discovery {p.Name}: gave up after {p.Calls} calls");
					}
					break;
				}
				discoveryMaxTick = Math.Max(discoveryMaxTick, stopwatch2.Elapsed.TotalMilliseconds);
				discoveryTicks++;
				yield return null;
				ticks++;
			}
			Log.Info($"chestdump[{r}.c3] discovery: {prefabs.Count} container prefabs, {prefabs.Sum((PrefabInfo prefabInfo) => prefabInfo.Raw)} returned, {prefabs.Sum((PrefabInfo prefabInfo) => prefabInfo.Ids.Count)} unique, {prefabs.Sum((PrefabInfo prefabInfo) => prefabInfo.Duplicates)} duplicates removed, {Ms(discovery.Elapsed.TotalMilliseconds)} ms over {discoveryTicks} ticks (busiest tick {Ms(discoveryMaxTick)} ms, {prefabs.Max((PrefabInfo prefabInfo) => prefabInfo.Calls)} calls for the largest prefab); zdosResident={GameAccess.ResidentZdoCount()} playersConnected={PeersConnected()}");
			if (run == 1)
			{
				Stopwatch stopwatch4 = Stopwatch.StartNew();
				Safe(r + ".c2", "member visibility", delegate
				{
					Visibility(r);
				});
				Safe(r + ".c2", "probes", delegate
				{
					Probes(r, prefabs);
				});
				Log.Info("chestdump[" + r + ".c2] probe tick " + Ms(stopwatch4.Elapsed.TotalMilliseconds) + " ms");
				yield return null;
				ticks++;
			}
			Loader loader = Loaders().FirstOrDefault((Loader l) => l.Name == _chosenLoader) ?? Loaders()[1];
			string[] materials = Materials();
			long[] matTotals = new long[materials.Length];
			long[] matShared = new long[materials.Length];
			int[] matChests = new int[materials.Length];
			Dictionary<string, long> itemTotals = new Dictionary<string, long>(StringComparer.Ordinal);
			SortedDictionary<string, int> sources = new SortedDictionary<string, int>(StringComparer.Ordinal);
			Dictionary<string, Snapshot> snapshots = new Dictionary<string, Snapshot>(StringComparer.Ordinal);
			int stacksThisTick = 0;
			int decodeTicks = 0;
			int collisions = 0;
			double decodeMaxTick = 0.0;
			Stopwatch decodeAll = Stopwatch.StartNew();
			Stopwatch decodeTick = Stopwatch.StartNew();
			foreach (PrefabInfo p2 in prefabs)
			{
				foreach (ZDOID id in p2.Ids)
				{
					if (ZDOMan.instance == null)
					{
						Log.Warn("chestdump[" + r + "] ZDOMan went away; stopping");
						yield break;
					}
					ZDO zdo = ZDOMan.instance.GetZDO(id);
					if (zdo == null || zdo.GetPrefab() != p2.Hash)
					{
						p2.Gone++;
						continue;
					}
					long num = zdo.GetLong(ZDOVars.s_creator, 0L);
					if (num == 0L)
					{
						p2.Creator0++;
						continue;
					}
					p2.Counted++;
					Decoded d = null;
					Safe(r + ".c3", "decode " + p2.Name, delegate
					{
						d = Decode(zdo, p2, loader);
					});
					Snapshot snapshot = new Snapshot
					{
						Prefab = p2.Name,
						Pos = zdo.GetPosition(),
						Creator = num,
						Private = ((int)p2.Privacy == 0),
						Id = id
					};
					string key = d?.Source ?? "none";
					sources[key] = (sources.TryGetValue(key, out var value) ? value : 0) + 1;
					if (d == null || d.Error != null)
					{
						p2.Unreadable++;
						snapshot.Unreadable = true;
					}
					else if (d.Source == null)
					{
						p2.Empty++;
					}
					else
					{
						p2.DecodeMs += d.Ms;
						p2.Stacks += d.Items.Count;
						stacksThisTick += d.Items.Count;
						if (d.HeaderPlausible)
						{
							if (d.HeaderCount >= d.Items.Count)
							{
								p2.Dropped += d.HeaderCount - d.Items.Count;
							}
							else
							{
								p2.HeaderMismatch++;
							}
						}
						else
						{
							p2.HeaderMismatch++;
						}
						p2.MaxRow = Math.Max(p2.MaxRow, d.MaxRow);
						if (d.MaxRow >= p2.Height)
						{
							p2.RowsBeyondPrefab++;
						}
						bool[] array = new bool[materials.Length];
						foreach (ItemData item2 in d.Items)
						{
							string text = ItemName(item2);
							string key2 = text + " q" + item2.m_quality.ToString(Inv);
							snapshot.Stacks[key2] = (snapshot.Stacks.TryGetValue(key2, out var value2) ? value2 : 0) + item2.m_stack;
							itemTotals[text] = (itemTotals.TryGetValue(text, out var value3) ? value3 : 0) + item2.m_stack;
							for (int num2 = 0; num2 < materials.Length; num2++)
							{
								if (string.Equals(text, materials[num2], StringComparison.OrdinalIgnoreCase))
								{
									matTotals[num2] += item2.m_stack;
									array[num2] = true;
									if (!snapshot.Private)
									{
										matShared[num2] += item2.m_stack;
									}
								}
							}
						}
						for (int num3 = 0; num3 < materials.Length; num3++)
						{
							if (array[num3])
							{
								matChests[num3]++;
							}
						}
					}
					string key3 = SnapshotKey(snapshot);
					if (snapshots.ContainsKey(key3))
					{
						int num4 = collisions;
						collisions = num4 + 1;
					}
					else
					{
						snapshots[key3] = snapshot;
					}
					if (stacksThisTick >= 200)
					{
						decodeMaxTick = Math.Max(decodeMaxTick, decodeTick.Elapsed.TotalMilliseconds);
						stacksThisTick = 0;
						int num4 = decodeTicks;
						decodeTicks = num4 + 1;
						yield return null;
						ticks++;
						decodeTick.Restart();
					}
				}
			}
			decodeMaxTick = Math.Max(decodeMaxTick, decodeTick.Elapsed.TotalMilliseconds);
			decodeTicks++;
			Safe(r + ".c3", "counts", delegate
			{
				Counts(r, prefabs, loader, sources, decodeAll.Elapsed.TotalMilliseconds, decodeTicks, decodeMaxTick, collisions);
			});
			Safe(r + ".c3", "materials", delegate
			{
				MaterialLines(r, materials, matTotals, matShared, matChests, itemTotals);
			});
			Safe(r + ".c4", "creator", delegate
			{
				Creators(r, snapshots);
			});
			if (_previous != null)
			{
				Safe(r + ".c6", "freshness", delegate
				{
					Freshness(r, snapshots, materials, matTotals);
				});
			}
			_previous = snapshots;
			_previousMaterials = matTotals;
			_previousAt = DateTime.UtcNow;
			Log.Info(string.Format("chestdump[{0}] done in {1} s over {2} ticks", r, total.Elapsed.TotalSeconds.ToString("0.0", Inv), ticks));
		}

		private static void Header(string r, int run)
		{
			_names = GameAccess.ZdoVarNames();
			_itemsHash = ZDOVars.s_items;
			int? num = GameAccess.ZdoVar("s_itemData");
			int? zdoVar = GameAccess.ZdoVar("s_creatorIndex");
			_itemDataHash = num ?? StringExtensionMethods.GetStableHashCode("itemData");
			Log.Info($"chestdump[{r}] start (run {run} of {_runsTotal}): game={GameInfo.GameVersion()} dedicated={(Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsDedicated()} world={GameInfo.WorldUid()} zdosResident={GameAccess.ResidentZdoCount()} playersConnected={PeersConnected()} zdoVarNames={_names.Count}");
			if (run <= 1)
			{
				Log.Info("chestdump[" + r + ".c1] hashes: " + Compare("items", ZDOVars.s_items) + "; " + Compare("itemData", num) + "; " + Compare("creator", ZDOVars.s_creator) + "; " + Compare("creatorIndex", zdoVar));
			}
		}

		private static string Compare(string literal, int? zdoVar)
		{
			int stableHashCode = StringExtensionMethods.GetStableHashCode(literal);
			string text = (zdoVar.HasValue ? zdoVar.Value.ToString(Inv) : "missing");
			string text2 = ((!zdoVar.HasValue) ? "no such ZDOVars field" : ((zdoVar.Value == stableHashCode) ? "same" : "DIFFERENT"));
			return $"\"{literal}\"={stableHashCode} ZDOVars.s_{literal}={text} ({text2})";
		}

		private static List<PrefabInfo> ContainerPrefabs(string r, bool logIt)
		{
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			HashSet<string> hashSet = BuildablePieces();
			List<PrefabInfo> list = new List<PrefabInfo>();
			foreach (GameObject prefab in ZNetScene.instance.m_prefabs)
			{
				if ((Object)(object)prefab == (Object)null)
				{
					continue;
				}
				Container[] componentsInChildren = prefab.GetComponentsInChildren<Container>(true);
				if (componentsInChildren.Length == 0)
				{
					continue;
				}
				Container val = componentsInChildren[0];
				int num;
				if (!((Object)(object)prefab.GetComponent<Ship>() != (Object)null) && !((Object)(object)prefab.GetComponent<Vagon>() != (Object)null))
				{
					if (GameAccess.TryRead(val, "m_rootObjectOverride", out var value))
					{
						Object val2 = (Object)((value is Object) ? value : null);
						if (val2 != null)
						{
							num = ((val2 != (Object)null) ? 1 : 0);
							goto IL_0093;
						}
					}
					num = 0;
				}
				else
				{
					num = 1;
				}
				goto IL_0093;
				IL_0093:
				bool flag = (byte)num != 0;
				PrefabInfo prefabInfo = new PrefabInfo
				{
					Name = ((Object)prefab).name,
					Hash = StringExtensionMethods.GetStableHashCode(((Object)prefab).name),
					Width = val.m_width,
					Height = val.m_height,
					Privacy = val.m_privacy,
					Buildable = hashSet.Contains(((Object)prefab).name),
					Vehicle = flag
				};
				list.Add(prefabInfo);
				if (logIt)
				{
					Log.Info(string.Format("chestdump[{0}.c5] Container {1}: size={2}x{3} privacy={4} buildable={5} piece={6} vehicle={7} on={8} containers={9} {10}", r, ((Object)prefab).name, val.m_width, val.m_height, val.m_privacy, prefabInfo.Buildable, (Object)(object)prefab.GetComponent<Piece>() != (Object)null, flag, ((Object)(object)((Component)val).gameObject == (Object)(object)prefab) ? "root" : ((Object)((Component)val).gameObject).name, componentsInChildren.Length, Fields(val, "m_checkGuardStone", "m_autoDestroyEmpty", "m_rootObjectOverride", "m_wagon")));
				}
			}
			if (logIt)
			{
				int num2 = ZNetScene.instance.m_nonNetViewPrefabs.Count((GameObject go) => (Object)(object)go != (Object)null && go.GetComponentsInChildren<Container>(true).Length != 0);
				Log.Info($"chestdump[{r}.c5] containers: {list.Count} prefabs ({list.Count((PrefabInfo p) => p.Buildable)} buildable, {list.Count((PrefabInfo p) => p.Vehicle)} vehicles, {list.Count((PrefabInfo p) => (int)p.Privacy == 0)} private); {hashSet.Count} buildable pieces from tools; {num2} non-netview prefabs with a Container (never ZDOs)");
			}
			return list;
		}

		private static HashSet<string> BuildablePieces()
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (GameObject item in ObjectDB.instance.m_items)
			{
				ItemDrop val = (((Object)(object)item != (Object)null) ? item.GetComponent<ItemDrop>() : null);
				PieceTable val2 = (((Object)(object)val != (Object)null && val.m_itemData != null && val.m_itemData.m_shared != null) ? val.m_itemData.m_shared.m_buildPieces : null);
				if ((Object)(object)val2 == (Object)null || val2.m_pieces == null)
				{
					continue;
				}
				foreach (GameObject piece in val2.m_pieces)
				{
					if ((Object)(object)piece != (Object)null)
					{
						hashSet.Add(((Object)piece).name);
					}
				}
			}
			return hashSet;
		}

		private static IEnumerable<(string prefab, Component comp)> StationComponents()
		{
			foreach (GameObject go in ZNetScene.instance.m_prefabs.Concat(ZNetScene.instance.m_nonNetViewPrefabs))
			{
				if (!((Object)(object)go == (Object)null))
				{
					Smelter[] componentsInChildren = go.GetComponentsInChildren<Smelter>(true);
					foreach (Smelter item in componentsInChildren)
					{
						yield return (prefab: ((Object)go).name, comp: (Component)(object)item);
					}
					Fermenter[] componentsInChildren2 = go.GetComponentsInChildren<Fermenter>(true);
					foreach (Fermenter item2 in componentsInChildren2)
					{
						yield return (prefab: ((Object)go).name, comp: (Component)(object)item2);
					}
					CookingStation[] componentsInChildren3 = go.GetComponentsInChildren<CookingStation>(true);
					foreach (CookingStation item3 in componentsInChildren3)
					{
						yield return (prefab: ((Object)go).name, comp: (Component)(object)item3);
					}
					Incinerator[] componentsInChildren4 = go.GetComponentsInChildren<Incinerator>(true);
					foreach (Incinerator item4 in componentsInChildren4)
					{
						yield return (prefab: ((Object)go).name, comp: (Component)(object)item4);
					}
					CraftingStation[] componentsInChildren5 = go.GetComponentsInChildren<CraftingStation>(true);
					foreach (CraftingStation item5 in componentsInChildren5)
					{
						yield return (prefab: ((Object)go).name, comp: (Component)(object)item5);
					}
				}
			}
		}

		private static void Stations(string r)
		{
			SortedDictionary<string, int> sortedDictionary = new SortedDictionary<string, int>(StringComparer.Ordinal);
			foreach (var item3 in StationComponents())
			{
				string item = item3.prefab;
				Component item2 = item3.comp;
				string name = ((object)item2).GetType().Name;
				sortedDictionary[name] = (sortedDictionary.TryGetValue(name, out var value) ? value : 0) + 1;
				string text = ((((Object)item2.gameObject).name == item) ? "" : (" on=" + ((Object)item2.gameObject).name));
				if (!(item2 is Smelter))
				{
					if (!(item2 is Fermenter))
					{
						if (!(item2 is CookingStation))
						{
							if (!(item2 is Incinerator))
							{
								if (item2 is CraftingStation)
								{
									Log.Info("chestdump[" + r + ".c5] CraftingStation " + item + text + ": " + Fields(item2, "m_name", "m_craftRequireRoof", "m_craftRequireFire", "m_showBasicRecipies", "m_rangeBuild"));
								}
								continue;
							}
							Log.Info("chestdump[" + r + ".c5] Incinerator " + item + text + ": " + Fields(item2, "m_defaultResult", "m_defaultCost", "m_container") + " conversions=" + List(item2, "m_conversions", (object e) => "p" + F(e, "m_priority") + " " + List(e, "m_requirements", (object q) => F(q, "m_resItem") + "×" + F(q, "m_amount"), "+") + "→" + F(e, "m_result") + "×" + F(e, "m_resultAmount") + ((F(e, "m_requireOnlyOneIngredient") == "True") ? " (any one)" : "")));
						}
						else
						{
							Log.Info("chestdump[" + r + ".c5] CookingStation " + item + text + ": " + Fields(item2, "m_name", "m_useFuel", "m_fuelItem", "m_maxFuel", "m_secPerFuel", "m_overCookedItem", "m_slots", "m_requireFire") + " conversions=" + List(item2, "m_conversion", (object e) => F(e, "m_from") + "→" + F(e, "m_to") + " " + F(e, "m_cookTime") + "s"));
						}
					}
					else
					{
						Log.Info("chestdump[" + r + ".c5] Fermenter " + item + text + ": " + Fields(item2, "m_name", "m_fermentationDuration", "m_tapDelay") + " conversions=" + List(item2, "m_conversion", (object e) => F(e, "m_from") + "→" + F(e, "m_to") + "×" + F(e, "m_producedItems")));
					}
				}
				else
				{
					Log.Info("chestdump[" + r + ".c5] Smelter " + item + text + ": " + Fields(item2, "m_name", "m_fuelItem", "m_maxFuel", "m_fuelPerProduct", "m_secPerProduct", "m_maxOre", "m_requiresRoof", "m_windmill", "m_spawnStack") + " conversions=" + List(item2, "m_conversion", (object e) => F(e, "m_from") + "→" + F(e, "m_to")));
				}
			}
			Log.Info("chestdump[" + r + ".c5] stations: " + string.Join(", ", sortedDictionary.Select((KeyValuePair<string, int> kv) => kv.Key + "=" + kv.Value)));
		}

		private static void NewFields(string r)
		{
			List<(string, Component)> source = StationComponents().ToList();
			Log.Info("chestdump[" + r + ".c7] Smelter.m_noSourceConversion (" + GameAccess.MemberVisibility(typeof(Smelter), "m_noSourceConversion") + "): " + string.Join(" ", from s in source
				where s.comp is Smelter
				select s.prefab + "=" + F(s.comp, "m_noSourceConversion")));
			foreach (var (text, obj) in source.Where<(string, Component)>(((string prefab, Component comp) s) => s.comp is CookingStation))
			{
				Log.Info("chestdump[" + r + ".c7] CookingStation " + text + ": " + Fields(obj, "m_canOvercookItems", "m_canGiveBonusYield", "m_useFueldWhileEmpty", "m_spawnFullDurability", "m_recordCrafter", "m_cheapFireCheck"));
			}
			foreach (var (text2, obj2) in source.Where<(string, Component)>(((string prefab, Component comp) s) => s.comp is CraftingStation))
			{
				Log.Info("chestdump[" + r + ".c7] CraftingStation " + text2 + ": " + Fields(obj2, "m_upgrader", "m_canRepair", "m_hasCraftTab"));
			}
			List<Recipe> list = ObjectDB.instance.m_recipes.Where((Recipe x) => (Object)(object)x != (Object)null).ToList();
			List<string> list2 = (from x in list
				where F(x, "m_noCraftOnlyUpgrade") == "True"
				select ((Object)x).name).ToList();
			Log.Info(string.Format("chestdump[{0}.c7] Recipe.m_noCraftOnlyUpgrade ({1}): true on {2} of {3} recipes{4}", r, GameAccess.MemberVisibility(typeof(Recipe), "m_noCraftOnlyUpgrade"), list2.Count, list.Count, Sample(list2)));
			List<string> list3 = new List<string>();
			foreach (Recipe item in list)
			{
				if (item.m_resources == null)
				{
					continue;
				}
				Requirement[] resources = item.m_resources;
				foreach (Requirement val in resources)
				{
					if (val != null && F(val, "m_upgraderResource") == "True")
					{
						list3.Add(((Object)item).name + ":" + (((Object)(object)val.m_resItem != (Object)null) ? ((Object)val.m_resItem).name : "null"));
					}
				}
			}
			Log.Info(string.Format("chestdump[{0}.c7] Piece.Requirement.m_upgraderResource ({1}): true on {2} requirements{3}", r, GameAccess.MemberVisibility(typeof(Requirement), "m_upgraderResource"), list3.Count, Sample(list3)));
			IEnumerable<string> values = from x in list
				where (Object)(object)x.m_craftingStation != (Object)null
				group x by ((Object)x.m_craftingStation).name into g
				select $"{g.Key}={g.Count()}";
			Log.Info(string.Format("chestdump[{0}.c7] recipes per crafting station: {1}; no station={2}", r, string.Join(" ", values), list.Count((Recipe x) => (Object)(object)x.m_craftingStation == (Object)null)));
		}

		private static void Visibility(string r)
		{
			Line(typeof(Inventory), new string[7] { ".ctor", "Load", "LoadOld", "SetHeight", "AddTempItem", "GetAllItems", "m_temoraryInventory" });
			Line(typeof(ZDOMan), new string[6] { "GetAllZDOsWithPrefabIterative", "GetZDO", "m_objectsBySector", "m_objectsByID", "ConvertContainers", "ConvertInventories" });
			Line(typeof(ZDOExtraData), new string[4] { "GetData", "GetStrings", "GetByteArrays", "GetAllZDOIDsWithHash" });
			Line(typeof(ZDO), new string[5] { "GetString", "GetByteArray", "GetLong", "GetPrefab", "GetPosition" });
			Line(typeof(ZNetScene), new string[3] { "GetPrefab", "FindInstance", "m_prefabs" });
			Line(typeof(Container), new string[8] { "m_privacy", "m_width", "m_height", "UpdateRows", "CheckAccess", "GetInventory", "Load", "Save" });
			Line(typeof(Piece), new string[4] { "SetCreator", "GetCreator", "GetCreatorPlatformUserIdIndex", "m_creatorPlatformUserIDIndex" });
			Line(typeof(ItemData), new string[3] { "m_cheated", "m_dropPrefab", "m_worldLevel" });
			Line(typeof(ZDOVars), new string[4] { "s_items", "s_itemData", "s_creator", "s_creatorIndex" });
			void Line(Type t, params string[] members)
			{
				Log.Info("chestdump[" + r + ".c2] visibility " + t.Name + ": " + string.Join("  ", members.Select((string m) => m + "=" + GameAccess.MemberVisibility(t, m))));
			}
		}

		private static List<Loader> Loaders()
		{
			return new List<Loader>
			{
				new Loader
				{
					Name = "Inventory(w,h).Load(pkg)",
					Make = (int w, int h) => new Inventory("rlvl", (Sprite)null, w, h),
					Load = delegate(Inventory inv, ZPackage pkg)
					{
						inv.Load(pkg);
					}
				},
				new Loader
				{
					Name = $"Inventory(w,{64}).Load(pkg)",
					Make = (int w, int h) => new Inventory("rlvl", (Sprite)null, w, Math.Max(h, 64)),
					Load = delegate(Inventory inv, ZPackage pkg)
					{
						inv.Load(pkg);
					}
				},
				new Loader
				{
					Name = "Inventory(true).Load(pkg,true)",
					Make = (int w, int h) => GameAccess.NewTemporaryInventory(),
					Load = delegate(Inventory inv, ZPackage pkg)
					{
						GameAccess.LoadInventory(inv, pkg, flag: true);
					}
				},
				new Loader
				{
					Name = "Inventory(true).Load(pkg)",
					Make = (int w, int h) => GameAccess.NewTemporaryInventory(),
					Load = delegate(Inventory inv, ZPackage pkg)
					{
						inv.Load(pkg);
					}
				},
				new Loader
				{
					Name = $"Inventory(w,{64}).Load(pkg,true)",
					Make = (int w, int h) => new Inventory("rlvl", (Sprite)null, w, Math.Max(h, 64)),
					Load = delegate(Inventory inv, ZPackage pkg)
					{
						GameAccess.LoadInventory(inv, pkg, flag: true);
					}
				},
				new Loader
				{
					Name = $"Inventory(w,{64}).Load(pkg,false)",
					Make = (int w, int h) => new Inventory("rlvl", (Sprite)null, w, Math.Max(h, 64)),
					Load = delegate(Inventory inv, ZPackage pkg)
					{
						GameAccess.LoadInventory(inv, pkg, flag: false);
					}
				}
			};
		}

		private static void Probes(string r, List<PrefabInfo> prefabs)
		{
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Expected O, but got Unknown
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Expected O, but got Unknown
			List<(PrefabInfo, ZDO)> list = PickProbes(prefabs);
			Log.Info(string.Format("chestdump[{0}.c1] probes: {1} ({2}); one per prefab, the first player-built one with inventory data", r, list.Count, string.Join(", ", list.Select<(PrefabInfo, ZDO), string>(((PrefabInfo info, ZDO zdo) p) => p.info.Name))));
			Dictionary<string, int> votes = new Dictionary<string, int>(StringComparer.Ordinal);
			Harmony val = new Harmony("RaidLedger.ValheimLink.chestdump");
			MethodInfo methodInfo = GameAccess.ItemDropAwake();
			bool counting = false;
			try
			{
				try
				{
					if (methodInfo != null)
					{
						val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(ChestDump).GetMethod("CountItemDropAwake", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
						counting = true;
					}
					else
					{
						Log.Info("chestdump[" + r + ".c2] ItemDrop.Awake not found; instantiation is judged by the object count only");
					}
				}
				catch (Exception e)
				{
					Log.Warn("chestdump[" + r + ".c2] could not count ItemDrop.Awake (" + Short(e) + "); instantiation is judged by the object count only");
				}
				int num = 0;
				foreach (var item in list)
				{
					PrefabInfo info = item.Item1;
					ZDO zdo = item.Item2;
					string tag = $"{r}.c1.p{++num}";
					Safe(tag, "key list", delegate
					{
						ListKeys(tag, zdo, info.Name);
					});
					string ctag = $"{r}.c2.p{num}";
					Safe(ctag, "decode variants", delegate
					{
						string text = ProbeDecode(ctag, zdo, info, counting);
						if (text != null)
						{
							votes[text] = (votes.TryGetValue(text, out var value) ? value : 0) + 1;
						}
					});
				}
			}
			finally
			{
				val.UnpatchSelf();
			}
			KeyValuePair<string, int> keyValuePair = votes.OrderByDescending((KeyValuePair<string, int> kv) => kv.Value).FirstOrDefault();
			_chosenLoader = keyValuePair.Key ?? Loaders()[1].Name;
			Log.Info("chestdump[" + r + ".c2] decision: counting with " + _chosenLoader + " (" + ((keyValuePair.Key == null) ? "default: no probe decoded anything" : $"{keyValuePair.Value} of {votes.Values.Sum()} probes") + "; votes " + string.Join(", ", votes.Select((KeyValuePair<string, int> kv) => kv.Key + "=" + kv.Value)) + ")");
		}

		private static void CountItemDropAwake()
		{
			_itemDropAwakes++;
		}

		private static List<(PrefabInfo info, ZDO zdo)> PickProbes(List<PrefabInfo> prefabs)
		{
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: 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)
			IOrderedEnumerable<PrefabInfo> orderedEnumerable = from p in prefabs
				where p.Ids.Count > 0
				orderby p.Buildable && !p.Vehicle descending, (int)p.Privacy == 0 descending, p.Buildable descending, p.Ids.Count descending
				select p;
			List<(PrefabInfo, ZDO)> list = new List<(PrefabInfo, ZDO)>();
			foreach (PrefabInfo item in orderedEnumerable)
			{
				ZDO val = null;
				ZDO val2 = null;
				foreach (ZDOID id in item.Ids)
				{
					ZDO zDO = ZDOMan.instance.GetZDO(id);
					if (zDO != null && zDO.GetPrefab() == item.Hash && zDO.GetLong(ZDOVars.s_creator, 0L) != 0L)
					{
						if (val2 == null)
						{
							val2 = zDO;
						}
						if (ReadData(zDO, out var _) != null)
						{
							val = zDO;
							break;
						}
					}
				}
				ZDO val3 = val ?? val2;
				if (val3 != null)
				{
					list.Add((item, val3));
				}
				if (list.Count >= 6)
				{
					break;
				}
			}
			return list;
		}

		private static void ListKeys(string tag, ZDO zdo, string prefab)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			GameAccess.ZdoKeySet zdoKeySet = GameAccess.ZdoKeys(zdo.m_uid);
			Log.Info($"chestdump[{tag}] keys of {prefab} @{Pos(zdo.GetPosition())} zdoid={zdo.m_uid} owner={zdo.GetOwner()} dataRevision={zdo.DataRevision} persistent={zdo.Persistent} via {zdoKeySet.Via}: floats={zdoKeySet.Floats.Count} vec3={zdoKeySet.Vec3s.Count} quats={zdoKeySet.Quats.Count} ints={zdoKeySet.Ints.Count} longs={zdoKeySet.Longs.Count} strings={zdoKeySet.Strings.Count} byteArrays={zdoKeySet.ByteArrays.Count}");
			foreach (KeyValuePair<int, string> @string in zdoKeySet.Strings)
			{
				byte[] array = Base64(@string.Value);
				Log.Info(string.Format("chestdump[{0}]   string {1}{2} len={3} base64={4}", tag, KeyName(@string.Key), Mark(@string.Key), @string.Value?.Length ?? 0, (array != null) ? $"yes ({array.Length} bytes{Head(@string.Key, array)})" : "no"));
			}
			foreach (KeyValuePair<int, byte[]> byteArray in zdoKeySet.ByteArrays)
			{
				object[] obj = new object[5]
				{
					tag,
					KeyName(byteArray.Key),
					Mark(byteArray.Key),
					null,
					null
				};
				byte[] value = byteArray.Value;
				obj[3] = ((value != null) ? value.Length : 0);
				obj[4] = Head(byteArray.Key, byteArray.Value);
				Log.Info(string.Format("chestdump[{0}]   bytes {1}{2} len={3}{4}", obj));
			}
			foreach (KeyValuePair<int, int> @int in zdoKeySet.Ints)
			{
				Log.Info($"chestdump[{tag}]   int {KeyName(@int.Key)}{Mark(@int.Key)} = {@int.Value}");
			}
			foreach (KeyValuePair<int, long> @long in zdoKeySet.Longs)
			{
				Log.Info($"chestdump[{tag}]   long {KeyName(@long.Key)}{Mark(@long.Key)} = {@long.Value}");
			}
			if (zdoKeySet.Floats.Count + zdoKeySet.Vec3s.Count + zdoKeySet.Quats.Count > 0)
			{
				Log.Info("chestdump[" + tag + "]   floats: " + string.Join(" ", zdoKeySet.Floats.Select((KeyValuePair<int, float> f) => KeyName(f.Key))) + "; vec3: " + string.Join(" ", zdoKeySet.Vec3s.Select((KeyValuePair<int, Vector3> f) => KeyName(f.Key))) + "; quats: " + string.Join(" ", zdoKeySet.Quats.Select((KeyValuePair<int, Quaternion> f) => KeyName(f.Key))));
			}
		}

		private static string ProbeDecode(string tag, ZDO zdo, PrefabInfo p, bool countingAwakes)
		{
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Expected O, but got Unknown
			string source;
			byte[] array = ReadData(zdo, out source);
			if (array == null)
			{
				Log.Info("chestdump[" + tag + "] no inventory data under items/itemData (source=" + (source ?? "none") + "); the key list above shows what is there");
				return null;
			}
			int num = ((array.Length >= 4) ? BitConverter.ToInt32(array, 0) : int.MinValue);
			int num2 = ((array.Length >= 8) ? BitConverter.ToInt32(array, 4) : (-1));
			Log.Info(string.Format("chestdump[{0}] {1} source={2} {3} bytes; header int32 version={4} ({5}) count={6} (numItems; int32 reading {7}) first16={8}", tag, p.Name, source, array.Length, num, GameAccess.ItemVersionName(num) ?? "not a Version.Item value", NumItems(array), num2, Hex(array, 16)));
			List<(Loader, Decoded, int, int)> list = new List<(Loader, Decoded, int, int)>();
			foreach (Loader item2 in Loaders())
			{
				Decoded decoded = new Decoded
				{
					Source = source
				};
				ZPackage pkg = new ZPackage(array);
				Inventory val = null;
				int itemDropAwakes = _itemDropAwakes;
				int num3 = CountItemDrops();
				Stopwatch stopwatch = Stopwatch.StartNew();
				try
				{
					val = item2.Make(p.Width, p.Height) ?? throw new MissingMethodException("Inventory", ".ctor(Boolean)");
					item2.Load(val, pkg);
					decoded.Items = new List<ItemData>(val.GetAllItems());
					foreach (ItemData item3 in decoded.Items)
					{
						decoded.MaxRow = Math.Max(decoded.MaxRow, item3.m_gridPos.y);
					}
				}
				catch (Exception e)
				{
					decoded.Error = Short(e);
				}
				decoded.Ms = stopwatch.Elapsed.TotalMilliseconds;
				int item = _itemDropAwakes - itemDropAwakes;
				int num4 = CountItemDrops() - num3;
				list.Add((item2, decoded, item, num4));
				string text = ((val != null) ? $"{val.GetWidth()}x{val.GetHeight()}" : "-");
				string text2 = SafeString(() => $"{pkg.GetPos()}/{pkg.Size()}");
				Log.Info((decoded.Error != null) ? ("chestdump[" + tag + "] loader " + item2.Name + ": threw " + decoded.Error + " (read " + text2 + " bytes)") : string.Format("chestdump[{0}] loader {1}: stacks={2} in {3} ms ({4} ms/stack, first call includes JIT) itemDrop.Awake={5} itemDropObjects+={6} inventory={7} maxRow={8} read={9} bytes dropPrefabNull={10}", tag, item2.Name, decoded.Items.Count, Ms(decoded.Ms), Ms((decoded.Items.Count > 0) ? (decoded.Ms / (double)decoded.Items.Count) : 0.0), countingAwakes ? item.ToString(Inv) : "n/a", num4, text, decoded.MaxRow, text2, decoded.Items.Count((ItemData i) => (Object)(object)i.m_dropPrefab == (Object)null)));
			}
			List<(Loader, Decoded, int, int)> list2 = list.Where<(Loader, Decoded, int, int)>(((Loader loader, Decoded d, int awakes, int objects) x) => x.d.Error == null).ToList();
			if (list2.Count == 0)
			{
				return null;
			}
			int most = list2.Max<(Loader, Decoded, int, int)>(((Loader loader, Decoded d, int awakes, int objects) x) => x.d.Items.Count);
			(Loader, Decoded, int, int) tuple = list2.FirstOrDefault<(Loader, Decoded, int, int)>(((Loader loader, Decoded d, int awakes, int objects) x) => x.loader.Name.StartsWith("Inventory(true)", StringComparison.Ordinal) && x.d.Items.Count == most && x.awakes == 0 && x.objects == 0 && x.d.Items.All((ItemData i) => (Object)(object)i.m_dropPrefab != (Object)null));
			if (tuple.Item1 == null)
			{
				tuple = list2.FirstOrDefault<(Loader, Decoded, int, int)>(((Loader loader, Decoded d, int awakes, int objects) x) => x.loader.Name == Loaders()[1].Name && x.d.Items.Count == most);
			}
			if (tuple.Item1 == null)
			{
				tuple = list2.First<(Loader, Decoded, int, int)>(((Loader loader, Decoded d, int awakes, int objects) x) => x.d.Items.Count == most);
			}
			foreach (ItemData item4 in tuple.Item2.Items.Take(8))
			{
				Log.Info(string.Format("chestdump[{0}]   item {1} x{2} q{3} v{4} at ({5},{6}) worldLevel={7} cheated={8} shared={9}", tag, ItemName(item4), item4.m_stack, item4.m_quality, item4.m_variant, item4.m_gridPos.x, item4.m_gridPos.y, item4.m_worldLevel, F(item4, "m_cheated"), (item4.m_shared != null) ? item4.m_shared.m_name : "null"));
			}
			if (tuple.Item2.Items.Count > 8)
			{
				Log.Info($"chestdump[{tag}]   … {tuple.Item2.Items.Count - 8} more stacks");
			}
			Log.Info(string.Format("chestdump[{0}] rows: prefab {1}x{2}, decoded maxRow={3}{4}; header count {5} vs decoded {6}; live instance: {7}; vote {8}", tag, p.Width, p.Height, tuple.Item2.MaxRow, (tuple.Item2.MaxRow >= p.Height) ? " → BEYOND the prefab's rows" : "", NumItems(array), tuple.Item2.Items.Count, LiveInventory(zdo), tuple.Item1.Name));
			if (most <= 0)
			{
				return null;
			}
			return tuple.Item1.Name;
		}

		private static int CountItemDrops()
		{
			try
			{
				return Resources.FindObjectsOfTypeAll<ItemDrop>().Length;
			}
			catch
			{
				return -1;
			}
		}

		private static string LiveInventory(ZDO zdo)
		{
			ZNetView val = ZNetScene.instance.FindInstance(zdo);
			if ((Object)(object)val == (Object)null)
			{
				return "not loaded";
			}
			Container componentInChildren = ((Component)val).GetComponentInChildren<Container>(true);
			Inventory val2 = (((Object)(object)componentInChildren != (Object)null) ? componentInChildren.GetInventory() : null);
			if (val2 != null)
			{
				return $"{val2.GetWidth()}x{val2.GetHeight()} stacks={val2.GetAllItems().Count}";
			}
			return "loaded, no inventory";
		}

		private static byte[] ReadData(ZDO zdo, out string source)
		{
			string text = zdo.GetString(ZDOVars.s_items, "");
			if (!string.IsNullOrEmpty(text))
			{
				source = "string:items";
				return Base64(text);
			}
			byte[] byteArray = zdo.GetByteArray(ZDOVars.s_items, (byte[])null);
			if (byteArray != null && byteArray.Length != 0)
			{
				source = "bytes:items";
				return byteArray;
			}
			byteArray = zdo.GetByteArray(_itemDataHash, (byte[])null);
			if (byteArray != null && byteArray.Length != 0)
			{
				source = "bytes:itemData";
				return byteArray;
			}
			text = zdo.GetString(_itemDataHash, "");
			if (!string.IsNullOrEmpty(text))
			{
				source = "string:itemData";
				return Base64(text);
			}
			source = null;
			return null;
		}

		private static int NumItems(byte[] data)
		{
			if (data.Length < 5)
			{
				return -1;
			}
			if ((data[4] & 0x80) == 0)
			{
				return data[4];
			}
			if (data.Length < 6)
			{
				return -1;
			}
			return ((data[4] & 0x7F) << 8) | data[5];
		}

		private static Decoded Decode(ZDO zdo, PrefabInfo p, Loader loader)
		{
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Expected O, but got Unknown
			Decoded decoded = new Decoded();
			byte[] array = ReadData(zdo, out decoded.Source);
			if (array == null)
			{
				if (decoded.Source != null)
				{
					decoded.Error = decoded.Source + " is not base64";
				}
				return decoded;
			}
			if (array.Length >= 5)
			{
				decoded.HeaderVersion = BitConverter.ToInt32(array, 0);
				decoded.HeaderCount = NumItems(array);
			}
			Stopwatch stopwatch = Stopwatch.StartNew();
			try
			{
				Inventory val = loader.Make(p.Width, p.Height) ?? throw new MissingMethodException("Inventory", ".ctor(Boolean)");
				loader.Load(val, new ZPackage(array));
				decoded.Items = new List<ItemData>(val.GetAllItems());
				foreach (ItemData item in decoded.Items)
				{
					decoded.MaxRow = Math.Max(decoded.MaxRow, item.m_gridPos.y);
				}
			}
			catch (Exception e)
			{
				decoded.Error = Short(e);
			}
			decoded.Ms = stopwatch.Elapsed.TotalMilliseconds;
			return decoded;
		}

		private static void Counts(string r, List<PrefabInfo> prefabs, Loader loader, SortedDictionary<string, int> sources, double decodeMs, int decodeTicks, double decodeMaxTick, int collisions)
		{
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			foreach (PrefabInfo item in prefabs.Where((PrefabInfo x) => x.Raw > 0).OrderBy<PrefabInfo, string>((PrefabInfo x) => x.Name, StringComparer.Ordinal))
			{
				Log.Info(string.Format("chestdump[{0}.c3] {1}: found={2} unique={3} duplicates={4} gone={5} creator0={6} counted={7} empty={8} unreadable={9} stacks={10} dropped={11} headerMismatch={12} maxRow={13} rowsBeyondPrefab={14} (prefab {15}x{16} {17}{18}{19}) discovery={20} ms over {21} calls decode={22} ms", r, item.Name, item.Raw, item.Ids.Count, item.Duplicates, item.Gone, item.Creator0, item.Counted, item.Empty, item.Unreadable, item.Stacks, item.Dropped, item.HeaderMismatch, item.MaxRow, item.RowsBeyondPrefab, item.Width, item.Height, item.Privacy, item.Buildable ? " buildable" : "", item.Vehicle ? " vehicle" : "", Ms(item.DiscoveryMs), item.Calls, Ms(item.DecodeMs)));
			}
			List<string> list = (from x in prefabs
				where x.Raw == 0
				select x.Name).OrderBy<string, string>((string x) => x, StringComparer.Ordinal).ToList();
			if (list.Count > 0)
			{
				Log.Info("chestdump[" + r + ".c3] none in this world: " + string.Join(", ", list));
			}
			int num = prefabs.Sum((PrefabInfo p) => p.Stacks);
			int num2 = prefabs.Sum((PrefabInfo p) => p.Counted);
			double ms = ((num > 0) ? (prefabs.Sum((PrefabInfo p) => p.DecodeMs) / (double)num) : 0.0);
			Log.Info($"chestdump[{r}.c3] totals with {loader.Name}: counted={num2} ({prefabs.Where((PrefabInfo p) => p.Vehicle).Sum((PrefabInfo p) => p.Counted)} in vehicles, {prefabs.Where((PrefabInfo p) => (int)p.Privacy == 0).Sum((PrefabInfo p) => p.Counted)} private) creator0={prefabs.Sum((PrefabInfo p) => p.Creator0)} duplicates={prefabs.Sum((PrefabInfo p) => p.Duplicates)} gone={prefabs.Sum((PrefabInfo p) => p.Gone)} empty={prefabs.Sum((PrefabInfo p) => p.Empty)} unreadable={prefabs.Sum((PrefabInfo p) => p.Unreadable)} stacks={num} dropped={prefabs.Sum((PrefabInfo p) => p.Dropped)} headerMismatch={prefabs.Sum((PrefabInfo p) => p.HeaderMismatch)} rowsBeyondPrefab={prefabs.Sum((PrefabInfo p) => p.RowsBeyondPrefab)} samePositionAndCreator={collisions}; decode {Ms(decodeMs)} ms wall over {decodeTicks} ticks (busiest tick {Ms(decodeMaxTick)} ms), {Ms(ms)} ms/stack");
			Log.Info("chestdump[" + r + ".c3] data sources: " + string.Join(", ", sources.Select((KeyValuePair<string, int> kv) => kv.Key + "=" + kv.Value)));
		}

		private static void MaterialLines(string r, string[] materials, long[] totals, long[] shared, int[] chests, Dictionary<string, long> itemTotals)
		{
			for (int i = 0; i < materials.Length; i++)
			{
				bool flag = (Object)(object)ObjectDB.instance.GetItemPrefab(materials[i]) != (Object)null;
				Log.Info(string.Format("chestdump[{0}.c3] material {1}{2}: {3} in {4} containers (shared {5}, personal {6})", r, materials[i], flag ? "" : " (NOT an item prefab in ObjectDB — check the spelling)", totals[i], chests[i], shared[i], totals[i] - shared[i]));
			}
			Log.Info(string.Format("chestdump[{0}.c3] top items: {1} ({2} distinct)", r, string.Join(", ", from kv in itemTotals.OrderByDescending((KeyValuePair<string, long> kv) => kv.Value).ThenBy<KeyValuePair<string, long>, string>((KeyValuePair<string, long> kv) => kv.Key, StringComparer.Ordinal).Take(10)
				select kv.Key + "=" + kv.Value), itemTotals.Count));
		}

		private static void Creators(string r, Dictionary<string, Snapshot> snapshots)
		{
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			//IL_0366: Unknown result type (might be due to invalid IL or missing references)
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			List<Snapshot> list = snapshots.Values.Where((Snapshot snapshot) => snapshot.Private).OrderBy<Snapshot, string>((Snapshot snapshot) => snapshot.Prefab, StringComparer.Ordinal).ThenBy((Snapshot snapshot) => snapshot.Pos.x)
				.ToList();
			int creatorIndexHash = GameAccess.ZdoVar("s_creatorIndex") ?? StringExtensionMethods.GetStableHashCode("creatorIndex");
			PeerIdentity.RefreshAll();
			List<PeerFacts> list2 = PeerIdentity.Known();
			Log.Info($"chestdump[{r}.c4] personal (privacy Private) containers with a creator: {list.Count}; distinct creators {list.Select((Snapshot snapshot) => snapshot.Creator).Distinct().Count()}; peers known {list2.Count}");
			foreach (Snapshot s in list.Take(10))
			{
				ZDO zDO = ZDOMan.instance.GetZDO(s.Id);
				string text = "absent";
				string text2 = "gone";
				if (zDO != null)
				{
					GameAccess.ZdoKeySet zdoKeySet = GameAccess.ZdoKeys(s.Id);
					List<string> list3 = new List<string>();
					list3.AddRange(from x in zdoKeySet.Ints
						where x.Key == creatorIndexHash
						select "int:" + x.Value);
					list3.AddRange(from x in zdoKeySet.Longs
						where x.Key == creatorIndexHash
						select "long:" + x.Value);
					list3.AddRange(from x in zdoKeySet.Strings
						where x.Key == creatorIndexHash
						select $"string(len {x.Value?.Length ?? 0})");
					list3.AddRange(zdoKeySet.ByteArrays.Where((KeyValuePair<int, byte[]> x) => x.Key == creatorIndexHash).Select(delegate(KeyValuePair<int, byte[]> x)
					{
						byte[] value = x.Value;
						return $"bytes(len {((value != null) ? value.Length : 0)})";
					}));
					if (list3.Count > 0)
					{
						text = string.Join(",", list3);
					}
					text2 = LivePiece(zDO);
				}
				PeerFacts peerFacts = list2.FirstOrDefault((PeerFacts peerFacts2) => peerFacts2.PlayerId == s.Creator);
				Log.Info(string.Format("chestdump[{0}.c4] {1} @{2} creator={3} creatorIndex={4} placer={5} livePiece={6} stacks={7}{8}", r, s.Prefab, Pos(s.Pos), s.Creator, text, (peerFacts != null) ? string.Format("peer {0} (playerId matches; steam={1})", peerFacts.Uid, peerFacts.SteamId ?? "none") : "no known peer has this playerId", text2, s.Stacks.Count, s.Unreadable ? " UNREADABLE" : ""));
			}
			Snapshot first = list.FirstOrDefault((Snapshot snapshot) => ZDOMan.instance.GetZDO(snapshot.Id) != null);
			if (first != null)
			{
				Safe(r + ".c4", "key list", delegate
				{
					//IL_001b: Unknown result type (might be due to invalid IL or missing references)
					ListKeys(r + ".c4", ZDOMan.instance.GetZDO(first.Id), first.Prefab);
				});
			}
			foreach (PeerFacts f in list2)
			{
				Log.Info(string.Format("chestdump[{0}.c4] peer {1}: playerId={2} steam={3} admin={4} personalChestsCreated={5} containersCreated={6}", r, f.Uid, f.PlayerId.HasValue ? f.PlayerId.Value.ToString(Inv) : "not spawned", f.SteamId ?? "none", f.IsAdmin, list.Count((Snapshot snapshot) => snapshot.Creator == f.PlayerId), snapshots.Values.Count((Snapshot snapshot) => snapshot.Creator == f.PlayerId)));
			}
		}

		private static string LivePiece(ZDO zdo)
		{
			ZNetView val = ZNetScene.instance.FindInstance(zdo);
			if ((Object)(object)val == (Object)null)
			{
				return "not loaded";
			}
			Piece component = ((Component)val).GetComponent<Piece>();
			if (!((Object)(object)component == (Object)null))
			{
				return Fields(component, "m_creator", "m_creatorPlatformUserIDIndex");
			}
			return "loaded, no Piece";
		}

		private static void Freshness(string r, Dictionary<string, Snapshot> now, string[] materials, long[] matTotals)
		{
			//IL_0294: Unknown result type (might be due to invalid IL or missing references)
			//IL_0382: Unknown result type (might be due to invalid IL or missing references)
			List<(Snapshot, Snapshot)> list = new List<(Snapshot, Snapshot)>();
			int num = 0;
			int num2 = 0;
			foreach (KeyValuePair<string, Snapshot> kv in now)
			{
				int value2;
				if (!_previous.TryGetValue(kv.Key, out var value))
				{
					num++;
				}
				else if (value.Stacks.Count == kv.Value.Stacks.Count && value.Stacks.All((KeyValuePair<string, int> e) => kv.Value.Stacks.TryGetValue(e.Key, out value2) && value2 == e.Value))
				{
					num2++;
				}
				else
				{
					list.Add((value, kv.Value));
				}
			}
			int num3 = _previous.Keys.Count((string k) => !now.ContainsKey(k));
			Log.Info(string.Format("chestdump[{0}.c6] since the previous run ({1} min ago): changed={2} unchanged={3} added={4} removed={5} (vehicles that moved count as removed + added)", r, (DateTime.UtcNow - _previousAt).TotalMinutes.ToString("0.0", Inv), list.Count, num2, num, num3));
			foreach (var item in list.Take(10))
			{
				Snapshot before = item.Item1;
				Snapshot after = item.Item2;
				int value2;
				int value3;
				IEnumerable<string> values = from k in before.Stacks.Keys.Union(after.Stacks.Keys).OrderBy<string, string>((string k) => k, StringComparer.Ordinal)
					select (k: k, b: before.Stacks.TryGetValue(k, out value2) ? value2 : 0, a: after.Stacks.TryGetValue(k, out value3) ? value3 : 0) into t
					where t.a != t.b
					select $"{t.k} {t.b}→{t.a}";
				Log.Info("chestdump[" + r + ".c6] changed " + after.Prefab + " @" + Pos(after.Pos) + (after.Private ? " (personal)" : "") + ": " + string.Join(", ", values));
			}
			if (_previousMaterials != null && _previousMaterials.Length == matTotals.Length)
			{
				Log.Info("chestdump[" + r + ".c6] materials since the previous run: " + string.Join(", ", materials.Select((string m, int i) => $"{m} {_previousMaterials[i]}→{matTotals[i]}")));
			}
			int num4 = 0;
			foreach (var item2 in list.Take(3))
			{
				Snapshot after2 = item2.Item2;
				ZDO zdo = ZDOMan.instance.GetZDO(after2.Id);
				if (zdo != null)
				{
					string tag = $"{r}.c6.p{++num4}";
					Safe(tag, "key list", delegate
					{
						ListKeys(tag, zdo, after2.Prefab);
					});
				}
			}
		}

		private static string[] Materials()
		{
			return (from s in (PluginConfig.ChestsDumpMaterials.Value ?? "").Split(new char[1] { ',' })
				select s.Trim() into s
				where s.Length > 0
				select s).Distinct<string>(StringComparer.OrdinalIgnoreCase).Take(5).ToArray();
		}

		private static string SnapshotKey(Snapshot s)
		{
			return string.Format(Inv, "{0}|{1:0.0}|{2:0.0}|{3:0.0}|{4}", s.Prefab, s.Pos.x, s.Pos.y, s.Pos.z, s.Creator);
		}

		private static string ItemName(ItemData it)
		{
			if (!((Object)(object)it.m_dropPrefab != (Object)null))
			{
				if (it.m_shared == null)
				{
					return "?";
				}
				return "shared:" + it.m_shared.m_name;
			}
			return ((Object)it.m_dropPrefab).name;
		}

		private static int PeersConnected()
		{
			if (!((Object)(object)ZNet.instance != (Object)null))
			{
				return -1;
			}
			return ZNet.instance.GetPeers().Count;
		}

		private static string KeyName(int hash)
		{
			if (!_names.TryGetValue(hash, out var value))
			{
				return $"? ({hash})";
			}
			return $"{value} ({hash})";
		}

		private static string Mark(int hash)
		{
			if (hash != _itemsHash)
			{
				if (hash != _itemDataHash)
				{
					return "";
				}
				return " [= \"itemData\"]";
			}
			return " [= \"items\"]";
		}

		private static string Pos(Vector3 p)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			return string.Format(Inv, "({0:0.0}, {1:0.0}, {2:0.0})", p.x, p.y, p.z);
		}

		private static string Ms(double ms)
		{
			return ms.ToString("0.000", Inv);
		}

		private static byte[] Base64(string s)
		{
			if (string.IsNullOrEmpty(s) || s.Length % 4 != 0)
			{
				return null;
			}
			try
			{
				return Convert.FromBase64String(s);
			}
			catch (FormatException)
			{
				return null;
			}
		}

		private static string Head(int hash, byte[] data)
		{
			if (data == null)
			{
				return "";
			}
			int num = ((data.Length >= 4) ? BitConverter.ToInt32(data, 0) : int.MinValue);
			bool flag = num >= 100 && num <= 200;
			if (!flag && hash != _itemsHash && hash != _itemDataHash)
			{
				return ", not package-like";
			}
			return ", first16=" + Hex(data, 16) + (flag ? string.Format(" (int32 version {0}: {1})", num, GameAccess.ItemVersionName(num) ?? "not a Version.Item value") : "");
		}

		private static string Hex(byte[] b, int max)
		{
			if (b == null)
			{
				return "null";
			}
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < Math.Min(b.Length, max); i++)
			{
				if (i > 0 && i % 4 == 0)
				{
					stringBuild