Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ValheimLink v0.4.1
plugins/RlValheimLink/RlValheimLink.Contract.dll
Decompiled 13 hours agousing 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 agousing 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
The result has been truncated due to the large size, download it to view full contents!
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