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 Barter v0.1.4
plugins/Barter/Barter.Core.dll
Decompiled 10 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using ExtraPotions.Barter.Availability; using ExtraPotions.Barter.Config; using ExtraPotions.Barter.Lists; using ExtraPotions.Barter.Trading; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ExtraPotions")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (C) 2026 expDARE (ExtraPotions Meadworks)")] [assembly: AssemblyFileVersion("0.1.4.0")] [assembly: AssemblyInformationalVersion("0.1.4")] [assembly: AssemblyProduct("Barter.Core")] [assembly: AssemblyTitle("Barter.Core")] [assembly: AssemblyVersion("0.1.4.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ExtraPotions.Barter { public static class BarterCore { public const string ProductId = "com.meadworks.barter"; public const string LegacyProductId = "com.extrapotions.barter"; public const string FilePrefix = "Barter."; public const string VanillaCurrency = "Coins"; public const string CommonTrader = "common"; public const int QualityStackMultiplier = 1000000; } public enum LegacyFileOutcome { None, Migrated, KeptNew, Failed } public sealed class LegacyFileResult { public LegacyFileOutcome Outcome { get; } public string? Backup { get; } public string? Problem { get; } internal LegacyFileResult(LegacyFileOutcome outcome, string? backup = null, string? problem = null) { Outcome = outcome; Backup = backup; Problem = problem; } } public static class LegacyFileMigration { public static LegacyFileResult PrepareSettings(string directory, string legacyName, string currentName) { LegacyFileResult legacyFileResult = Run(directory, legacyName, currentName); if (legacyFileResult.Outcome == LegacyFileOutcome.Failed) { throw new IOException("Legacy settings could not be carried over; initialization stopped before defaults were saved. " + legacyFileResult.Problem); } return legacyFileResult; } public static LegacyFileResult Run(string directory, string legacyName, string currentName) { if (string.IsNullOrEmpty(directory)) { throw new ArgumentException("A config directory is required.", "directory"); } if (string.IsNullOrEmpty(legacyName) || string.IsNullOrEmpty(currentName) || string.Equals(legacyName, currentName, StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException("Two different file names are required."); } string text = Path.Combine(directory, legacyName); string text2 = Path.Combine(directory, currentName); if (!File.Exists(text)) { return new LegacyFileResult(LegacyFileOutcome.None); } if (File.Exists(text2)) { return new LegacyFileResult(LegacyFileOutcome.KeptNew); } string text3 = text2 + ".migrate-" + Guid.NewGuid().ToString("N"); try { byte[] array = File.ReadAllBytes(text); File.WriteAllBytes(text3, array); if (!((ReadOnlySpan<byte>)array).SequenceEqual((ReadOnlySpan<byte>)File.ReadAllBytes(text3))) { throw new IOException("The copied settings failed readback."); } if (File.Exists(text2)) { return new LegacyFileResult(LegacyFileOutcome.KeptNew); } File.Move(text3, text2); } catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException) { return File.Exists(text2) ? new LegacyFileResult(LegacyFileOutcome.KeptNew) : new LegacyFileResult(LegacyFileOutcome.Failed, null, ex.Message); } finally { try { if (File.Exists(text3)) { File.Delete(text3); } } catch (IOException) { } catch (UnauthorizedAccessException) { } } try { string text4 = FreeBackupName(text); File.Move(text, text4); return new LegacyFileResult(LegacyFileOutcome.Migrated, text4); } catch (Exception ex4) when (ex4 is IOException || ex4 is UnauthorizedAccessException) { return new LegacyFileResult(LegacyFileOutcome.Migrated, null, "The old file kept its name: " + ex4.Message); } } private static string FreeBackupName(string path) { string text = path + ".bak"; if (!File.Exists(text)) { return text; } for (int i = 1; i < 1000; i++) { string text2 = path + "." + i + ".bak"; if (!File.Exists(text2)) { return text2; } } throw new IOException("No free backup name for " + Path.GetFileName(path) + "."); } } public readonly struct PanelBox { public float X { get; } public float Y { get; } public float Width { get; } public float Height { get; } public float Right => X + Width; public float Top => Y + Height; public bool Usable { get { if (Finite(X) && Finite(Y) && Finite(Width) && Finite(Height) && Width > 0f) { return Height > 0f; } return false; } } public PanelBox(float x, float y, float width, float height) { X = x; Y = y; Width = width; Height = height; } public static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } public enum SidePanelSide { Left, Right, Below } public readonly struct SidePlace { public SidePanelSide Side { get; } public PanelBox Box { get; } public SidePlace(SidePanelSide side, PanelBox box) { Side = side; Box = box; } } public readonly struct OpenPlace { public bool HasSell { get; } public SidePlace Sell { get; } public bool HasPurse { get; } public SidePlace Purse { get; } public OpenPlace(bool hasSell, SidePlace sell, bool hasPurse, SidePlace purse) { HasSell = hasSell; Sell = sell; HasPurse = hasPurse; Purse = purse; } } public static class SidePanelFit { public static SidePlace Place(PanelBox canvas, PanelBox store, PanelBox? beside, float width, float height, float gap, float frame) { width = NonNegative(width); height = NonNegative(height); gap = NonNegative(gap); frame = NonNegative(frame); if (!canvas.Usable) { return new SidePlace(SidePanelSide.Left, new PanelBox(0f, 0f, width, height)); } PanelBox other = (store.Usable ? store : canvas); PanelBox panelBox = NudgeY(canvas, Box(other.X - gap - width, other.Top - height, width, height), frame); if (Inside(canvas, panelBox, frame) && !Hits(panelBox, other) && !Hits(panelBox, beside)) { return new SidePlace(SidePanelSide.Left, panelBox); } float num = other.Right; if (beside.HasValue) { PanelBox valueOrDefault = beside.GetValueOrDefault(); if (valueOrDefault.Usable) { num = Math.Max(num, valueOrDefault.Right); } } PanelBox panelBox2 = NudgeY(canvas, Box(num + gap, other.Top - height, width, height), frame); if (Inside(canvas, panelBox2, frame) && !Hits(panelBox2, other) && !Hits(panelBox2, beside)) { return new SidePlace(SidePanelSide.Right, panelBox2); } float num2 = other.Y; if (beside.HasValue) { PanelBox valueOrDefault2 = beside.GetValueOrDefault(); if (valueOrDefault2.Usable) { num2 = Math.Min(num2, valueOrDefault2.Y); } } PanelBox panel = NudgeX(canvas, Box(other.X, num2 - gap - height, width, height), frame); return new SidePlace(SidePanelSide.Below, Clamp(canvas, panel, frame)); } public static SidePlace PlaceSell(PanelBox canvas, PanelBox store, float width, float height, float gap, float frame) { width = NonNegative(width); height = NonNegative(height); gap = NonNegative(gap); frame = NonNegative(frame); if (!canvas.Usable) { return new SidePlace(SidePanelSide.Right, new PanelBox(0f, 0f, width, height)); } PanelBox other = (store.Usable ? store : canvas); PanelBox panelBox = NudgeY(canvas, Box(other.Right + gap, other.Top - height, width, height), frame); if (Inside(canvas, panelBox, frame) && !Hits(panelBox, other)) { return new SidePlace(SidePanelSide.Right, panelBox); } PanelBox panelBox2 = NudgeY(canvas, Box(other.X - gap - width, other.Top - height, width, height), frame); if (Inside(canvas, panelBox2, frame) && !Hits(panelBox2, other)) { return new SidePlace(SidePanelSide.Left, panelBox2); } PanelBox panel = NudgeX(canvas, Box(other.X, other.Y - gap - height, width, height), frame); return new SidePlace(SidePanelSide.Below, Clamp(canvas, panel, frame)); } public static OpenPlace PlaceBoth(PanelBox canvas, PanelBox store, bool sell, float sellWidth, float sellHeight, bool purse, float purseWidth, float purseHeight, float gap, float frame) { gap = NonNegative(gap); frame = NonNegative(frame); sell = sell && sellWidth > 0f && sellHeight > 0f && PanelBox.Finite(sellWidth) && PanelBox.Finite(sellHeight); purse = purse && purseWidth > 0f && purseHeight > 0f && PanelBox.Finite(purseWidth) && PanelBox.Finite(purseHeight); SidePlace sidePlace = default(SidePlace); bool flag = false; if (sell) { sidePlace = PlaceSell(canvas, store, sellWidth, sellHeight, gap, frame); flag = sidePlace.Side != SidePanelSide.Below; } SidePlace sidePlace2 = default(SidePlace); if (purse) { PanelBox? beside = ((sell && flag) ? new PanelBox?(sidePlace.Box) : ((PanelBox?)null)); sidePlace2 = Place(canvas, store, beside, purseWidth, purseHeight, gap, frame); } if (sell && purse && Hits(sidePlace.Box, sidePlace2.Box)) { if (flag) { sidePlace2 = Yield(canvas, store, sidePlace2, sidePlace.Box, gap, frame); } else { sidePlace = Yield(canvas, store, sidePlace, sidePlace2.Box, gap, frame); } } if (sell && purse && Hits(sidePlace.Box, sidePlace2.Box)) { if (flag) { sidePlace = Yield(canvas, store, sidePlace, sidePlace2.Box, gap, frame); } else { sidePlace2 = Yield(canvas, store, sidePlace2, sidePlace.Box, gap, frame); } } return new OpenPlace(sell, sidePlace, purse, sidePlace2); } private static SidePlace Yield(PanelBox canvas, PanelBox store, SidePlace panel, PanelBox obstacle, float gap, float frame) { PanelBox panelBox = Separate(canvas, store, panel.Box, obstacle, gap, frame); if (!Hits(panelBox, obstacle)) { return new SidePlace(panel.Side, panelBox); } return panel; } private static PanelBox Separate(PanelBox canvas, PanelBox store, PanelBox panel, PanelBox obstacle, float gap, float frame) { if (!Hits(panel, obstacle) || !canvas.Usable || !obstacle.Usable) { return panel; } bool found = false; PanelBox best = panel; float bestArea = 0f; float bestDist = 0f; Consider(obstacle.X - gap - panel.Width, panel.Y); Consider(obstacle.Right + gap, panel.Y); Consider(panel.X, obstacle.Top + gap); Consider(panel.X, obstacle.Y - gap - panel.Height); Consider(canvas.X + frame, canvas.Y + frame); Consider(canvas.Right - frame - panel.Width, canvas.Y + frame); Consider(canvas.X + frame, canvas.Top - frame - panel.Height); Consider(canvas.Right - frame - panel.Width, canvas.Top - frame - panel.Height); if (store.Usable) { Consider(store.X - gap - panel.Width, store.Top - panel.Height); Consider(store.Right + gap, store.Top - panel.Height); } if (!found) { return panel; } return best; void Consider(float x, float y) { PanelBox panelBox = Clamp(canvas, new PanelBox(x, y, panel.Width, panel.Height), frame); if (Inside(canvas, panelBox, frame) && !Hits(panelBox, obstacle)) { float num = OverlapArea(panelBox, store); float num2 = panelBox.X - panel.X; float num3 = panelBox.Y - panel.Y; float num4 = num2 * num2 + num3 * num3; if (!found || num < bestArea - 0.5f || (Math.Abs(num - bestArea) <= 0.5f && num4 < bestDist)) { found = true; best = panelBox; bestArea = num; bestDist = num4; } } } } private static float OverlapArea(PanelBox panel, PanelBox other) { if (!panel.Usable || !other.Usable) { return 0f; } float num = Math.Min(panel.Right, other.Right) - Math.Max(panel.X, other.X); float num2 = Math.Min(panel.Top, other.Top) - Math.Max(panel.Y, other.Y); if (num <= 0.5f || num2 <= 0.5f) { return 0f; } return num * num2; } private static PanelBox Box(float x, float y, float width, float height) { return new PanelBox(x, y, width, height); } private static float NonNegative(float value) { if (!PanelBox.Finite(value) || !(value > 0f)) { return 0f; } return value; } private static PanelBox NudgeY(PanelBox canvas, PanelBox panel, float frame) { float num = canvas.Y + frame; float num2 = canvas.Top - frame; if (num2 - num < panel.Height) { return panel; } float num3 = panel.Y; if (num3 < num) { num3 = num; } if (num3 + panel.Height > num2) { num3 = num2 - panel.Height; } return new PanelBox(panel.X, num3, panel.Width, panel.Height); } private static PanelBox NudgeX(PanelBox canvas, PanelBox panel, float frame) { float num = canvas.X + frame; float num2 = canvas.Right - frame; if (num2 - num < panel.Width) { return panel; } float num3 = panel.X; if (num3 < num) { num3 = num; } if (num3 + panel.Width > num2) { num3 = num2 - panel.Width; } return new PanelBox(num3, panel.Y, panel.Width, panel.Height); } private static bool Inside(PanelBox canvas, PanelBox panel, float frame) { if (panel.X >= canvas.X + frame - 0.51f && panel.Y >= canvas.Y + frame - 0.51f && panel.Right <= canvas.Right - frame + 0.51f) { return panel.Top <= canvas.Top - frame + 0.51f; } return false; } private static bool Hits(PanelBox panel, PanelBox? other) { if (!other.HasValue || !other.Value.Usable) { return false; } PanelBox value = other.Value; float num = Math.Max(panel.X, value.X); float num2 = Math.Min(panel.Right, value.Right); float num3 = Math.Max(panel.Y, value.Y); float num4 = Math.Min(panel.Top, value.Top); if (num2 - num > 0.5f) { return num4 - num3 > 0.5f; } return false; } private static bool Hits(PanelBox panel, PanelBox other) { return Hits(panel, (PanelBox?)other); } private static PanelBox Clamp(PanelBox canvas, PanelBox panel, float frame) { float num = frame; float num2 = frame; if (canvas.Width <= panel.Width + frame * 2f) { num = 0f; } if (canvas.Height <= panel.Height + frame * 2f) { num2 = 0f; } float num3 = canvas.X + num; float num4 = canvas.Y + num2; float num5 = canvas.Right - num; float num6 = canvas.Top - num2; float x = panel.X; float y = panel.Y; x = ((!(num5 - num3 >= panel.Width)) ? num3 : Math.Max(num3, Math.Min(x, num5 - panel.Width))); y = ((!(num6 - num4 >= panel.Height)) ? (num6 - panel.Height) : Math.Max(num4, Math.Min(y, num6 - panel.Height))); return new PanelBox(x, y, panel.Width, panel.Height); } } } namespace ExtraPotions.Barter.Trading { public interface IAuthorityStorage { bool Exists(string path); byte[] Read(string path, int maximumBytes); void WriteNew(string path, byte[] bytes); void Promote(string pending, string current, string backup); IEnumerable<string> Files(string directory, string pattern); void Delete(string path); } public sealed class AuthenticatedStateStore { private sealed class Record { internal string Object = ""; internal string Field = ""; internal string Value = ""; } private sealed class LocalStorage : IAuthorityStorage { public bool Exists(string path) { return File.Exists(path); } public byte[] Read(string path, int maximumBytes) { using (RefillTiming.Measure(RefillTiming.Operation.LocalRead)) { if (new FileInfo(path).Length > maximumBytes) { throw new InvalidDataException("Authority file bound"); } byte[] array = File.ReadAllBytes(path); if (array.Length > maximumBytes) { throw new InvalidDataException("Authority file bound"); } return array; } } public void WriteNew(string path, byte[] bytes) { using (RefillTiming.Measure(RefillTiming.Operation.LocalWrite)) { Directory.CreateDirectory(Path.GetDirectoryName(path)); using FileStream fileStream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.None); fileStream.Write(bytes, 0, bytes.Length); using (RefillTiming.Measure(RefillTiming.Operation.Flush)) { fileStream.Flush(flushToDisk: true); } } } public void Promote(string pending, string current, string backup) { using (RefillTiming.Measure(RefillTiming.Operation.Promote)) { if (File.Exists(current)) { File.Replace(pending, current, backup); } else { File.Move(pending, current); } } } public IEnumerable<string> Files(string directory, string pattern) { if (!Directory.Exists(directory)) { return Array.Empty<string>(); } return Directory.EnumerateFiles(directory, pattern); } public void Delete(string path) { File.Delete(path); } } public const int MaximumValueLength = 1048576; private const int MaximumRecordBytes = 4198400; private readonly string directory; private readonly IAuthorityStorage files; private readonly bool requireExistingKey; public AuthenticatedStateStore(string directory, IAuthorityStorage? storage = null, bool requireExistingKey = false) { this.directory = ((storage == null) ? Path.GetFullPath(directory) : directory); files = storage ?? new LocalStorage(); this.requireExistingKey = requireExistingKey; } private string WorldDirectory(long world) { if (world == 0L) { throw new InvalidDataException("Authority world is unavailable"); } return Path.Combine(directory, world.ToString(CultureInfo.InvariantCulture)); } private static void Identity(string objectId, string field) { if (string.IsNullOrEmpty(objectId) || objectId.Length > 128 || string.IsNullOrEmpty(field) || field.Length > 256) { throw new InvalidDataException("Authority state identity bound"); } } private string RecordPath(long world, string objectId, string field) { Identity(objectId, field); using SHA256 sHA = SHA256.Create(); return Path.Combine(WorldDirectory(world), BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(objectId + "\0" + field))).Replace("-", "") + ".state"); } private byte[] Key(long world, bool create) { string path = WorldDirectory(world); string text = Path.Combine(path, "authority.key"); if (files.Exists(text + ".pending")) { byte[] array = null; string[] array2 = new string[2] { text, text + ".backup" }; foreach (string path2 in array2) { if (!files.Exists(path2)) { continue; } try { byte[] array3 = files.Read(path2, 32); if (array3.Length == 32) { array = array3; break; } } catch (InvalidDataException) { } } if (array != null) { byte[] array4 = null; try { array4 = files.Read(text + ".pending", 32); } catch (InvalidDataException) { } if (array4 == null || !((ReadOnlySpan<byte>)array4).SequenceEqual((ReadOnlySpan<byte>)array)) { Quarantine(text + ".pending"); } } } byte[] array5 = Recover(text, delegate(string p) { byte[] array6 = files.Read(p, 32); if (array6.Length != 32) { throw new InvalidDataException("Authority key bound"); } return array6; }); if (array5 != null) { return array5; } if (files.Files(path, "*.state*").Any()) { throw new InvalidDataException("Authority key missing for existing state"); } string path3 = Path.Combine(path, "allow-new-authority-key"); bool flag = files.Exists(path3) && Encoding.UTF8.GetString(files.Read(path3, 64)).Trim() == world.ToString(CultureInfo.InvariantCulture); if (requireExistingKey && !flag) { throw new InvalidDataException("Existing world is missing Barter authority state. Restore Barter.Authority or create allow-new-authority-key containing the world UID to authorize its initial balance."); } if (!create) { return Array.Empty<byte>(); } array5 = new byte[32]; using (RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create()) { randomNumberGenerator.GetBytes(array5); } AtomicWrite(text, array5); AtomicWrite(text + ".backup", array5); if (flag) { files.Delete(path3); } return array5; } private Record Decode(string path, long world, byte[] key, string? objectId = null, string? field = null) { byte[] array = files.Read(path, 4198400); if (array.Length < 48 || array.Length > 4198400) { throw new InvalidDataException("Authority record bound"); } byte[] array2; using (HMACSHA256 hMACSHA = new HMACSHA256(key)) { array2 = hMACSHA.ComputeHash(array, 32, array.Length - 32); } int num = 0; for (int i = 0; i < 32; i++) { num |= array2[i] ^ array[i]; } if (num != 0) { throw new InvalidDataException("Authority state authentication failed"); } using MemoryStream memoryStream = new MemoryStream(array, 32, array.Length - 32); using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8); if (binaryReader.ReadInt32() != 1 || binaryReader.ReadInt64() != world) { throw new InvalidDataException("Authority world mismatch"); } Record record = new Record { Object = binaryReader.ReadString(), Field = binaryReader.ReadString(), Value = binaryReader.ReadString() }; Identity(record.Object, record.Field); if (objectId != null) { if (record.Object != objectId || record.Field != field) { throw new InvalidDataException("Authority state identity mismatch"); } } else { string text = RecordPath(world, record.Object, record.Field).Replace('\\', '/'); string text2 = path.Replace('\\', '/'); if (text2 != text && text2 != text + ".pending" && text2 != text + ".backup") { throw new InvalidDataException("Authority record filename mismatch"); } } if (record.Value.Length > 1048576 || memoryStream.Position != memoryStream.Length) { throw new InvalidDataException("Authority state value bound"); } return record; } public string Read(long world, string objectId, string field) { return Read(world, objectId, field, RecordPath(world, objectId, field)); } private string Read(long world, string objectId, string field, string path) { byte[] key = Key(world, create: false); return Recover(path, delegate(string p) { if (key.Length != 32) { throw new InvalidDataException("Authority key missing"); } return Decode(p, world, key, objectId, field); })?.Value ?? ""; } private T? Recover<T>(string path, Func<string, T> validate) where T : class { string text = path + ".pending"; string text2 = path + ".backup"; bool flag = false; if (files.Exists(text)) { T val = null; try { val = validate(text); } catch (Exception ex) when (ex is InvalidDataException || ex is EndOfStreamException || ex is FormatException) { flag = true; } if (val != null) { files.Promote(text, path, text2); return val; } Quarantine(text); } if (files.Exists(path)) { try { return validate(path); } catch (Exception ex2) when (ex2 is InvalidDataException || ex2 is EndOfStreamException || ex2 is FormatException) { flag = true; } } if (files.Exists(text2)) { T result = validate(text2); byte[] bytes = files.Read(text2, 4198400); if (files.Exists(path)) { Quarantine(path); } files.WriteNew(text, bytes); files.Promote(text, path, path + ".replaced"); return result; } if (flag || files.Exists(path)) { throw new InvalidDataException("No valid authority state remains; restore the world backup"); } return null; } private void Quarantine(string path) { files.Promote(path, path + ".quarantine-" + Guid.NewGuid().ToString("N"), path + ".unused"); } public void Write(long world, string objectId, string field, string value) { if (value == null || value.Length > 1048576) { throw new InvalidDataException("Authority state value bound"); } string path = RecordPath(world, objectId, field); Read(world, objectId, field); byte[] key = Key(world, create: true); byte[] array; using (MemoryStream memoryStream = new MemoryStream()) { using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8); binaryWriter.Write(1); binaryWriter.Write(world); binaryWriter.Write(objectId); binaryWriter.Write(field); binaryWriter.Write(value); array = memoryStream.ToArray(); } using MemoryStream memoryStream2 = new MemoryStream(); using (HMACSHA256 hMACSHA = new HMACSHA256(key)) { byte[] array2 = hMACSHA.ComputeHash(array); memoryStream2.Write(array2, 0, array2.Length); } memoryStream2.Write(array, 0, array.Length); AtomicWrite(path, memoryStream2.ToArray()); } public string[] Fields(long world, string objectId, string prefix) { Identity(objectId, string.IsNullOrEmpty(prefix) ? "*" : prefix); byte[] key = Key(world, create: false); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (string item in RecordFiles(WorldDirectory(world))) { Record record = Recover(item, (string p) => Decode(p, world, key)); if (record != null && record.Object == objectId && record.Field.StartsWith(prefix ?? "", StringComparison.Ordinal)) { Read(world, objectId, record.Field, item); hashSet.Add(record.Field); } } return hashSet.ToArray(); } private IEnumerable<string> RecordFiles(string folder) { return (from path in files.Files(folder, "*.state*") where path.EndsWith(".state", StringComparison.Ordinal) || path.EndsWith(".state.pending", StringComparison.Ordinal) || path.EndsWith(".state.backup", StringComparison.Ordinal) select (!path.EndsWith(".pending", StringComparison.Ordinal)) ? ((!path.EndsWith(".backup", StringComparison.Ordinal)) ? path : path.Substring(0, path.Length - 7)) : path.Substring(0, path.Length - 8)).Distinct<string>(StringComparer.Ordinal).ToArray(); } public void Remove(long world, string objectId, string field) { string text = RecordPath(world, objectId, field); Read(world, objectId, field, text); string[] array = new string[3] { ".pending", ".backup", "" }; foreach (string text2 in array) { files.Delete(text + text2); } } public void ImportLocal(long world, string legacyDirectory) { string path = WorldDirectory(world); string path2 = Path.Combine(legacyDirectory, world.ToString(CultureInfo.InvariantCulture)); if (!files.Exists(Path.Combine(path, "authority.key")) && Directory.Exists(path2)) { ImportFrom(world, new AuthenticatedStateStore(legacyDirectory)); } } public void ImportFrom(long world, AuthenticatedStateStore old, bool replaceExisting = false) { string path = WorldDirectory(world); string folder = old.WorldDirectory(world); byte[] key = old.Key(world, create: false); if (key.Length != 32) { return; } string path2 = Path.Combine(path, "authority.key"); if (files.Exists(path2) && !((ReadOnlySpan<byte>)Key(world, create: false)).SequenceEqual((ReadOnlySpan<byte>)key)) { throw new InvalidDataException("Authority world migration key conflict"); } Dictionary<string, byte[]> dictionary = new Dictionary<string, byte[]>(StringComparer.Ordinal); foreach (string item in old.RecordFiles(folder)) { Record record = old.Recover(item, (string p) => old.Decode(p, world, key)); if (record != null) { old.Read(world, record.Object, record.Field); string path3 = old.RecordPath(world, record.Object, record.Field); dictionary[Path.GetFileName(path3)] = old.files.Read(path3, 4198400); } } foreach (KeyValuePair<string, byte[]> item2 in dictionary) { string path4 = Path.Combine(path, item2.Key); if (replaceExisting && files.Exists(path4) && !((ReadOnlySpan<byte>)files.Read(path4, 4198400)).SequenceEqual((ReadOnlySpan<byte>)item2.Value)) { Recover(path4, (string p) => Decode(p, world, key)); AtomicWrite(path4, item2.Value); } else { ImportFile(path4, item2.Value); } } ImportFile(Path.Combine(path, "authority.key.backup"), key); ImportFile(Path.Combine(path, "authority.key"), key); } private void ImportFile(string path, byte[] bytes) { if (files.Exists(path)) { if (!((ReadOnlySpan<byte>)files.Read(path, 4198400)).SequenceEqual((ReadOnlySpan<byte>)bytes)) { throw new InvalidDataException("Authority import conflicts with existing world state"); } return; } if (files.Exists(path + ".pending")) { bool flag = false; try { flag = ((ReadOnlySpan<byte>)files.Read(path + ".pending", 4198400)).SequenceEqual((ReadOnlySpan<byte>)bytes); } catch (InvalidDataException) { } if (flag) { files.Promote(path + ".pending", path, path + ".backup"); return; } Quarantine(path + ".pending"); } AtomicWrite(path, bytes); } private void AtomicWrite(string path, byte[] bytes) { string text = path + ".pending"; files.WriteNew(text, bytes); files.Promote(text, path, path + ".backup"); } } public sealed class PurseLedger { public int Balance { get; private set; } public string Actor { get; private set; } = ""; public string Token { get; private set; } = ""; public string Epoch { get; private set; } = ""; public string Fingerprint { get; private set; } = ""; public long Delta { get; private set; } public bool Pending { get; private set; } public bool Committed { get; private set; } public bool Quarantined { get; private set; } public long ReservedAt { get; private set; } public PurseLedger(int initial) { Balance = Math.Max(initial, 0); } public bool Reserve(string actor, string token, string epoch, string fingerprint, long delta, out string reason) { reason = "InvalidReservation"; if (string.IsNullOrEmpty(actor) || string.IsNullOrEmpty(token) || string.IsNullOrEmpty(epoch) || string.IsNullOrEmpty(fingerprint) || delta < -2147483647 || delta > int.MaxValue) { return false; } if (Actor == actor && Token == token && Epoch == epoch) { reason = ((Pending && Fingerprint == fingerprint && Delta == delta) ? "Reserved" : "AlreadyFinished"); if (Pending && Fingerprint == fingerprint) { return Delta == delta; } return false; } if (Pending) { reason = "TraderBusy"; return false; } if (Balance + Math.Min(delta, 0L) < 0) { reason = "TraderPoor"; return false; } if (Balance + Math.Max(delta, 0L) > int.MaxValue) { reason = "PurseOverflow"; return false; } Actor = actor; Token = token; Epoch = epoch; Fingerprint = fingerprint; Delta = delta; Balance += (int)Math.Min(delta, 0L); Pending = true; Committed = false; Quarantined = false; ReservedAt = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); reason = "Reserved"; return true; } public bool Finish(string actor, string token, string epoch, bool committed, bool inventoryVerified = false) { if (!inventoryVerified || Quarantined || Actor != actor || Token != token || Epoch != epoch) { return false; } if (!Pending) { return Committed == committed; } Settle(committed); return true; } public bool FinishReported(string actor, string token, string epoch, bool committed) { if (Quarantined || Actor != actor || Token != token || Epoch != epoch) { return false; } if (Pending) { Settle(committed); } return true; } private void Settle(bool committed) { Balance += (int)(committed ? Math.Max(Delta, 0L) : (-Math.Min(Delta, 0L))); Pending = false; Committed = committed; } public bool QuarantineExpired(long now, int timeoutSeconds = 120) { if (!Pending || timeoutSeconds < 1 || now < ReservedAt || now - ReservedAt < timeoutSeconds) { return false; } Pending = false; Quarantined = true; return true; } public bool Refill(int next) { if (Pending || next < 0) { return false; } Balance = next; return true; } public string Encode() { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(2); binaryWriter.Write(Balance); binaryWriter.Write(Actor); binaryWriter.Write(Token); binaryWriter.Write(Epoch); binaryWriter.Write(Fingerprint); binaryWriter.Write(Delta); binaryWriter.Write(Pending); binaryWriter.Write(Committed); binaryWriter.Write(Quarantined); binaryWriter.Write(ReservedAt); return Convert.ToBase64String(memoryStream.ToArray()); } public static PurseLedger Decode(string encoded) { if (encoded == null || encoded.Length > 4096) { throw new InvalidDataException("Ledger exceeds bounds."); } using MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(encoded)); using BinaryReader binaryReader = new BinaryReader(memoryStream); int num = binaryReader.ReadInt32(); if (num != 1 && num != 2) { throw new InvalidDataException("Unsupported ledger version."); } int num2 = binaryReader.ReadInt32(); PurseLedger purseLedger = new PurseLedger(num2) { Actor = binaryReader.ReadString(), Token = binaryReader.ReadString(), Epoch = binaryReader.ReadString(), Fingerprint = binaryReader.ReadString(), Delta = binaryReader.ReadInt64(), Pending = binaryReader.ReadBoolean(), Committed = binaryReader.ReadBoolean() }; if (num == 2) { purseLedger.Quarantined = binaryReader.ReadBoolean(); purseLedger.ReservedAt = binaryReader.ReadInt64(); } if (num2 < 0 || purseLedger.ReservedAt < 0 || (purseLedger.Quarantined && (purseLedger.Pending || purseLedger.Committed)) || purseLedger.Delta < -2147483647 || purseLedger.Delta > int.MaxValue || (purseLedger.Pending && num2 + Math.Abs(purseLedger.Delta) > int.MaxValue) || purseLedger.Actor.Length > 128 || purseLedger.Token.Length > 128 || purseLedger.Epoch.Length > 128 || purseLedger.Fingerprint.Length > 128 || memoryStream.Position != memoryStream.Length || (purseLedger.Pending && (purseLedger.Actor.Length == 0 || purseLedger.Token.Length == 0 || purseLedger.Epoch.Length == 0 || purseLedger.Fingerprint.Length == 0))) { throw new InvalidDataException("Invalid ledger state."); } return purseLedger; } } public static class RefillTiming { public enum Operation { AuthorityRead, AuthorityWrite, LocalRead, LocalWrite, CloudRead, CloudWrite, Promote, Flush, CloudFinish } public struct Cost { public long Ticks; public int Count; } public struct Result { public long TotalTicks; public int Day; public int Scanned; public int Traders; public int Refilled; public Cost AuthorityRead; public Cost AuthorityWrite; public Cost LocalRead; public Cost LocalWrite; public Cost CloudRead; public Cost CloudWrite; public Cost Promote; public Cost Flush; public Cost CloudFinish; } public readonly struct Span : IDisposable { private readonly Operation operation; private readonly long start; private readonly bool enabled; internal Span(Operation operation, bool enabled) { this.operation = operation; this.enabled = enabled; start = (enabled ? Stopwatch.GetTimestamp() : 0); } public void Dispose() { if (enabled) { long ticks = Stopwatch.GetTimestamp() - start; switch (operation) { case Operation.AuthorityRead: Add(ref result.AuthorityRead, ticks); break; case Operation.AuthorityWrite: Add(ref result.AuthorityWrite, ticks); break; case Operation.LocalRead: Add(ref result.LocalRead, ticks); break; case Operation.LocalWrite: Add(ref result.LocalWrite, ticks); break; case Operation.CloudRead: Add(ref result.CloudRead, ticks); break; case Operation.CloudWrite: Add(ref result.CloudWrite, ticks); break; case Operation.Promote: Add(ref result.Promote, ticks); break; case Operation.Flush: Add(ref result.Flush, ticks); break; case Operation.CloudFinish: Add(ref result.CloudFinish, ticks); break; } } } } [ThreadStatic] private static bool active; [ThreadStatic] private static long started; [ThreadStatic] private static Result result; public static void Begin() { result = default(Result); started = Stopwatch.GetTimestamp(); active = true; } public static Result End() { result.TotalTicks = Stopwatch.GetTimestamp() - started; active = false; return result; } public static void SetDay(int day) { if (active) { result.Day = day; } } public static void Scanned() { if (active) { result.Scanned++; } } public static void Trader() { if (active) { result.Traders++; } } public static void Refilled() { if (active) { result.Refilled++; } } public static Span Measure(Operation operation) { return new Span(operation, active); } private static void Add(ref Cost cost, long ticks) { cost.Ticks += ticks; cost.Count++; } } public static class SellCursor { public static int Move(int current, int count, int direction) { if (count > 0) { return Math.Max(0, Math.Min(count - 1, (current >= 0) ? (current + Math.Sign(direction)) : 0)); } return -1; } } public sealed class HeldItem { public object Id { get; } public string Prefab { get; } public int Stack { get; } public int Quality { get; } public int MaxStack { get; } public HeldItem(object id, string prefab, int stack, int quality, int maxStack) { Id = id ?? throw new ArgumentNullException("id"); Prefab = prefab ?? throw new ArgumentNullException("prefab"); Stack = stack; Quality = quality; MaxStack = maxStack; } } public enum SellKind { Single, Stack, Combined } public sealed class SellSettings { public float PriceFactor { get; } public float QualityMultiplier { get; } public SellSettings(float priceFactor, float qualityMultiplier) { PriceFactor = priceFactor; QualityMultiplier = qualityMultiplier; } } public sealed class SellOffer { private readonly List<object> sources = new List<object>(); public SellKind Kind { get; } public string Prefab { get; } public int Stack { get; internal set; } public int Amount { get; internal set; } public int Quality { get; } public int PricePerItem { get; } public int Price { get; internal set; } public string Currency { get; } public IReadOnlyList<object> Sources => sources; public int Lots { get { if (Kind != SellKind.Single) { if (Kind != SellKind.Combined) { return Amount / Math.Max(Stack, 1); } return Amount; } return 1; } } internal SellOffer(SellKind kind, string prefab, int stack, int amount, int quality, int pricePerItem, int price, string currency) { Kind = kind; Prefab = prefab; Stack = stack; Amount = amount; Quality = quality; PricePerItem = pricePerItem; Price = price; Currency = currency; } internal void AddSource(object source) { sources.Add(source); } } public static class SellPlanner { public static List<SellOffer> Plan(IEnumerable<HeldItem> held, SellPriceTable table, SellSettings settings, Func<string, string?> resolveCurrency) { List<SellOffer> list = new List<SellOffer>(); foreach (HeldItem item in held) { int configuredQuality; IReadOnlyDictionary<int, SellPrice> readOnlyDictionary = table.Lookup(item.Prefab, item.Quality, out configuredQuality); if (readOnlyDictionary == null) { continue; } foreach (int item2 in readOnlyDictionary.Keys.OrderBy((int x) => x)) { SellPrice sellPrice = readOnlyDictionary[item2]; string text = resolveCurrency(sellPrice.Currency); if (text != null && !string.Equals(item.Prefab, text, StringComparison.Ordinal)) { if (item2 == 1) { Add(list, item, (item.MaxStack == 1) ? 1 : item.Stack, sellPrice.Price, settings, configuredQuality, (item.MaxStack != 1) ? SellKind.Combined : SellKind.Single, text); } else { Add(list, item, item2, sellPrice.Price, settings, configuredQuality, SellKind.Stack, text); } } } } list.RemoveAll((SellOffer offer) => offer.Amount != 0 && offer.Amount < offer.Stack); return list; } private static void Add(List<SellOffer> offers, HeldItem item, int itemStack, int itemPrice, SellSettings settings, int configuredQuality, SellKind kind, string currency) { int effectiveQuality = ((configuredQuality > 0) ? configuredQuality : item.Quality); int adjustedPrice = itemPrice; if ((kind != SellKind.Stack && settings.QualityMultiplier != 0f && configuredQuality == 0 && item.Quality > 1 && !TradeAmounts.TryGetQualityPrice(itemPrice, item.Quality, settings.QualityMultiplier, out adjustedPrice)) || !TradeAmounts.TryGetPrice(adjustedPrice, (kind != SellKind.Combined) ? 1 : itemStack, settings.PriceFactor, out var price, roundUp: false)) { return; } if (kind == SellKind.Single) { SellOffer sellOffer = new SellOffer(kind, item.Prefab, itemStack, 0, effectiveQuality, adjustedPrice, price, currency); sellOffer.AddSource(item.Id); offers.Add(sellOffer); return; } if (kind == SellKind.Stack) { SellOffer sellOffer2 = offers.Find((SellOffer o) => o.Kind == kind && o.Stack == itemStack && o.Prefab == item.Prefab && o.Quality == effectiveQuality && o.Price == price && o.Currency == currency); if (sellOffer2 != null) { sellOffer2.Amount = TradeAmounts.ClampBalance((long)sellOffer2.Amount + (long)item.Stack); sellOffer2.AddSource(item.Id); } else { SellOffer sellOffer3 = new SellOffer(kind, item.Prefab, itemStack, item.Stack, effectiveQuality, adjustedPrice, price, currency); sellOffer3.AddSource(item.Id); offers.Add(sellOffer3); } return; } SellOffer sellOffer4 = offers.Find((SellOffer o) => o.Kind == kind && o.Prefab == item.Prefab && o.Quality == effectiveQuality && o.Stack == 1 && o.PricePerItem == adjustedPrice && o.Currency == currency); if (sellOffer4 != null) { int num = TradeAmounts.ClampBalance((long)sellOffer4.Amount + (long)itemStack); if (!TradeAmounts.TryGetPrice(sellOffer4.PricePerItem, num, settings.PriceFactor, out var price2, roundUp: false)) { offers.Remove(sellOffer4); return; } sellOffer4.Amount = num; sellOffer4.Price = price2; sellOffer4.AddSource(item.Id); } else { SellOffer sellOffer5 = new SellOffer(kind, item.Prefab, 1, itemStack, effectiveQuality, adjustedPrice, price, currency); sellOffer5.AddSource(item.Id); offers.Add(sellOffer5); } } } public sealed class SellPrice { public int Price { get; } public string Currency { get; } public SellPrice(int price, string currency) { Price = price; Currency = currency ?? string.Empty; } } public sealed class SellPriceTable { private readonly Dictionary<string, Dictionary<int, SellPrice>> prices; public static SellPriceTable Empty { get; } = new SellPriceTable(new Dictionary<string, Dictionary<int, SellPrice>>()); public int Count => prices.Count; private SellPriceTable(Dictionary<string, Dictionary<int, SellPrice>> prices) { this.prices = prices; } public static SellPriceTable Build(IEnumerable<TradeEntry> entries, ITradeContext context) { Dictionary<string, Dictionary<int, SellPrice>> dictionary = new Dictionary<string, Dictionary<int, SellPrice>>(StringComparer.Ordinal); foreach (TradeEntry entry in entries) { if (EntryAvailability.CanPrice(entry, context)) { string key = Key(entry.Prefab, entry.Quality); if (!dictionary.TryGetValue(key, out var value)) { value = new Dictionary<int, SellPrice>(); dictionary.Add(key, value); } value[entry.Stack] = new SellPrice(entry.Price, entry.Currency); } } return new SellPriceTable(dictionary); } public IReadOnlyDictionary<int, SellPrice>? Lookup(string prefab, int itemQuality, out int configuredQuality) { configuredQuality = itemQuality; if (prices.TryGetValue(Key(prefab, itemQuality), out Dictionary<int, SellPrice> value)) { return value; } configuredQuality = 0; if (!prices.TryGetValue(Key(prefab, 0), out Dictionary<int, SellPrice> value2)) { return null; } return value2; } private static string Key(string prefab, int quality) { return prefab + "|" + quality; } } public static class ServiceRules { public static bool RepairPrice(int basePrice, int quality, float durability, float maximum, float factor, out int price) { price = 0; if (basePrice <= 0 || quality <= 0 || float.IsNaN(durability) || float.IsInfinity(durability) || float.IsNaN(maximum) || float.IsInfinity(maximum) || maximum <= 0f || durability < 0f || durability >= maximum || float.IsNaN(factor) || float.IsInfinity(factor) || factor < 0f) { return false; } double num = Math.Ceiling((double)basePrice * (double)quality * (double)((maximum - durability) / maximum) * (double)factor); if (num > 2147483647.0) { return false; } price = Math.Max(1, (int)num); return true; } } public static class TradeAmounts { public const int MaximumSliderLots = 16777216; public static bool TryGetItemCount(int lotSize, int lots, out int amount) { long num = (long)lotSize * (long)lots; amount = 0; if (lotSize <= 0 || lots <= 0 || num > int.MaxValue) { return false; } amount = (int)num; return true; } public static bool TryGetPrice(int pricePerLot, int lots, float factor, out int price, bool roundUp = true) { return TryGetPrice(pricePerLot, lots, Math.Round(factor, 2), out price, roundUp); } public static bool TryGetPrice(int pricePerLot, int lots, double factor, out int price, bool roundUp = true) { price = 0; if (pricePerLot <= 0 || lots <= 0 || double.IsNaN(factor) || double.IsInfinity(factor) || factor < 0.0 || factor > 2147483647.0) { return false; } decimal d = (decimal)pricePerLot * (decimal)lots * ToDecimal(factor); decimal num = Math.Max(roundUp ? decimal.Ceiling(d) : decimal.Floor(d), 1m); if (num > 2147483647m) { return false; } price = (int)num; return true; } public static bool TryGetQualityPrice(int basePrice, int quality, float multiplier, out int price) { price = 0; if (basePrice <= 0 || quality <= 0 || float.IsNaN(multiplier) || float.IsInfinity(multiplier) || Math.Abs((double)multiplier) > 2147483647.0) { return false; } decimal num = (decimal)basePrice * (1m + ToDecimal(multiplier) * (decimal)((long)quality - 1L)); if (num < 1m || num > 2147483647m) { return false; } price = (int)num; return true; } public static decimal ToDecimal(double value) { if (double.IsNaN(value) || double.IsInfinity(value) || Math.Abs(value) < 1E-09) { return 0m; } return decimal.Parse(value.ToString("R", CultureInfo.InvariantCulture), NumberStyles.Float, CultureInfo.InvariantCulture); } public static decimal ToDecimal(float value) { if (float.IsNaN(value) || float.IsInfinity(value) || Math.Abs(value) < 1E-09f) { return 0m; } return decimal.Parse(value.ToString("R", CultureInfo.InvariantCulture), NumberStyles.Float, CultureInfo.InvariantCulture); } public static int MaximumBuyLots(int lotSize, int pricePerLot, int currency, int capacity) { if (lotSize <= 0 || pricePerLot <= 0 || currency <= 0 || capacity <= 0) { return 0; } return Math.Min(16777216, Math.Min(capacity / lotSize, currency / pricePerLot)); } public static int MaximumSellLots(int lotSize, int pricePerLot, int availableItems, float factor, int budget) { return MaximumSellLots(lotSize, pricePerLot, availableItems, Math.Round(factor, 2), budget); } public static int MaximumSellLots(int lotSize, int pricePerLot, int availableItems, double factor, int budget) { if (lotSize <= 0 || pricePerLot <= 0 || availableItems <= 0 || budget <= 0) { return 0; } int num = 0; int num2 = Math.Min(16777216, availableItems / lotSize); while (num < num2) { int num3 = num + (num2 - num + 1) / 2; if (TryGetPrice(pricePerLot, num3, factor, out var price, roundUp: false) && price <= budget) { num = num3; } else { num2 = num3 - 1; } } return num; } public static bool TryEncodeStack(int stack, int quality, int multiplier, out int encoded) { encoded = 0; long num = stack + (long)multiplier * (long)quality; if (multiplier <= 1 || stack <= 0 || stack >= multiplier || quality < 0 || num > int.MaxValue) { return false; } encoded = (int)num; return true; } public static bool TryEncodeStack(int stack, int quality, out int encoded) { return TryEncodeStack(stack, quality, 1000000, out encoded); } public static void DecodeStack(int encoded, out int stack, out int quality) { stack = encoded % 1000000; quality = encoded / 1000000; } public static int ClampBalance(long amount) { return (int)Math.Max(0L, Math.Min(2147483647L, amount)); } } public sealed class JournalEntry { public string Token = ""; public string Actor = ""; public string BeforeInventory = ""; public string AfterInventory = ""; public long World; public byte Phase; public bool Consumed; public byte[] Request = Array.Empty<byte>(); } public sealed class TradeJournal { private readonly string directory; public TradeJournal(string directory) { this.directory = Path.GetFullPath(directory); } private string PathFor(string token) { if (!Guid.TryParseExact(token, "N", out var _)) { throw new InvalidDataException("Invalid receipt token"); } return Path.Combine(directory, token + ".receipt"); } public void Save(JournalEntry entry) { if (entry == null || entry.World == 0L || entry.Actor.Length == 0 || entry.Actor.Length > 128 || entry.Phase > 3 || entry.Request.Length > 98304 || entry.BeforeInventory.Length > 2097152 || entry.AfterInventory.Length > 2097152) { throw new InvalidDataException("Invalid journal entry"); } string text = PathFor(entry.Token); Directory.CreateDirectory(directory); if (!File.Exists(text)) { int num = 0; foreach (JournalEntry item in Read()) { if (item.World == entry.World && ++num >= 256) { throw new InvalidDataException("Journal receipt bound"); } } } byte[] array; using (MemoryStream memoryStream = new MemoryStream()) { using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(3); binaryWriter.Write(entry.Token); binaryWriter.Write(entry.World); binaryWriter.Write(entry.Actor); binaryWriter.Write(entry.Phase); binaryWriter.Write(entry.Request.Length); binaryWriter.Write(entry.Request); binaryWriter.Write(entry.BeforeInventory); binaryWriter.Write(entry.AfterInventory); binaryWriter.Write(entry.Consumed); array = memoryStream.ToArray(); } string text2 = text + ".pending"; using (FileStream fileStream = new FileStream(text2, FileMode.Create, FileAccess.Write, FileShare.None)) { using (SHA256 sHA = SHA256.Create()) { byte[] array2 = sHA.ComputeHash(array); fileStream.Write(array2, 0, array2.Length); } fileStream.Write(array, 0, array.Length); fileStream.Flush(flushToDisk: true); } if (File.Exists(text)) { File.Replace(text2, text, null); } else { File.Move(text2, text); } } public IReadOnlyList<JournalEntry> Read() { List<JournalEntry> list = new List<JournalEntry>(); if (!Directory.Exists(directory)) { return list; } using (IEnumerator<string> enumerator = Directory.EnumerateFiles(directory, "*.pending").GetEnumerator()) { if (enumerator.MoveNext()) { _ = enumerator.Current; throw new InvalidDataException("Incomplete journal write"); } } Dictionary<long, int> dictionary = new Dictionary<long, int>(); Dictionary<long, long> dictionary2 = new Dictionary<long, long>(); foreach (string item in Directory.EnumerateFiles(directory, "*.receipt")) { long length = new FileInfo(item).Length; if (length < 40 || length > 4400000) { throw new InvalidDataException("Journal size"); } byte[] array = File.ReadAllBytes(item); if (array.Length < 40 || array.Length > 4400000) { throw new InvalidDataException("Journal size"); } byte[] array2 = new byte[array.Length - 32]; Buffer.BlockCopy(array, 32, array2, 0, array2.Length); using (SHA256 sHA = SHA256.Create()) { byte[] array3 = sHA.ComputeHash(array2); for (int i = 0; i < 32; i++) { if (array3[i] != array[i]) { throw new InvalidDataException("Journal checksum"); } } } using MemoryStream memoryStream = new MemoryStream(array2); using BinaryReader binaryReader = new BinaryReader(memoryStream); int num = binaryReader.ReadInt32(); if (num < 1 || num > 3) { throw new InvalidDataException("Journal version"); } JournalEntry journalEntry = new JournalEntry { Token = binaryReader.ReadString(), World = binaryReader.ReadInt64(), Actor = binaryReader.ReadString(), Phase = binaryReader.ReadByte() }; int num2 = binaryReader.ReadInt32(); if (num2 < 0 || num2 > 98304) { throw new InvalidDataException("Journal request"); } journalEntry.Request = binaryReader.ReadBytes(num2); journalEntry.BeforeInventory = binaryReader.ReadString(); if (num >= 2) { journalEntry.AfterInventory = binaryReader.ReadString(); } journalEntry.Consumed = ((num >= 3) ? binaryReader.ReadBoolean() : (journalEntry.Phase == 2)); if (journalEntry.Request.Length != num2 || memoryStream.Position != memoryStream.Length || journalEntry.Actor.Length == 0 || journalEntry.Actor.Length > 128 || journalEntry.World == 0L || journalEntry.Phase > 3 || journalEntry.BeforeInventory.Length > 2097152 || journalEntry.AfterInventory.Length > 2097152 || !string.Equals(item, PathFor(journalEntry.Token), StringComparison.OrdinalIgnoreCase)) { throw new InvalidDataException("Journal identity"); } dictionary.TryGetValue(journalEntry.World, out var value); dictionary2.TryGetValue(journalEntry.World, out var value2); if (++value > 256 || (value2 += length) > 67108864) { throw new InvalidDataException("Journal world bound"); } dictionary[journalEntry.World] = value; dictionary2[journalEntry.World] = value2; list.Add(journalEntry); } return list; } public void Remove(string token, bool characterSaved = false) { if (!characterSaved) { foreach (JournalEntry item in Read()) { if (item.Token == token && (item.Phase == 1 || item.Phase == 2)) { throw new InvalidOperationException("Character-save proof required before discarding inventory evidence"); } } } File.Delete(PathFor(token)); } } public static class TradeQuoteRules { public static bool BuyMatches(IEnumerable<TradeEntry> entries, ITradeContext context, DiscoveryRule discovery, bool personalStateAvailable, string prefab, int amount, int quality, int price, string currency, int defaultQuality, float factor, Func<string, string?> resolveCurrency) { if (amount <= 0 || quality <= 0 || price <= 0 || string.IsNullOrEmpty(currency)) { return false; } int price2; return entries.Any((TradeEntry entry) => entry.Prefab == prefab && entry.Stack == amount && ((entry.Quality > 0) ? entry.Quality : defaultQuality) == quality && (personalStateAvailable || !NeedsPersonalKeys(entry)) && EntryAvailability.CanOffer(entry, context, discovery) && resolveCurrency(entry.Currency) == currency && TradeAmounts.TryGetPrice(entry.Price, 1, factor, out price2) && price2 == price); } public static bool SellMatches(IEnumerable<TradeEntry> entries, ITradeContext context, bool personalStateAvailable, string prefab, int amount, int quality, int lotSize, int maxStack, int price, string currency, float factor, float qualityMultiplier, Func<string, string?> resolveCurrency) { if (amount <= 0 || quality <= 0 || lotSize <= 0 || maxStack <= 0 || price <= 0 || string.IsNullOrEmpty(currency)) { return false; } SellPriceTable table = SellPriceTable.Build(entries.Where((TradeEntry entry) => personalStateAvailable || !NeedsPersonalKeys(entry)), context); return SellPlanner.Plan(new HeldItem[1] { new HeldItem(new object(), prefab, amount, quality, maxStack) }, table, new SellSettings(factor, qualityMultiplier), resolveCurrency).Any((SellOffer offer) => offer.Stack == lotSize && offer.Price == price && offer.Currency == currency && ((offer.Kind != SellKind.Single) ? ((offer.Kind != SellKind.Stack) ? (amount == offer.Amount) : (amount == offer.Stack)) : (amount == 1))); } private static bool NeedsPersonalKeys(TradeEntry entry) { if (TradeEntry.SplitKeys(entry.RequiredPlayerKey).Count == 0) { return TradeEntry.SplitKeys(entry.NotRequiredPlayerKey).Count != 0; } return true; } } public sealed class TradeRecovery { private readonly long world; private readonly string actor; private bool awaitingReservation = true; private bool inFlight; private double retryAt; public bool? Outcome { get; private set; } = false; public bool Done { get; private set; } public TradeRecovery(long world, string actor) { if (world == 0L || string.IsNullOrEmpty(actor)) { throw new ArgumentException("Missing recovery identity."); } this.world = world; this.actor = actor; } public void ReservationEnded() { awaitingReservation = false; } public void InventoryStarting() { awaitingReservation = false; Outcome = null; } public void InventoryFinished(bool success) { awaitingReservation = false; Outcome = success; } public bool TryDispatch(long currentWorld, string currentActor, double now) { if (Done || awaitingReservation || inFlight || !Outcome.HasValue || currentWorld != world || currentActor != actor || now < retryAt) { return false; } inFlight = true; retryAt = now + 2.0; return true; } public void Reply(bool acknowledged) { inFlight = false; Done |= acknowledged; } public void Suspend() { awaitingReservation = false; inFlight = false; } public static bool CanConfirm(JournalEntry entry, string loadedInventory) { if (entry.Phase == 2 && entry.AfterInventory.Length > 0) { return entry.AfterInventory == loadedInventory; } return false; } } } namespace ExtraPotions.Barter.Pricing { public static class PriceCurve { public static float BuyFactor(int balance, PurseRules rules) { if (!rules.Enabled || !rules.Flexible) { return 1f; } rules.Range(out var minimum, out var maximum); if (balance < minimum) { float t = ((minimum > 0) ? Clamp01((float)balance / (float)minimum) : 1f); return RoundToPercent(Lerp(rules.Markup, 1f, t)); } float t2 = Clamp01((float)(balance - minimum) / (float)Math.Max(maximum - minimum, 1)); return RoundToPercent(Lerp(1f, rules.Discount, t2)); } public static float SellFactor(int balance, PurseRules rules) { if (!rules.Enabled || !rules.Flexible) { return 1f; } rules.Range(out var minimum, out var maximum); if (balance < minimum) { float t = ((minimum > 0) ? Clamp01((float)balance / (float)minimum) : 1f); return RoundToPercent(Lerp(rules.Discount, 1f, t)); } float t2 = Clamp01((float)(balance - minimum) / (float)Math.Max(maximum - minimum, 1)); return RoundToPercent(Lerp(1f, rules.Markup, t2)); } public static float RoundToPercent(float factor) { if (float.IsNaN(factor) || float.IsInfinity(factor) || factor < 0f) { return 1f; } return (float)(Math.Round((double)factor * 100.0) / 100.0); } public static string Describe(float factor, bool reversed, out bool favourable) { favourable = true; if (factor == 1f) { return string.Empty; } favourable = (reversed && factor < 1f) || (!reversed && factor > 1f); double num = (factor - 1f) * 100f; return ((num >= 0.0) ? "+" : "-") + Math.Abs(Math.Round(num)).ToString("0", CultureInfo.InvariantCulture); } private static float Clamp01(float value) { if (!(value < 0f)) { if (!(value > 1f)) { return value; } return 1f; } return 0f; } private static float Lerp(float a, float b, float t) { return a + (b - a) * Clamp01(t); } } public static class PurseRefill { public static int Initial(PurseRules rules) { rules.Range(out var minimum, out var _); return minimum; } public static bool TryReplenish(int current, int lastDay, int today, PurseRules rules, out int next) { next = current; if (today - lastDay < Math.Max(rules.ReplenishDays, 1)) { return false; } rules.Range(out var minimum, out var maximum); if (current >= maximum && rules.DailyDecrease <= 0) { return false; } next = ((current >= maximum) ? Math.Max(maximum, current - Math.Max(rules.DailyDecrease, 0)) : Math.Max(minimum, Math.Min(maximum, TradeAmounts.ClampBalance((long)current + (long)rules.DailyIncrease)))); return true; } } public sealed class PurseRules { public bool Enabled { get; set; } public bool Flexible { get; set; } public int Minimum { get; set; } public int Maximum { get; set; } public int DailyIncrease { get; set; } public int DailyDecrease { get; set; } public float Discount { get; set; } public float Markup { get; set; } public int ReplenishDays { get; set; } public static PurseRules Defaults() { return new PurseRules { Enabled = BarterSettings.Purse.Enabled.Default, Flexible = BarterSettings.Purse.FlexiblePricing.Default, Minimum = BarterSettings.Purse.MinimumBalance.Default, Maximum = BarterSettings.Purse.MaximumBalance.Default, DailyIncrease = BarterSettings.Purse.DailyIncrease.Default, DailyDecrease = BarterSettings.Purse.DailyDecrease.Default, Discount = BarterSettings.Purse.Discount.Default, Markup = BarterSettings.Purse.Markup.Default, ReplenishDays = BarterSettings.Purse.ReplenishDays.Default }; } internal void Range(out int minimum, out int maximum) { minimum = Math.Max(Minimum, 0); maximum = Math.Max(Maximum, minimum); } } } namespace ExtraPotions.Barter.Lists { public static class ListEditRules { public static string Revision(IReadOnlyDictionary<string, string> files) { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true); using SHA256 sHA = SHA256.Create(); foreach (KeyValuePair<string, string> item in files.OrderBy<KeyValuePair<string, string>, string>((KeyValuePair<string, string> p) => p.Key, StringComparer.Ordinal)) { binaryWriter.Write(item.Key); binaryWriter.Write(item.Value); } binaryWriter.Flush(); return Convert.ToBase64String(sHA.ComputeHash(memoryStream.ToArray())); } public static string OwnedPath(string root, string relative) { if (string.IsNullOrWhiteSpace(relative) || relative.Contains("..") || relative.Contains("\\") || Path.IsPathRooted(relative)) { throw new ArgumentException("Invalid list path."); } string text = Path.GetFullPath(root).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar; string fullPath = Path.GetFullPath(Path.Combine(text, relative.Replace('/', Path.DirectorySeparatorChar))); if (!fullPath.StartsWith(text, StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException("List outside config root."); } string text2 = fullPath; while (text2 != null && text2.Length >= text.TrimEnd(Path.DirectorySeparatorChar).Length) { if ((File.Exists(text2) || Directory.Exists(text2)) && (File.GetAttributes(text2) & FileAttributes.ReparsePoint) != FileAttributes.None) { throw new IOException("Linked config paths cannot be edited."); } text2 = Path.GetDirectoryName(text2); } return fullPath; } public static bool AtomicReplace(string path, string expected, string replacement) { if (!File.Exists(path) || File.ReadAllText(path) != expected) { return false; } string text = path + ".barter-edit-" + Guid.NewGuid().ToString("N"); string destinationBackupFileName = path + ".barter-backup-" + Guid.NewGuid().ToString("N"); try { using (FileStream fileStream = new FileStream(text, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { byte[] bytes = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetBytes(replacement); fileStream.Write(bytes, 0, bytes.Length); fileStream.Flush(flushToDisk: true); } if (File.ReadAllText(path) != expected) { return false; } File.Replace(text, path, destinationBackupFileName); return true; } finally { if (File.Exists(text)) { File.Delete(text); } } } } public sealed class ListFileName { public string FileName { get; } public string Trader { get; } public ListKind Kind { get; } public string Identifier { get; } public ListFormat Format { get; } public string ListKey => TraderNames.ListKey(Trader, Kind); private ListFileName(string fileName, string trader, ListKind kind, string identifier, ListFormat format) { FileName = fileName; Trader = trader; Kind = kind; Identifier = identifier; Format = format; } public static bool IsSupportedExtension(string? extension) { if (!string.Equals(extension, ".json", StringComparison.OrdinalIgnoreCase) && !string.Equals(extension, ".yaml", StringComparison.OrdinalIgnoreCase) && !string.Equals(extension, ".yml", StringComparison.OrdinalIgnoreCase)) { return string.Equals(extension, ".csv", StringComparison.OrdinalIgnoreCase); } return true; } public static bool TryParse(string? fileName, out ListFileName? parsed, bool allowUnprefixed = false) { parsed = null; if (string.IsNullOrWhiteSpace(fileName)) { return false; } string fileName2 = Path.GetFileName(fileName); string extension = Path.GetExtension(fileName2); if (!IsSupportedExtension(extension)) { return false; } string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName2); bool flag = fileNameWithoutExtension.StartsWith("Barter.", StringComparison.OrdinalIgnoreCase); if (!flag && !allowUnprefixed) { return false; } string[] array = (flag ? fileNameWithoutExtension.Substring("Barter.".Length) : fileNameWithoutExtension).Split('.'); int num = 1; while (num < array.Length) { ListKind kind; if (string.Equals(array[num], "Buy", StringComparison.OrdinalIgnoreCase)) { kind = ListKind.Buy; } else { if (!string.Equals(array[num], "Sell", StringComparison.OrdinalIgnoreCase)) { num++; continue; } kind = ListKind.Sell; } string text = TraderNames.Normalize(string.Join(".", array.Take(num))); if (text.Length == 0) { return false; } string identifier = string.Join(".", array.Skip(num + 1)); ListFormat format = (string.Equals(extension, ".csv", StringComparison.OrdinalIgnoreCase) ? ListFormat.Csv : (string.Equals(extension, ".json", StringComparison.OrdinalIgnoreCase) ? ListFormat.Json : ListFormat.Yaml)); parsed = new ListFileName(fileName2, text, kind, identifier, format); return true; } return false; } public static IEnumerable<ListSourceFile> Order(IEnumerable<ListSourceFile> files) { return files.OrderBy((ListSourceFile file) => (!file.Embedded) ? 1 : 0).ThenBy<ListSourceFile, string>((ListSourceFile file) => file.FileName, StringComparer.OrdinalIgnoreCase).ThenBy<ListSourceFile, string>((ListSourceFile file) => file.FileName, StringComparer.Ordinal); } } public sealed class ListSourceFile { public string FileName { get; } public bool Embedded { get; } public ListSourceFile(string fileName, bool embedded) { FileName = fileName ?? throw new ArgumentNullException("fileName"); Embedded = embedded; } } public enum ListKind { Buy, Sell } public enum ListFormat { Yaml, Json, Csv } public sealed class ListContribution { public ListFileName Name { get; } public IReadOnlyList<TradeEntry> Entries { get; } public string SourceName { get; } public bool Embedded { get; } public ListContribution(ListFileName name, IReadOnlyList<TradeEntry> entries, string? sourceName = null, bool embedded = false) { Name = name ?? throw new ArgumentNullException("name"); Entries = entries ?? throw new ArgumentNullException("entries"); SourceName = (string.IsNullOrEmpty(sourceName) ? name.FileName : sourceName); Embedded = embedded; } } public sealed class TradeBook { private static readonly IReadOnlyList<TradeEntry> None = Array.Empty<TradeEntry>(); private readonly Dictionary<string, List<TradeEntry>> lists; public static TradeBook Empty { get; } = new TradeBook(new Dictionary<string, List<TradeEntry>>(StringComparer.OrdinalIgnoreCase)); public long Revision { get; } public IReadOnlyList<string> Traders => (from key in lists.Keys select key.Substring(0, key.LastIndexOf('.')) into trader where !TraderNames.IsCommon(trader) select trader).Distinct<string>(StringComparer.OrdinalIgnoreCase).OrderBy<string, string>((string trader) => trader, StringComparer.Ordinal).ToList(); private TradeBook(Dictionary<string, List<TradeEntry>> lists) { this.lists = lists; Revision = ComputeRevision(lists); } public static TradeBook Build(IEnumerable<ListContribution> files) { Dictionary<string, List<TradeEntry>> dictionary = new Dictionary<string, List<TradeEntry>>(StringComparer.OrdinalIgnoreCase); foreach (ListContribution item in files.OrderBy((ListContribution file) => (!file.Embedded) ? 1 : 0).ThenBy<ListContribution, string>((ListContribution file) => file.SourceName, StringComparer.OrdinalIgnoreCase).ThenBy<ListContribution, string>((ListContribution file) => file.SourceName, StringComparer.Ordinal)) { string listKey = item.Name.ListKey; if (!dictionary.TryGetValue(listKey, out var value)) { value = new List<TradeEntry>(); dictionary.Add(listKey, value); } value.AddRange(item.Entries.Select((TradeEntry entry) => entry.Clone())); } return new TradeBook(dictionary); } public TradeBook WithAutomatic(IEnumerable<TradeEntry> automatic) { Dictionary<string, List<TradeEntry>> dictionary = lists.ToDictionary<KeyValuePair<string, List<TradeEntry>>, string, List<TradeEntry>>((KeyValuePair<string, List<TradeEntry>> pair) => pair.Key, (KeyValuePair<string, List<TradeEntry>> pair) => pair.Value.Select((TradeEntry entry) => entry.Clone()).ToList(), StringComparer.OrdinalIgnoreCase); string key = TraderNames.CommonKey(ListKind.Sell); if (!dictionary.TryGetValue(key, out var value)) { value = new List<TradeEntry>(); dictionary.Add(key, value); } value.AddRange(automatic.Select(delegate(TradeEntry entry) { TradeEntry tradeEntry = entry.Clone(); tradeEntry.Automatic = true; return tradeEntry; })); return new TradeBook(dictionary); } public IReadOnlyList<TradeEntry> Buy(string? trader) { return Combined(trader, ListKind.Buy, includeAutomatic: true); } public IReadOnlyList<TradeEntry> Sell(string? trader, bool includeAutomatic = true) { return Combined(trader, ListKind.Sell, includeAutomatic); } private IReadOnlyList<TradeEntry> Combined(string? trader, ListKind kind, bool includeAutomatic) { List<TradeEntry> list = new List<TradeEntry>(); if (lists.TryGetValue(TraderNames.CommonKey(kind), out List<TradeEntry> value)) { list.AddRange(value.Where((TradeEntry entry) => includeAutomatic || !entry.Automatic)); } if (!TraderNames.IsCommon(trader) && lists.TryGetValue(TraderNames.ListKey(trader, kind), out List<TradeEntry> value2)) { list.AddRange(value2.Where((TradeEntry entry) => includeAutomatic || !entry.Automatic)); } if (list.Count != 0) { return list; } return None; } private static long ComputeRevision(Dictionary<string, List<TradeEntry>> lists) { ulong hash = 14695981039346656037uL; foreach (string item in lists.Keys.OrderBy<string, string>((string k) => k, StringComparer.OrdinalIgnoreCase)) { Mix(item.ToLowerInvariant()); foreach (TradeEntry item2 in lists[item]) { Mix(item2.Prefab); Mix(item2.Stack.ToString()); Mix(item2.Price.ToString()); Mix(item2.Quality.ToString()); Mix(item2.Currency); Mix(item2.RequiredGlobalKey); Mix(item2.NotRequiredGlobalKey); Mix(item2.RequiredPlayerKey); Mix(item2.NotRequiredPlayerKey); Mix(item2.Automatic ? "1" : "0"); } } return (long)hash; void Mix(string text) { foreach (char c in text) { hash ^= c; hash *= 1099511628211uL; } hash ^= 31uL; hash *= 1099511628211uL; } } } public static class CommonValuables { public static List<TradeEntry> Build(IEnumerable<(string Prefab, int Value)> items, IEnumerable<string> alreadyListed, string currencyPrefab) { HashSet<string> hashSet = new HashSet<string>(alreadyListed ?? Array.Empty<string>(), StringComparer.OrdinalIgnoreCase); List<TradeEntry> list = new List<TradeEntry>(); foreach (var (text, num) in items) { if (!string.IsNullOrWhiteSpace(text) && num > 0 && !string.Equals(text, "Coins", StringComparison.OrdinalIgnoreCase) && !string.Equals(text, currencyPrefab, StringComparison.OrdinalIgnoreCase) && hashSet.Add(text)) { list.Add(new TradeEntry { Prefab = text, Price = num, Stack = 1, Automatic = true }); } } return list; } } public sealed class TradeEntry { public string Prefab { get; set; } = string.Empty; public int Stack { get; set; } = 1; public int Price { get; set; } = 1; public int Quality { get; set; } public string Currency { get; set; } = string.Empty; public string RequiredGlobalKey { get; set; } = string.Empty; public string NotRequiredGlobalKey { get; set; } = string.Empty; public string RequiredPlayerKey { get; set; } = string.Empty; public string NotRequiredPlayerKey { get; set; } = string.Empty; public bool Automatic { get; set; } public IReadOnlyList<string> Validate() { List<string> list = new List<string>(); if (string.IsNullOrWhiteSpace(Prefab)) { list.Add("prefab is empty"); } int encoded; if (Price <= 0) { encoded = Price; list.Add("price must be above 0, found " + encoded); } if (Stack <= 0 || Stack >= 1000000) { string text = 999999.ToString(); encoded = Stack; list.Add("stack must be from 1 to " + text + ", found " + encoded); } if (Quality < 0) { list.Add("quality must not be negative, found " + Quality); } else if (Stack > 0 && Stack < 1000000 && !TradeAmounts.TryEncodeStack(Stack, Quality, out encoded)) { list.Add("quality " + Quality + " is too large to combine with stack " + Stack); } return list; } public static IReadOnlyList<string> SplitKeys(string? value) { List<string> list = new List<string>(); if (string.IsNullOrWhiteSpace(value)) { return list; } string[] array = value.Split(','); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { list.Add(text); } } return list; } public TradeEntry Clone() { return new TradeEntry { Prefab = Prefab, Stack = Stack, Price = Price, Quality = Quality, Currency = Currency, RequiredGlobalKey = RequiredGlobalKey, NotRequiredGlobalKey = NotRequiredGlobalKey, RequiredPlayerKey = RequiredPlayerKey, NotRequiredPlayerKey = NotRequiredPlayerKey, Automatic = Automatic }; } } public sealed class ListReadResult { public List<TradeEntry> Entries { get; } = new List<TradeEntry>(); public List<string> Errors { get; } = new List<string>(); public List<string> Warnings { get; } = new List<string>(); public bool Ok => Errors.Count == 0; } public static class TradeListReader { private static readonly string[] Fields = new string[9] { "prefab", "stack", "price", "quality", "currency", "requiredGlobalKey", "notRequiredGlobalKey", "requiredPlayerKey", "notRequiredPlayerKey" }; public static ListReadResult FromRows(IEnumerable<IReadOnlyDictionary<string, string>>? rows, string source) { ListReadResult listReadResult = new ListReadResult(); if (rows == null) { return listReadResult; } HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); int num = 0; foreach (IReadOnlyDictionary<string, string> row in rows) { num++; Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair<string, string> item in row) { if (item.Key == null) { continue; } if (!Fields.Contains<string>(item.Key, StringComparer.OrdinalIgnoreCase)) { if (hashSet.Add(item.Key)) { listReadResult.Warnings.Add("Ignored unknown field '" + item.Key + "' in '" + source + "'."); } } else { dictionary[item.Key] = item.Value ?? string.Empty; } } TradeEntry tradeEntry = new TradeEntry(); try { tradeEntry.Prefab = Text(dictionary, "prefab"); tradeEntry.Stack = Number(dictionary, "stack", tradeEntry.Stack, source, num); tradeEntry.Price = Number(dictionary, "price", tradeEntry.Price, source, num); tradeEntry.Quality = Number(dictionary, "quality", tradeEntry.Quality, source, num); } catch (FormatException ex) { listReadResult.Errors.Add(ex.Message); continue; } tradeEntry.Currency = Text(dictionary, "currency"); tradeEntry.RequiredGlobalKey = Text(dictionary, "requiredGlobalKey"); tradeEntry.NotRequiredGlobalKey = Text(dictionary, "notRequiredGlobalKey"); tradeEntry.RequiredPlayerKey = Text(dictionary, "requiredPlayerKey"); tradeEntry.NotRequiredPlayerKey = Text(dictionary, "notRequiredPlayerKey"); if (string.IsNullOrWhiteSpace(tradeEntry.Prefab)) { listReadResult.Warnings.Add($"Skipped row {num} of '{source}': it has no prefab."); continue; } IReadOnlyList<string> readOnlyList = tradeEntry.Validate(); if (readOnlyList.Count > 0) { listReadResult.Warnings.Add(string.Format("Skipped row {0} of '{1}' ({2}): {3}.", num, source, tradeEntry.Prefab, string.Join("; ", readOnlyList))); } else { listReadResult.Entries.Add(tradeEntry); } } return listReadResult; } public static ListReadResult FromCsv(string? content, string source) { try { List<List<string>> list = ParseCsvRecords(content ?? string.Empty); if (list.Count == 0) { return new ListReadResult(); } string[] array = list[0].Select((string header, int index) => (index != 0) ? (header ?? string.Empty).Trim() : (header ?? string.Empty).TrimStart('\ufeff').Trim()).ToArray(); if (!array.Any((string header) => string.Equals(header, "prefab", StringComparison.OrdinalIgnoreCase))) { throw new FormatException("CSV list '" + source + "' must contain the 'prefab' header."); } if (array.Where((string header) => header.Length > 0).GroupBy<string, string>((string header) => header, StringComparer.OrdinalIgnoreCase).Any((IGrouping<string, string> group) => group.Count() > 1)) { throw new FormatException("CSV list '" + source + "' contains a duplicate column header."); } List<IReadOnlyDictionary<string, string>> list2 = new List<IReadOnlyDictionary<string, string>>(); for (int num = 1; num < list.Count; num++) { List<string> list3 = list[num]; if (list3.All(string.IsNullOrWhiteSpace)) { continue; } if (list3.Skip(array.Length).Any((string value2) => !string.IsNullOrWhiteSpace(value2))) { throw new FormatException($"CSV row {num + 1} of '{source}' has more values than its header."); } Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); for (int num2 = 0; num2 < array.Length; num2++) { if (array[num2].Length != 0) { string value = ((num2 < list3.Count) ? (list3[num2] ?? string.Empty) : string.Empty); dictionary[array[num2]] = value; } } list2.Add(dictionary); } return FromRows(list2, source); } catch (FormatException ex) { return new ListReadResult { Errors = { ex.Message } }; } } private static string Text(Dictionary<string, string> row, string field) { if (!row.TryGetValue(field, out string value)) { return string.Empty; } return (value ?? string.Empty).Trim(); } private static int Number(Dictionary<string, string> row, string field, int fallback, string source, int rowNumber) { string text = Text(row, field); if (text.Length == 0) { return fallback; } if (int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return result; } throw new FormatException($"Invalid integer '{text}' in row {rowNumber}, field '{field}' of '{source}'."); } internal static List<List<string>> ParseCsvRecords(string content) { List<List<string>> list = new List<List<string>>(); List<string> list2 = new List<string>(); StringBuilder stringBuilder = new StringBuilder(); bool flag = false; bool flag2 = false; content = (content ?? string.Empty).TrimStart('\ufeff'); for (int i = 0; i < content.Length; i++) { char c = content[i]; if (flag) { if (c != '"') { stringBuilder.Append(c); } else if (i + 1 < content.Length && content[i + 1] == '"') { stringBuilder.Append('"'); i++; } else { flag = false; flag2 = true; } continue; } switch (c) { case '\n': case '\r': case ',': list2.Add(stringBuilder.ToString()); stringBuilder.Clear(); flag2 = false; if (c != ',') { if (c == '\r' && i + 1 < content.Length && content[i + 1] == '\n') { i++; } list.Add(list2); list2 = new List<string>(); } continue; case '"': if (stringBuilder.Length == 0 && !flag2) { flag = true; continue; } break; } if (flag2 || c == '"') { throw new FormatException("CSV contains unexpected text outside a quoted field."); } stringBuilder.Append(c); } if (flag) { throw new FormatException("CSV contains an unterminated quote in a quoted field."); } if (stringBuilder.Length > 0 || flag2 || list2.Count > 0) { list2.Add(stringBuilder.ToString()); list.Add(list2); } return list; } } public static class TraderNames { public static readonly IReadOnlyList<string> Vanilla = new string[3] { "Haldor", "Hildir", "BogWitch" }; public static string Normalize(string? name) { if (string.IsNullOrWhiteSpace(name)) { return string.Empty; } return name.Trim().ToLowerInvariant().Replace("$npc_", string.Empty) .Replace("npc_", string.Empty); } public static string ListKey(string? trader, ListKind kind) { return Normalize(trader) + "." + kind; } public static string CommonKey(ListKind kind) { return ListKey("common", kind); } public static bool IsCommon(string? trader) { return string.Equals(Normalize(trader), "common", StringComparison.Ordinal); } public static IEnumerable<string> SplitList(string? value) { string[] array = (value ?? string.Empty).Split(new char[4] { ',', ';', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { yield return text; } } } public static bool ListContains(string? value, string? trader) { string b = Normalize(trader); foreach (string item in SplitList(value)) { if (string.Equals(Normalize(item), b, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } } } namespace ExtraPotions.Barter.Currency { public static class CurrencyRules { public static string ForTrader(string? overrides, string? trader, out string? warning) { warning = null; if (string.IsNullOrWhiteSpace(overrides)) { return string.Empty; } string text = overrides.Trim(); if (text.IndexOf(':') < 0 && text.IndexOf('=') < 0 && text.IndexOf(',') < 0 && text.IndexOf(';') < 0 && text.IndexOf('\n') < 0 && text.IndexOf('\r') < 0) { warning = "Ignored invalid trader currency override '" + text + "'. Expected TraderPrefab:CurrencyPrefab."; return string.Empty; } string b = TraderNames.Normalize(trader); string[] array = text.Split(new char[4] { ',', ';', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); int num = text2.IndexOf(':'); if (num < 0) { num = text2.IndexOf('='); } if (num > 0 && num < text2.Length - 1 && string.Equals(TraderNames.Normalize(text2.Substring(0, num)), b, StringComparison.OrdinalIgnoreCase)) { return text2.Substring(num + 1).Trim(); } } return string.Empty; } public static string Effective(string? entryCurrency, string? traderCurrency) { string text = (entryCurrency ?? string.Empty).Trim(); if (text.Length <= 0) { return (traderCurrency ?? string.Empty).Trim(); } return text; } public static bool IsVanilla(string? currency) { if (!string.IsNullOrWhiteSpace(currency)) { return string.Equals(currency.Trim(), "Coins", StringComparison.OrdinalIgnoreCase); } return true; } public static string PrefabOrVanilla(string? currency) { if (!string.IsNullOrWhiteSpace(currency)) { return currency.Trim(); } return "Coins"; } } } namespace ExtraPotions.Barter.Config { public enum SettingScope { Local, ServerAuthoritative } public abstract class BarterSetting { public string Section { get; } public string Key { get; } public string Id => Section + "." + Key; public SettingScope Scope { get; } public string Description { get; } public abstract Type ValueType { get; } public abstract object DefaultValue { get; } public abstract object? Minimum { get; } public abstract object? Maximum { get; } private protected BarterSetting(string section, string key, SettingScope scope, string description) { if (string.IsNullOrWhiteSpace(section) || section.Trim() != section) { throw new ArgumentException("Expected a trimmed section name.", "section"); } if (string.IsNullOrWhiteSpace(key) || key.Trim() != key) { throw new ArgumentException("Expected a trimmed key.", "key"); } if (!Enum.IsDefined(typeof(SettingScope), scope)) { throw new ArgumentException("Unknown scope.", "scope"); } if (string.IsNullOrWhiteSpace(description)) { throw new ArgumentException("Expected a description.", "description"); } Section = section; Key = key; Scope = scope; Description = description; } public abstract object NormalizeBoxed(object? value); public override string ToString() { return Id + " (" + Scope.ToString() + ")"; } public static BarterSetting<bool> Toggle(string section, string key, bool defaultValue, SettingScope scope, string description) { return new BarterSetting<bool>(section, key, scope, description, defaultValue, null, (bool v) => v); } public static BarterSetting<int> Integer(string section, string key, int defaultValue, int min, int max, SettingScope scope, string description) { if (min > max) { throw new ArgumentException("Minimum above maximum.", "min"); } return new BarterSetting<int>(section, key, scope, description, defaultValue, (min, max), (int v) => Math.Min(max, Math.Max(min, v))); } public static BarterSetting<float> Number(string section, string key, float defaultValue, float min, float max, SettingScope scope, string description) { if (!(min <= max) || float.IsInfinity(min) || float.IsInfinity(max)) { throw new ArgumentException("Expected a finite range.", "min"); } return new BarterSetting<float>(section, key, scope, description, defaultValue, (min, max), (float v) => (!float.IsNaN(v) && !float.IsInfinity(v)) ? Math.Min(max, Math.Max(min, v)) : defaultValue); } public static BarterSetting<string> Text(string section, string key, string defaultValue, SettingScope scope, string description, Func<string, string> normalize) { if (normalize == null) { throw new ArgumentNullException("normalize"); } return new BarterSetting<string>(section, key, scope, description, defaultValue, null, normalize); } } public sealed class BarterSetting<T> : BarterSetting { private readonly Func<T, T> normalize; private readonly (T Min, T Max)? range; public T Default { get; } public override Type ValueType => typeof(T); public override object DefaultValue => Default; public override object? Minimum { get { if (!range.HasValue) { return null; } return range.Value.Min; } } public override object? Maximum { get { if (!range.HasValue) { return null; } return range.Value.Max; } } internal BarterSetting(string section, string key, SettingScope scope, string description, T defaultValue, (T, T)? range, Func<T, T> normalize) : base(section, key, scope, description) { this.normalize = normalize ?? throw new ArgumentNullException("normalize"); this.range = range; if (!EqualityComparer<T>.Default.Equals(normalize(defaultValue), defaultValue)) { throw new ArgumentException(base.Id + ": the default is outside its own rule.", "defaultValue"); } Default = defaultValue; } public T Normalize(T value) { return normalize(value); } public override object NormalizeBoxed(object? value) { return (value is T value2) ? Normalize(value2) : Default; } } public static class BarterSettings { public static class Purse { public const string Section = "Trader Purse"; public static readonly BarterSetting<bool> Enabled = BarterSetting.Toggle("Trader Purse", "Enabled", defaultValue: true, SettingScope.ServerAuthoritative, "Traders that have a Barter list (Haldor, Hildir and the Bog Witch with the shipped lists, Custom Traders, and any trader you write a list for) have a limited balance that goes down when they pay you and up when you buy, and refills each morning. Other mods' traders keep the game's prices."); public static readonly BarterSetting<bool> FlexiblePricing = BarterSetting.Toggle("Trader Purse", "Flexible Pricing", defaultValue: true, SettingScope.ServerAuthoritative, "Prices follow the trader's balance: a poor trader charges more and pays less, a rich one the opposite."); public static readonly BarterSetting<int> MinimumBalance = BarterSetting.Integer("Trader Purse", "Minimum Balance", 2000, 0, 1000000, SettingScope.ServerAuthoritative, "The balance a trader starts with and never refills below; prices are neutral at this balance."); public static readonly BarterSetting<int> MaximumBalance = BarterSetting.Integer("Trader Purse", "Maximum Balance", 6000, 0, 1000000, SettingScope.ServerAuthoritative, "The balance a trader refills up to; prices reach the discount and the markup at this balance."); public static readonly BarterSetting<int> DailyIncrease = BarterSetting.Integer("Trader Purse", "Daily Increase", 1000, 0, 1000000, SettingScope.ServerAuthoritative, "How much a trader's balance grows at each refill, up to the maximum."); public static readonly BarterSetting<int> DailyDecrease = BarterSetting.Integer("Trader Purse", "Daily Decrease", 0, 0, 1000000, SettingScope.ServerAuthoritative, "How much a balance above the maximum shrinks at each refill, down to the maximum."); public static readonly BarterSetting<float> Discount = BarterSetting.Number("Trader Purse", "Discount", 0.7f, 0f, 10f, SettingScope.ServerAuthoritative, "The price you pay at the maximum balance, as a factor (0.7 is 30 percent off); also what an empty purse pays you."); public static readonly BarterSetting<float> Markup = BarterSetting.Number("Trader Purse", "Markup", 1.5f, 0f, 10f, SettingScope.ServerAuthoritative, "The price you pay at an empty purse, as a factor (1.5 is 50 percent more); also what a full purse pays you."); public static readonly BarterSetting<int> ReplenishDays = BarterSetting.Integer("Trader Purse", "Replenish Days", 1, 1, 365, SettingScope.ServerAuthoritative, "How many in-game days pass between refills of a trader's balance."); public static readonly BarterSetting<bool> ReplenishMessage = BarterSetting.Toggle("Trader Purse", "Replenish Message", defaultValue: true, SettingScope.ServerAuthoritative, "Tell everyone in the morning when trader balances were refilled."); } public static class Lists { public const string Section = "Trade Lists"; public static readonly BarterSetting<bool> SellOnlyDiscovered = BarterSetting.Toggle("Trade Lists", "Sell Only Discovered", defaultValue: true, SettingScope.ServerAuthoritative, "A trader does not offer items you have not discovered yet."); public static readonly BarterSetting<string> AlwaysOffered = BarterSetting.Text("Trade Lists", "Always Offered", "", SettingScope.ServerAuthoritative, "Comma list of item prefabs a trader offers even when you have not discovered them. Items the game's own traders sell are always included.", Trim); public static readonly BarterSetting<bool> DisableVanillaItems = BarterSetting.Toggle("Trade Lists", "Disable Vanilla Items", defaultValue: false, SettingScope.ServerAuthoritative, "Remove the game's own items from trader buy lists, so only Barter's lists remain. Progress purchases such as inventory rows stay."); public static readonly BarterSetting<bool> DisableOtherModsItems = BarterSetting.Toggle("Trade Lists", "Disable Other Mods Items", defaultValue: false, SettingScope.ServerAuthoritative, "Remove items other mods added to trader buy lists before Barter adds its own."); public static readonly BarterSetting<float> QualityPriceMultiplier = BarterSetting.Number("Trade Lists", "Quality Price Multiplier", 0f, 0f, 10f, SettingScope.ServerAuthoritative, "Extra price per quality level above one when you sell gear, as a share of the listed price (0.5 adds half per level). 0 ignores quality."); public static readonly BarterSetting<bool> CommonValuables = BarterSetting.Toggle("Trade Lists", "Common Valuables", defaultValue: true, SettingScope.ServerAuthoritative, "Every item the game gives a value can be sold to any trader at that value, unless a Sell list prices it."); public static readonly BarterSetting<string> CustomTraders = BarterSetting.Text("Trade Lists", "Custom Traders", "", SettingScope.ServerAuthoritative, "Comma list of extra trader prefab names (case sensitive in the game) that get a balance and use Barter's lists.", Trim); public static readonly BarterSetting<string> TraderCurrencies = BarterSetting.Text("Trade Lists", "Trader Currencies", "", SettingScope.ServerAuthoritative, "Comma list of Trader:Currency pairs, for example Haldor:Coins,Hildir:SilverCoin. The item is that trader's currency unless a list entry names its own.", Trim); } public static class Services { public const string Section = "Trader Services"; public static readonly BarterSetting<bool> BuybackEnabled = BarterSetting.Toggle("Trader Services", "Buyback Enabled", defaultValue: true, SettingScope.ServerAuthoritative, "Keep exact sold items available to their seller until the next in-game day. The server owns receipts; buying back costs the original payout."); public static readonly BarterSetting<bool> RepairEnabled = BarterSetting.Toggle("Trader Services", "Repair Enabled", defaultValue: true, SettingScope.ServerAuthoritative, "Let traders repair carried equipment for their own currency. The server owns eligibility and prices."); public static readonly BarterSetting<int> RepairBasePrice = BarterSetting.Integer("Trader Services", "Repair Base Price", 10, 1, 1000000, SettingScope.ServerAuthoritative, "Base price for repairing a fully damaged quality-one item; scales with quality, missing durability and the trader's buy price."); } public static class Interface { public const string Section = "Interface"; public static readonly BarterSetting<bool> PriceTips = BarterSetting.Toggle("Interface", "Price Tips", defaultValue: true, SettingScope.Local, "Append this trader's current effective buy and sell prices to item tooltips while trading. Prices are quotes and may change before a trade."); public static readonly BarterSetting<bool> SellPanel = BarterSetting.Toggle("Interface", "Sell Panel", defaultValue: true, SettingScope.Local, "Show the sell list beside the trader's window."); public static readonly BarterSetting<bool> ShowPurse = BarterSetting.Toggle("Interface", "Show Purse", defaultValue: true, SettingScope.Local, "Show the trader's balance, currency and price change beside the trader's window."); public static readonly BarterSetting<bool> HideEquipped = BarterSetting.Toggle("Interface", "Hide Equipped Items", defaultValue: true, SettingScope.Local, "Leave unequipped hotbar gear out of the sell list. Equipped gear and protected extra slots are always kept."); } private static readonly Lazy<BarterSetting[]> all = new Lazy<BarterSetting[]>(() => new BarterSetting[25] { Purse.Enabled, Purse.FlexiblePricing, Purse.MinimumBalance, Purse.MaximumBalance, Purse.DailyIncrease, Purse.DailyDecrease, Purse.Discount, Purse.Markup, Purse.ReplenishDays, Purse.ReplenishMessage, Lists.SellOnlyDiscovered, Lists.AlwaysOffered, Lists.DisableVanillaItems, Lists.DisableOtherModsItems, Lists.QualityPriceMultiplier, Lists.CommonValuables, Lists.CustomTraders, Lists.TraderCurrencies, Services.BuybackEnabled, Services.RepairEnabled, Services.RepairBasePrice, Interface.PriceTips, Interface.SellPanel, Interface.ShowPurse, Interface.HideEquipped }); private static readonly Lazy<Dictionary<string, BarterSetting>> byId = new Lazy<Dictionary<string, BarterSetting>>(() => all.Value.ToDictionary<BarterSetting, string>((BarterSetting s) => s.Id, StringComparer.OrdinalIgnoreCase)); public static IReadOnlyList<BarterSetting> All => all.Value; public static BarterSetting? Find(string? id) { if (id == null || !byId.Value.TryGetValue(id.Trim(), out BarterSetting value
plugins/Barter/Barter.dll
Decompiled 10 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.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using ExtraPotions.Barter.Availability; using ExtraPotions.Barter.Config; using ExtraPotions.Barter.Currency; using ExtraPotions.Barter.Lists; using ExtraPotions.Barter.Pricing; using ExtraPotions.Barter.Runtime.Lists; using ExtraPotions.Barter.Trading; using ExtraPotions.Meadworks.Crucible; using ExtraPotions.Meadworks.Crucible.UI; using GUIFramework; using HarmonyLib; using Jotunn.Extensions; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Splatform; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; using YamlDotNet.Core; using YamlDotNet.RepresentationModel; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_guiutils")] [assembly: IgnoresAccessChecksTo("assembly_utils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ExtraPotions")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (C) 2026 expDARE (ExtraPotions Meadworks)")] [assembly: AssemblyFileVersion("0.1.4.0")] [assembly: AssemblyInformationalVersion("0.1.4")] [assembly: AssemblyProduct("Barter")] [assembly: AssemblyTitle("Barter")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.4.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ExtraPotions.Barter.Runtime { public static class BarterText { private static MeadworksTranslations catalog; public static void Initialize(BepInPlugin metadata, ManualLogSource log) { //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); Assembly assembly = typeof(BarterText).Assembly; foreach (string item in MeadworksLanguages.Supported) { using Stream stream = assembly.GetManifestResourceStream("Barter.localization." + item + ".json"); if (stream != null) { using StreamReader streamReader = new StreamReader(stream); dictionary.Add(item, streamReader.ReadToEnd()); } } catalog = MeadworksTranslations.Register(metadata, new ProductTranslations("barter", (IReadOnlyDictionary<string, string>)dictionary), (string)null, log); } public static string Text(string key) { MeadworksTranslations obj = catalog; return ((obj != null) ? obj.Text(key) : null) ?? key; } public static string Format(string key, params object[] arguments) { MeadworksTranslations obj = catalog; return ((obj != null) ? obj.Format(key, arguments) : null) ?? key; } public static string Token(string key) { MeadworksTranslations obj = catalog; return "$" + (((obj != null) ? obj.Token(key) : null) ?? ("meadworks_barter_" + key)); } public static void Tick() { MeadworksTranslations obj = catalog; if (obj != null) { obj.Update(); } } public static void Shutdown() { MeadworksTranslations obj = catalog; if (obj != null) { obj.Dispose(); } catalog = null; } } public sealed class BarterConfig { private const string Product = "com.meadworks.barter"; private readonly ConfigFile file; private readonly Dictionary<BarterSetting, ConfigEntryBase> entries = new Dictionary<BarterSetting, ConfigEntryBase>(); public event EventHandler Changed; private BarterConfig(ConfigFile file) { this.file = file; bool saveOnConfigSet = file.SaveOnConfigSet; file.SaveOnConfigSet = false; try { foreach (BarterSetting item in BarterSettings.All) { BindSetting(item); } } finally { file.SaveOnConfigSet = saveOnConfigSet; } file.Save(); file.SettingChanged += OnSettingChanged; } public static BarterConfig Bind(ConfigFile file, MeadworksLog log) { if (file == null) { throw new ArgumentNullException("file"); } BarterConfig barterConfig = new BarterConfig(file); foreach (KeyValuePair<BarterSetting, ConfigEntryBase> item in barterConfig.entries.Where<KeyValuePair<BarterSetting, ConfigEntryBase>>((KeyValuePair<BarterSetting, ConfigEntryBase> p) => (int)p.Key.Scope > 0)) { MeadworksBlockedOptions.Track("com.meadworks.barter", item.Value, (ConfigScope)1, true); } if (log != null) { log.Verbose("Configuration bound: " + barterConfig.entries.Count<KeyValuePair<BarterSetting, ConfigEntryBase>>((KeyValuePair<BarterSetting, ConfigEntryBase> p) => (int)p.Key.Scope == 0) + " Local, " + barterConfig.entries.Count<KeyValuePair<BarterSetting, ConfigEntryBase>>((KeyValuePair<BarterSetting, ConfigEntryBase> p) => (int)p.Key.Scope == 1) + " ServerAuthoritative."); } return barterConfig; } public ConfigEntryBase Entry(BarterSetting setting) { if (!entries.TryGetValue(setting ?? throw new ArgumentNullException("setting"), out ConfigEntryBase value)) { throw new ArgumentException("Not a Barter setting.", "setting"); } return value; } public T Get<T>(BarterSetting<T> setting) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 ConfigEntry<T> val = (ConfigEntry<T>)(object)Entry((BarterSetting)(object)setting); if ((int)((BarterSetting)setting).Scope != 1) { return setting.Normalize(val.Value); } return setting.Normalize(MeadworksConfig.Effective<T>(val, "com.meadworks.barter", (ConfigScope)1, setting.Default, true)); } public T GetFor<T>(BarterSetting<T> setting, string trader) { return TraderPolicies.Get(setting, trader, Get<T>(setting)); } public PurseRules Purse() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Expected O, but got Unknown return new PurseRules { Enabled = Get<bool>(Purse.Enabled), Flexible = Get<bool>(Purse.FlexiblePricing), Minimum = Get<int>(Purse.MinimumBalance), Maximum = Get<int>(Purse.MaximumBalance), DailyIncrease = Get<int>(Purse.DailyIncrease), DailyDecrease = Get<int>(Purse.DailyDecrease), Discount = Get<float>(Purse.Discount), Markup = Get<float>(Purse.Markup), ReplenishDays = Get<int>(Purse.ReplenishDays) }; } public void Shutdown() { file.SettingChanged -= OnSettingChanged; this.Changed = null; foreach (ConfigEntryBase value in entries.Values) { MeadworksBlockedOptions.Untrack(value); } } private void OnSettingChanged(object sender, SettingChangedEventArgs args) { this.Changed?.Invoke(this, EventArgs.Empty); } private void BindSetting(BarterSetting setting) { ConfigEntryBase value; if (!(setting is BarterSetting<bool> setting2)) { if (!(setting is BarterSetting<int> val)) { if (!(setting is BarterSetting<float> val2)) { if (!(setting is BarterSetting<string> setting3)) { throw new NotSupportedException(setting.Id + ": unsupported value type " + setting.ValueType.Name + "."); } value = (ConfigEntryBase)(object)Bind<string>(setting3, null); } else { value = (ConfigEntryBase)(object)Bind<float>(val2, (AcceptableValueBase)(object)new AcceptableValueRange<float>((float)((BarterSetting)val2).Minimum, (float)((BarterSetting)val2).Maximum)); } } else { value = (ConfigEntryBase)(object)Bind<int>(val, (AcceptableValueBase)(object)new AcceptableValueRange<int>((int)((BarterSetting)val).Minimum, (int)((BarterSetting)val).Maximum)); } } else { value = (ConfigEntryBase)(object)Bind<bool>(setting2, null); } entries.Add(setting, value); } private ConfigEntry<T> Bind<T>(BarterSetting<T> setting, AcceptableValueBase acceptable) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) return MeadworksConfig.Bind<T>(file, ((BarterSetting)setting).Section, ((BarterSetting)setting).Key, setting.Default, ((BarterSetting)setting).Description, (ConfigScope)(((int)((BarterSetting)setting).Scope != 0) ? 1 : 0), acceptable); } } internal sealed class SyncReceipt { private ZNet network; private ZRpc server; private string epoch = ""; private long world; internal long CurrentGeneration { get { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Invalid comparison between Unknown and I4 if (!Plugin.IsReady || (Object)(object)network == (Object)null || server == null || !server.IsConnected() || network != ZNet.instance || network.IsServer() || server != network.GetServerRPC()) { return -1L; } SessionSnapshot session = Plugin.Session; if ((int)session.Role != 3 || !session.AuthorityReady || session.WorldId == 0L || string.IsNullOrEmpty(session.SessionId)) { return -1L; } if (epoch.Length == 0) { epoch = session.SessionId; world = session.WorldId; } if (!(epoch == session.SessionId) || world != session.WorldId) { return -1L; } return session.Generation; } } internal void Observe() { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Invalid comparison between Unknown and I4 network = ZNet.instance; server = (((Object)(object)network != (Object)null && !network.IsServer()) ? network.GetServerRPC() : null); epoch = ""; world = 0L; if (server != null && server.IsConnected() && Plugin.IsReady) { SessionSnapshot session = Plugin.Session; if ((int)session.Role == 3 && !string.IsNullOrEmpty(session.SessionId)) { epoch = session.SessionId; world = session.WorldId; } } } } internal static class TraderPolicies { internal const string FileName = "Barter.Traders.json"; private const string Empty = "{\"version\":1,\"traders\":{}}"; private static ConfigEntry<string> carrier; private static SyncReceipt receipt = new SyncReceipt(); private static TraderPolicyBook current = TraderPolicyBook.Empty; private static long generation = -1L; private static float nextCheck; private static string applied; private static bool initialized; internal static bool Ready { get; private set; } internal static event Action Changed; internal static void Initialize(ConfigFile config) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown bool saveOnConfigSet = config.SaveOnConfigSet; config.SaveOnConfigSet = false; try { ConfigurationManagerAttributes val = new ConfigurationManagerAttributes { Browsable = false, ReadOnly = true }; carrier = ConfigFileExtensions.BindConfig<string>(config, "Trade Lists", "Managed Trader Policies", "", "Managed by Barter: server trader policies. Edit Barter.Traders.json on the host.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, val); } finally { config.SaveOnConfigSet = saveOnConfigSet; } config.Save(); SynchronizationManager.OnConfigurationSynchronized += Synchronized; initialized = true; Tick(); } private static void Synchronized(object sender, ConfigurationSynchronizationEventArgs args) { if (Plugin.IsReady && args.UpdatedPluginGUIDs != null && args.UpdatedPluginGUIDs.Contains("com.meadworks.barter")) { receipt.Observe(); nextCheck = 0f; } } internal static T Get<T>(BarterSetting<T> setting, string trader, T fallback) { return current.Get<T>(trader, setting, fallback); } internal static string Currency(string trader, string fallback) { return current.Currency(trader, fallback); } internal static bool HasTrader(string trader) { return current.Traders.ContainsKey(TraderNames.Normalize(trader)); } internal static void Tick() { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Invalid comparison between Unknown and I4 //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Invalid comparison between Unknown and I4 //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Invalid comparison between Unknown and I4 if (!initialized || !Plugin.IsReady) { return; } if (generation != Plugin.Session.Generation) { generation = Plugin.Session.Generation; applied = null; current = TraderPolicyBook.Empty; Ready = false; nextCheck = 0f; } if (Time.unscaledTime < nextCheck) { return; } nextCheck = Time.unscaledTime + 2f; try { bool flag = (int)Plugin.Session.Role == 3; AuthorityDecision val = MeadworksAuthority.Decide("com.meadworks.barter", (ConfigScope)1, true); if ((int)val.Source == 2 || (flag && ((int)val.Source != 1 || receipt.CurrentGeneration != generation))) { current = TraderPolicyBook.Empty; Ready = false; return; } string text; if (flag) { text = MeadworksConfig.Effective<string>(carrier, "com.meadworks.barter", (ConfigScope)1, "", true); } else { string text2 = Path.Combine(Paths.ConfigPath, "Barter.Traders.json"); if (!File.Exists(text2)) { text = "{\"version\":1,\"traders\":{}}"; } else { if (new FileInfo(text2).Length > 262144) { throw new InvalidDataException("Trader policies exceed 256 KiB."); } text = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetString(File.ReadAllBytes(text2)).TrimStart('\ufeff'); } } if (!(text == applied) || !Ready) { current = TraderPolicyJson.Read(text); applied = text; Ready = true; if (!flag && carrier.Value != text) { MeadworksConfig.Amend<string>(carrier, "com.meadworks.barter", (ConfigScope)1, text, (string)null, true); } TraderPolicies.Changed?.Invoke(); } } catch (Exception ex) { MeadworksLog log = Plugin.Log; if (log != null) { log.Warning("Trader policy reload refused: " + ex.GetType().Name + ": " + ex.Message); } } } internal static void Shutdown() { SynchronizationManager.OnConfigurationSynchronized -= Synchronized; initialized = false; carrier = null; current = TraderPolicyBook.Empty; applied = null; Ready = false; generation = -1L; receipt = new SyncReceipt(); TraderPolicies.Changed = null; } } internal static class TraderPolicyJson { internal const int MaximumBytes = 262144; internal static TraderPolicyBook Read(string text) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Invalid comparison between Unknown and I4 //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Expected O, but got Unknown if (Encoding.UTF8.GetByteCount(text) > 262144) { throw new InvalidDataException("Trader policies exceed 256 KiB."); } JsonTextReader val = new JsonTextReader((TextReader)new StringReader(text)) { DateParseHandling = (DateParseHandling)0, MaxDepth = 12 }; try { JObject val2 = JObject.Load((JsonReader)(object)val, new JsonLoadSettings { DuplicatePropertyNameHandling = (DuplicatePropertyNameHandling)2 }); if (((JsonReader)val).Read()) { throw new InvalidDataException("Unexpected content after trader policies."); } if (val2.Properties().Count() == 2) { JToken obj = val2["version"]; if (obj != null && (int)obj.Type == 6 && Extensions.Value<int>((IEnumerable<JToken>)val2["version"]) == 1) { JToken obj2 = val2["traders"]; JObject val3 = (JObject)(object)((obj2 is JObject) ? obj2 : null); if (val3 != null) { Dictionary<string, IDictionary<string, object>> dictionary = new Dictionary<string, IDictionary<string, object>>(StringComparer.Ordinal); foreach (JProperty item in val3.Properties()) { JToken value = item.Value; JToken obj3 = ((value is JObject) ? value : null) ?? throw new InvalidDataException("Expected trader settings object."); Dictionary<string, object> dictionary2 = new Dictionary<string, object>(StringComparer.Ordinal); foreach (JProperty item2 in ((JObject)obj3).Properties()) { JToken value2 = item2.Value; JValue val4 = (JValue)(object)((value2 is JValue) ? value2 : null); if (val4 == null || val4.Value == null) { throw new InvalidDataException("Expected scalar setting values."); } dictionary2.Add(item2.Name, val4.Value); } dictionary.Add(item.Name, dictionary2); } return new TraderPolicyBook((IDictionary<string, IDictionary<string, object>>)dictionary); } } } throw new InvalidDataException("Expected version 1 and a traders object."); } finally { ((IDisposable)val)?.Dispose(); } } internal static string Write(TraderPolicyBook book) { return JsonConvert.SerializeObject((object)new { version = 1, traders = book.Traders }, (Formatting)1); } } internal sealed class AuthorityReceipt { internal string Actor = ""; internal string Fingerprint = ""; internal string Epoch = ""; internal byte State; internal bool Applied; internal bool CharacterConsumed; private static readonly Dictionary<ZDOID, string> rotated = new Dictionary<ZDOID, string>(); private static string rotationEpoch = ""; internal static void Rotate(ZDO zdo, PurseLedger ledger, string epoch) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) if (rotationEpoch != epoch) { rotated.Clear(); rotationEpoch = epoch; } if (rotated.TryGetValue(zdo.m_uid, out string value) && value == epoch) { return; } string[] array = AuthorityState.Fields(zdo, "receipt-"); foreach (string text in array) { string text2 = text.Substring("receipt-".Length); if (!(text2 == ledger.Token)) { AuthorityReceipt authorityReceipt = Read(zdo, text2); if (authorityReceipt != null && authorityReceipt.Applied && authorityReceipt.Epoch != epoch) { AuthorityState.Remove(zdo, text); } } } rotated[zdo.m_uid] = epoch; } internal static void FinalizePrevious(ZDO zdo, PurseLedger ledger) { if (ledger.Pending || ledger.Token.Length == 0) { return; } AuthorityReceipt authorityReceipt = Read(zdo, ledger.Token); if (authorityReceipt != null && !authorityReceipt.Applied) { if (authorityReceipt.Actor != ledger.Actor || authorityReceipt.Fingerprint != ledger.Fingerprint) { throw new InvalidDataException("Prior settlement receipt mismatch"); } authorityReceipt.State = (byte)(ledger.Quarantined ? 4 : ((authorityReceipt.State == 5 || authorityReceipt.State == 6) ? authorityReceipt.State : (ledger.Committed ? 1 : 2))); authorityReceipt.Applied = true; authorityReceipt.Save(zdo, ledger.Token); } } internal static AuthorityReceipt Read(ZDO zdo, string token) { string text = AuthorityState.Read(zdo, "receipt-" + token); if (text.Length == 0) { return null; } using MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(text)); using BinaryReader binaryReader = new BinaryReader(memoryStream); byte b = binaryReader.ReadByte(); if (b != 1 && b != 2) { throw new InvalidDataException("Receipt version"); } AuthorityReceipt authorityReceipt = new AuthorityReceipt { Actor = binaryReader.ReadString(), Fingerprint = binaryReader.ReadString(), State = binaryReader.ReadByte(), Applied = binaryReader.ReadBoolean() }; if (b >= 2) { authorityReceipt.Epoch = binaryReader.ReadString(); authorityReceipt.CharacterConsumed = binaryReader.ReadBoolean(); } else { authorityReceipt.CharacterConsumed = authorityReceipt.State == 1 || authorityReceipt.State == 5; } if (authorityReceipt.Actor.Length == 0 || authorityReceipt.Actor.Length > 128 || authorityReceipt.Fingerprint.Length != 44 || authorityReceipt.Epoch.Length > 128 || authorityReceipt.State > 6 || memoryStream.Position != memoryStream.Length) { throw new InvalidDataException("Receipt state"); } return authorityReceipt; } internal void Save(ZDO zdo, string token) { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write((byte)2); binaryWriter.Write(Actor); binaryWriter.Write(Fingerprint); binaryWriter.Write(State); binaryWriter.Write(Applied); binaryWriter.Write(Epoch); binaryWriter.Write(CharacterConsumed); AuthorityState.Write(zdo, "receipt-" + token, Convert.ToBase64String(memoryStream.ToArray())); } } internal static class AuthorityState { private static AuthenticatedStateStore store; private static World storedWorld; private static string storedPath; private static FileSource storedSource; internal static bool IsAuthority { get { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } } private static AuthenticatedStateStore Store { get { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Invalid comparison between Unknown and I4 //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O, but got Unknown //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Invalid comparison between Unknown and I4 //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Expected O, but got Unknown World world = ZNet.World; if (world == null) { throw new InvalidOperationException("Authority world save is unavailable"); } string text = (world.IsChunkedSave() ? Path.Combine(world.GetSaveDirectory(world.m_fileSource), "Barter.Authority") : (world.GetDBPath() + ".Barter.Authority")); if (store == null || storedWorld != world || storedPath != text || storedSource != world.m_fileSource) { AuthenticatedStateStore val = new AuthenticatedStateStore(text, (IAuthorityStorage)(object)(((int)world.m_fileSource == 4) ? new WorldAuthorityStorage(world.m_fileSource) : null), world.m_needsDB || FileHelpers.Exists(world.GetDBPath(), world.m_fileSource)); if (store != null && storedWorld == world) { val.ImportFrom(Plugin.Session.WorldId, store, true); } else if (world.IsChunkedSave()) { val.ImportFrom(Plugin.Session.WorldId, new AuthenticatedStateStore(world.GetDBPath() + ".Barter.Authority", (IAuthorityStorage)(object)(((int)world.m_fileSource == 4) ? new WorldAuthorityStorage(world.m_fileSource) : null), false), false); } val.ImportLocal(Plugin.Session.WorldId, Path.Combine(Paths.ConfigPath, "Barter.Authority")); store = val; storedWorld = world; storedPath = text; storedSource = world.m_fileSource; } return store; } } private static string ObjectId(ZDO zdo) { return ((ZDOID)(ref zdo.m_uid)).UserID.ToString(CultureInfo.InvariantCulture) + ":" + ((ZDOID)(ref zdo.m_uid)).ID.ToString(CultureInfo.InvariantCulture); } internal unsafe static string Read(ZDO zdo, string field, string displayMirror = "") { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) Span val = RefillTiming.Measure((Operation)0); try { if (!IsAuthority) { return displayMirror; } if (zdo == null || !Plugin.IsReady || !Plugin.Session.AuthorityReady) { throw new InvalidOperationException("Authority state is unavailable"); } zdo.SetOwner(ZDOMan.GetSessionID()); return Store.Read(Plugin.Session.WorldId, ObjectId(zdo), field); } finally { ((IDisposable)(*(Span*)(&val))/*cast due to .constrained prefix*/).Dispose(); } } internal unsafe static void Write(ZDO zdo, string field, string value) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) Span val = RefillTiming.Measure((Operation)1); try { if (!IsAuthority || zdo == null || !Plugin.IsReady || !Plugin.Session.AuthorityReady) { throw new InvalidOperationException("Only the ready authority writes trader state"); } zdo.SetOwner(ZDOMan.GetSessionID()); Store.Write(Plugin.Session.WorldId, ObjectId(zdo), field, value); } finally { ((IDisposable)(*(Span*)(&val))/*cast due to .constrained prefix*/).Dispose(); } } internal static string[] Fields(ZDO zdo, string prefix) { if (!IsAuthority || zdo == null || !Plugin.IsReady || !Plugin.Session.AuthorityReady) { throw new InvalidOperationException("Authority state is unavailable"); } return Store.Fields(Plugin.Session.WorldId, ObjectId(zdo), prefix); } internal static void Remove(ZDO zdo, string field) { if (!IsAuthority || zdo == null || !Plugin.IsReady || !Plugin.Session.AuthorityReady) { throw new InvalidOperationException("Authority state is unavailable"); } Store.Remove(Plugin.Session.WorldId, ObjectId(zdo), field); } } internal static class BarterOperations { internal const string ReserveId = "meadworks.barter.reserve"; internal const string FinishId = "meadworks.barter.finish"; internal const string EditListId = "meadworks.barter.edit_list"; internal const string RecoverId = "meadworks.barter.recover"; private static readonly Version Protocol = new Version(1, 0); internal static readonly OperationDescriptor Reserve = new OperationDescriptor("meadworks.barter.reserve", Protocol); internal static readonly OperationDescriptor Finish = new OperationDescriptor("meadworks.barter.finish", Protocol); internal static readonly OperationDescriptor EditList = new OperationDescriptor("meadworks.barter.edit_list", Protocol); internal static readonly OperationDescriptor Recover = new OperationDescriptor("meadworks.barter.recover", Protocol); } internal static class BuyService { private static bool busy; private static int pendingGeneration; internal static void ResetPending() { busy = false; pendingGeneration++; } internal static void Buy(StoreGui store, TradeItem item, OfferInfo info) { if (busy || (Object)(object)store == (Object)null || (Object)(object)store.m_trader == (Object)null || item == null || (Object)(object)Player.m_localPlayer == (Object)null) { return; } Player player = Player.m_localPlayer; if ((Object)(object)item.m_prefab == (Object)null || !string.IsNullOrEmpty(item.m_buyKey) || !string.IsNullOrEmpty(item.m_incrementKey)) { MeadworksLog log = Plugin.Log; if (log != null) { log.Warning("Barter refused a native key or inventory-upgrade reward; atomic reward rollback is not qualified."); } ((Character)player).Message((MessageType)2, TradeAuthority.UnavailableReason, 0, (Sprite)null, false); return; } Inventory inventory = ((Humanoid)player).GetInventory(); if (OptionalTradeIntegrations.InventoryHeld(inventory)) { ((Character)player).Message((MessageType)2, BarterText.Text("trade_inventory_held"), 0, (Sprite)null, false); return; } if (TradeAuthority.RecoveryBlocked || SafeTradeInventory.IsBlocked(inventory)) { ((Character)player).Message((MessageType)2, TradeAuthority.UnavailableReason, 0, (Sprite)null, false); return; } Trader trader = store.m_trader; ItemDrop currency = TradeOffers.CurrencyOf(info, store); int price = item.m_price; bool flag = info?.FromList ?? false; int stack = (flag ? item.m_stack : Math.Min(item.m_stack, item.m_prefab.m_itemData.m_shared.m_maxStackSize)); int quality = ((info != null && info.Quality > 0) ? info.Quality : item.m_prefab.m_itemData.m_quality); if ((Object)(object)currency == (Object)null || price < 0 || stack <= 0) { return; } if (Currencies.Count(inventory, currency) < price) { ((Character)player).Message((MessageType)2, BarterText.Format("buy_need", price, Currencies.Name(currency)), 0, (Sprite)null, false); return; } TradeRequest request = new TradeRequest { Buy = true, Vanilla = !flag, Prefab = Utils.GetPrefabName(((Component)item.m_prefab).gameObject), Currency = Utils.GetPrefabName(((Component)currency).gameObject), Amount = stack, Quality = quality, LotSize = stack, Price = price }; busy = true; int attempt = ++pendingGeneration; if (!TradeAuthority.TryBegin(store, request, delegate(TradePermit permit) { //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_01d8: 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_0202: Unknown result type (might be due to invalid IL or missing references) //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) try { if (attempt != pendingGeneration) { permit?.Finish(success: false); } else if (permit == null) { ((Character)player).Message((MessageType)2, TradeAuthority.UnavailableReason, 0, (Sprite)null, false); } else if (!permit.IsCurrent || (Object)(object)Player.m_localPlayer != (Object)(object)player || (Object)(object)store == (Object)null || (Object)(object)store.m_trader != (Object)(object)trader || ((Humanoid)player).GetInventory() != inventory || !StoreGui.IsVisible()) { permit.Finish(success: false); } else { List<SafeTradeInventory.Take> list = SafeTradeInventory.Currency(inventory, currency, price); if (((IEnumerable<SafeTradeInventory.Take>)list).Sum((Func<SafeTradeInventory.Take, long>)((SafeTradeInventory.Take take) => take.Amount)) != price) { permit.Finish(success: false); } else { bool cheated = list.Any((SafeTradeInventory.Take take) => take.Item.m_cheated); ItemData val = SafeTradeInventory.Template(item.m_prefab, quality, cheated); ExchangeResult exchangeResult = SafeTradeInventory.Exchange(inventory, list, val, stack); if (exchangeResult == ExchangeResult.Unresolved) { MeadworksLog log2 = Plugin.Log; if (log2 != null) { log2.Warning("Trade inventory rollback could not be proven; the server reservation remains locked for recovery."); } } else { permit.Finish(exchangeResult == ExchangeResult.Completed); if (exchangeResult != ExchangeResult.Completed) { ((Character)player).Message((MessageType)2, BarterText.Text((exchangeResult == ExchangeResult.InventoryHeld) ? "trade_inventory_held" : "buy_no_room"), 0, (Sprite)null, false); } else { trader.OnBought(item); store.m_buyEffects.Create(((Component)store).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID)); EffectList buyPlayerEffects = item.m_buyPlayerEffects; if (buyPlayerEffects != null) { buyPlayerEffects.Create(((Component)player).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID)); } if (item.m_levelUpEffect) { player.OnSkillLevelup((SkillType)0, 0f); } ((Character)player).ShowPickupMessage(val, stack); Gogan.LogEvent("Game", "BoughtItem", ((Object)item.m_prefab).name, 0L); } } } } } finally { if (attempt == pendingGeneration) { busy = false; } if ((Object)(object)store != (Object)null && (Object)(object)store.m_trader == (Object)(object)trader && StoreGui.IsVisible()) { store.FillList(); } } })) { busy = false; ((Character)player).Message((MessageType)2, TradeAuthority.UnavailableReason, 0, (Sprite)null, false); } } } internal static class Currencies { private static ItemDrop vanilla; private static readonly HashSet<string> warned = new HashSet<string>(StringComparer.OrdinalIgnoreCase); internal static ItemDrop Vanilla(StoreGui store = null) { if ((Object)(object)vanilla != (Object)null) { return vanilla; } vanilla = (ItemDrop)(((Object)(object)store != (Object)null && (Object)(object)store.m_coinPrefab != (Object)null && ((Object)store.m_coinPrefab).name == "Coins") ? ((object)store.m_coinPrefab) : ((object)TradeWorld.Item("Coins"))); return vanilla; } internal static string NameFor(string trader) { string text = default(string); string fallback = CurrencyRules.ForTrader(Plugin.Settings.Get<string>(Lists.TraderCurrencies), trader, ref text); if (text != null && warned.Add(text)) { MeadworksLog log = Plugin.Log; if (log != null) { log.Warning(text); } } return TraderPolicies.Currency(trader, fallback); } internal static ItemDrop Resolve(string prefab, ItemDrop fallback) { if (CurrencyRules.IsVanilla(prefab)) { return Vanilla(); } ItemDrop val = TradeWorld.Item(prefab); if ((Object)(object)val != (Object)null) { return val; } if (warned.Add("missing:" + prefab)) { MeadworksLog log = Plugin.Log; if (log != null) { log.Warning("Currency item prefab '" + prefab + "' was not found. The trader's default currency will be used."); } } return fallback ?? Vanilla(); } internal static ItemDrop OfTrader(string trader) { return Resolve(NameFor(trader), Vanilla()); } internal static ItemDrop Apply(StoreGui store, Trader trader) { if ((Object)(object)store == (Object)null) { return null; } Vanilla(store); ItemDrop val = (((Object)(object)trader == (Object)null) ? Vanilla() : OfTrader(TradeWorld.TraderName(trader))); if ((Object)(object)val == (Object)null) { val = Vanilla(); } if ((Object)(object)val != (Object)null) { store.m_coinPrefab = val; SetIcons(store.m_rootPanel, val); } return val; } internal static void Restore(StoreGui store) { if ((Object)(object)store != (Object)null) { ItemDrop val = vanilla ?? TradeWorld.Item("Coins"); if ((Object)(object)val != (Object)null) { store.m_coinPrefab = val; SetIcons(store.m_rootPanel, val); } } } internal static int Count(Inventory inventory, ItemDrop currency) { if (inventory != null && !((Object)(object)currency == (Object)null)) { return inventory.CountItems(currency.m_itemData.m_shared.m_name, -1, true); } return 0; } internal static void SetIcons(GameObject root, ItemDrop currency) { if ((Object)(object)root == (Object)null || (Object)(object)currency == (Object)null) { return; } Sprite icon = currency.m_itemData.GetIcon(); Image[] componentsInChildren = root.GetComponentsInChildren<Image>(true); foreach (Image val in componentsInChildren) { string name = ((Object)((Component)val).gameObject).name; if (name.IndexOf("coin", StringComparison.OrdinalIgnoreCase) >= 0 && name.IndexOf("bkg", StringComparison.OrdinalIgnoreCase) < 0) { val.sprite = icon; } } } internal static string Name(ItemDrop currency) { if (!((Object)(object)currency == (Object)null)) { return Localization.instance.Localize(currency.m_itemData.m_shared.m_name); } return string.Empty; } } internal static class OptionalTradeIntegrations { internal const string EpicLootGuid = "randyknapp.mods.epicloot"; internal const string ExtraSlotsGuid = "shudnal.ExtraSlots"; internal const string AzuSlotsGuid = "Azumatt.AzuExtendedPlayerInventory"; private static readonly Dictionary<string, object> plugins = new Dictionary<string, object>(); private static readonly HashSet<string> warned = new HashSet<string>(); private const string BurrowHoldId = "meadworks.burrow.inventory_hold"; private const string BurrowOwner = "com.meadworks.burrow"; internal static bool EpicLootAdventure { get { if (!plugins.TryGetValue("randyknapp.mods.epicloot", out object value)) { return false; } try { MethodInfo method = value.GetType().GetMethod("IsAdventureModeEnabled", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method == null || method.ReturnType != typeof(bool)) { throw new MissingMethodException("IsAdventureModeEnabled"); } return (bool)method.Invoke(method.IsStatic ? null : value, null); } catch (Exception error) { Warn("randyknapp.mods.epicloot", error); return true; } } } internal static bool InventoryHeld(Inventory inventory) { if (inventory == null || !Plugin.IsReady) { return false; } try { CapabilityRegistry capabilities = Plugin.Services.Capabilities; IReadOnlyList<CapabilityMetadata> readOnlyList = capabilities.Providers("meadworks.burrow.inventory_hold"); if (readOnlyList.Count == 0) { return false; } Func<object, bool> func = default(Func<object, bool>); if (readOnlyList.Count != 1 || readOnlyList[0].Owner != "com.meadworks.burrow" || !capabilities.TryResolve<Func<object, bool>>("meadworks.burrow.inventory_hold", new Version(1, 0), ref func) || func == null) { throw new InvalidOperationException("Burrow inventory hold contract unavailable."); } return func(inventory); } catch (Exception error) { Warn("com.meadworks.burrow", error); return true; } } internal static void Initialize() { Shutdown(); string[] array = new string[3] { "randyknapp.mods.epicloot", "shudnal.ExtraSlots", "Azumatt.AzuExtendedPlayerInventory" }; foreach (string key in array) { if (Chainloader.PluginInfos.TryGetValue(key, out var value)) { plugins[key] = value.Instance; } } } internal static bool ProtectedItem(ItemData item, Player player) { if (item == null) { return true; } string[] array = new string[2] { "shudnal.ExtraSlots", "Azumatt.AzuExtendedPlayerInventory" }; foreach (string text in array) { if (!plugins.TryGetValue(text, out object value)) { continue; } try { if (text == "shudnal.ExtraSlots") { MethodInfo method = (value.GetType().Assembly.GetType("ExtraSlots.API") ?? throw new TypeLoadException("ExtraSlots.API")).GetMethod("GetAllExtraSlotsItems", BindingFlags.Static | BindingFlags.Public, null, Type.EmptyTypes, null); if (method == null || !(method.Invoke(null, null) is IEnumerable enumerable)) { throw new MissingMethodException("GetAllExtraSlotsItems"); } foreach (object item2 in enumerable) { if (item == item2) { return true; } } continue; } object obj = (value.GetType().Assembly.GetType("AzuExtendedPlayerInventory.API") ?? throw new TypeLoadException("AzuExtendedPlayerInventory.API")).GetMethod("GetSlots", BindingFlags.Static | BindingFlags.Public, null, Type.EmptyTypes, null)?.Invoke(null, null) ?? throw new MissingMethodException("GetSlots"); foreach (object item3 in ((obj.GetType().GetField("GetItemFuncs")?.GetValue(obj) ?? obj.GetType().GetProperty("GetItemFuncs")?.GetValue(obj)) as IEnumerable) ?? throw new MissingMemberException("GetItemFuncs")) { if (item3 != null && ((item3 as Delegate) ?? throw new InvalidOperationException("Unexpected slot callback type.")).DynamicInvoke(player) == item) { return true; } } } catch (Exception error) { Warn(text, error); return true; } } return false; } private static void Warn(string guid, Exception error) { if (warned.Add(guid)) { MeadworksLog log = Plugin.Log; if (log != null) { log.Warning("Optional trader integration unavailable for " + guid + ": " + error.GetType().Name + ". Barter keeps protected items and foreign panels safe."); } } } internal static void Shutdown() { plugins.Clear(); warned.Clear(); } } [HarmonyPatch(typeof(Player), "Load")] internal static class Player_Load_BarterRecovery { private static void Prefix(Player __instance) { TradeAuthority.BeforeCharacterLoad(__instance); } private static void Postfix(Player __instance) { TradeAuthority.CharacterLoaded(__instance); } } internal static class Purses { private static readonly int BalanceKey = StringExtensionMethods.GetStableHashCode("traderCoins"); private static readonly int DayKey = StringExtensionMethods.GetStableHashCode("traderCoinsReplenished"); private static readonly int LedgerKey = StringExtensionMethods.GetStableHashCode("meadworks.barter.ledger.v1"); internal static bool HasPurse(string trader) { if (string.IsNullOrEmpty(trader) || Plugin.Settings == null) { return false; } if (TraderNames.Vanilla.Any((string name) => TraderNames.Normalize(name) == trader)) { return true; } if (TraderNames.ListContains(Plugin.Settings.Get<string>(Lists.CustomTraders), trader)) { return true; } if (!TradeLists.Written.Traders.Contains(trader)) { return TraderPolicies.HasTrader(trader); } return true; } internal static PurseRules Rules(string trader) { PurseRules val = Plugin.Settings.Purse(); val.Enabled = TraderPolicies.Get(Purse.Enabled, trader, val.Enabled); val.Flexible = TraderPolicies.Get(Purse.FlexiblePricing, trader, val.Flexible); val.Minimum = TraderPolicies.Get(Purse.MinimumBalance, trader, val.Minimum); val.Maximum = TraderPolicies.Get(Purse.MaximumBalance, trader, val.Maximum); val.DailyIncrease = TraderPolicies.Get(Purse.DailyIncrease, trader, val.DailyIncrease); val.DailyDecrease = TraderPolicies.Get(Purse.DailyDecrease, trader, val.DailyDecrease); val.Discount = TraderPolicies.Get(Purse.Discount, trader, val.Discount); val.Markup = TraderPolicies.Get(Purse.Markup, trader, val.Markup); val.ReplenishDays = TraderPolicies.Get(Purse.ReplenishDays, trader, val.ReplenishDays); if (!HasPurse(trader)) { val.Enabled = false; } return val; } internal static bool Applies(Trader trader) { return Rules(TradeWorld.TraderName(trader)).Enabled; } internal static int Balance(Trader trader, ZDO authorityZdo = null) { int num = PurseRefill.Initial(Rules(TradeWorld.TraderName(trader))); if (authorityZdo != null) { return ReadLedger(authorityZdo, num).Balance; } ZNetView val = (((Object)(object)trader != (Object)null) ? ((Component)trader).GetComponent<ZNetView>() : null); if ((Object)(object)val == (Object)null || !val.IsValid()) { return num; } return ReadLedger(val.GetZDO(), num).Balance; } internal static float BuyFactor(Trader trader, ZDO authorityZdo = null) { PurseRules val = Rules(TradeWorld.TraderName(trader)); if (!val.Enabled) { return 1f; } return PriceCurve.BuyFactor(Balance(trader, authorityZdo), val); } internal static float SellFactor(Trader trader, ZDO authorityZdo = null) { PurseRules val = Rules(TradeWorld.TraderName(trader)); if (!val.Enabled) { return 1f; } return PriceCurve.SellFactor(Balance(trader, authorityZdo), val); } internal static bool CanPay(Trader trader, int amount) { if (amount > 0) { if (Applies(trader)) { return amount <= Balance(trader); } return true; } return false; } internal static void Change(Trader trader, long delta) { if (delta != 0L) { MeadworksLog log = Plugin.Log; if (log != null) { log.Warning("Ignored purse write outside the trade authority protocol."); } } } internal static PurseLedger ReadLedger(ZDO zdo, int initial) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) string text = AuthorityState.Read(zdo, "ledger", zdo.GetString(LedgerKey, "")); PurseLedger val = (PurseLedger)((text.Length != 0) ? ((object)PurseLedger.Decode(text)) : ((object)new PurseLedger(initial))); if (AuthorityState.IsAuthority) { if (text.Length == 0) { AuthorityState.Write(zdo, "untrusted-legacy-ledger", zdo.GetString(LedgerKey, "") + "\n" + zdo.GetInt(BalanceKey, initial).ToString(CultureInfo.InvariantCulture)); AuthorityState.Write(zdo, "ledger", val.Encode()); } if (val.QuarantineExpired(DateTimeOffset.UtcNow.ToUnixTimeSeconds(), 120)) { WriteLedger(zdo, val); } Mirror(zdo, val); } return val; } internal static void WriteLedger(ZDO zdo, PurseLedger ledger) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { throw new InvalidOperationException("Only the server writes purse state."); } AuthorityState.Write(zdo, "ledger", ledger.Encode()); Mirror(zdo, ledger); } private static void Mirror(ZDO zdo, PurseLedger ledger) { zdo.SetOwner(ZDOMan.GetSessionID()); string text = ledger.Encode(); if (zdo.GetString(LedgerKey, "") != text) { zdo.Set(LedgerKey, text); } if (zdo.GetInt(BalanceKey, -1) != ledger.Balance) { zdo.Set(BalanceKey, ledger.Balance, false); } } private static int RefillDay(ZDO zdo) { string text = AuthorityState.Read(zdo, "refill-day"); if (text.Length == 0) { return 0; } if (!int.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result < 0) { throw new InvalidDataException("Invalid authority refill day"); } return result; } internal static bool Refill() { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || !Object.op_Implicit((Object)(object)EnvMan.instance) || !Object.op_Implicit((Object)(object)ZNetScene.instance) || ZDOMan.instance == null || Plugin.Settings == null) { return false; } bool flag = false; int currentDay = EnvMan.instance.GetCurrentDay(); RefillTiming.SetDay(currentDay); Dictionary<int, (string, string)> dictionary = new Dictionary<int, (string, string)>(); int num = default(int); foreach (ZDO item2 in ZDOMan.instance.m_objectsByID.Values.Where((ZDO zdo) => zdo != null)) { RefillTiming.Scanned(); int prefab = item2.GetPrefab(); if (!dictionary.TryGetValue(prefab, out var value)) { GameObject prefab2 = ZNetScene.instance.GetPrefab(prefab); string obj = ((prefab2 != null) ? ((Object)prefab2).name : null); string text = TraderNames.Normalize(obj); value = (obj, (!string.IsNullOrEmpty(obj) && HasPurse(text)) ? text : ""); dictionary.Add(prefab, value); } if (value.Item2.Length == 0) { continue; } string item = value.Item2; PurseRules val = Rules(item); if (!val.Enabled) { continue; } RefillTiming.Trader(); PurseLedger val2 = ReadLedger(item2, PurseRefill.Initial(val)); if (val2.Pending) { continue; } int balance = val2.Balance; if (PurseRefill.TryReplenish(balance, RefillDay(item2), currentDay, val, ref num) && val2.Refill(num)) { AuthorityState.Write(item2, "refill-day", currentDay.ToString(CultureInfo.InvariantCulture)); WriteLedger(item2, val2); item2.Set(DayKey, currentDay, false); RefillTiming.Refilled(); flag |= TraderPolicies.Get(Purse.ReplenishMessage, item, Plugin.Settings.Get<bool>(Purse.ReplenishMessage)); MeadworksLog log = Plugin.Log; if (log != null) { log.Verbose($"{value.Item1} balance updated {balance} -> {num}"); } } } return flag; } } internal enum ExchangeResult { Completed, RolledBack, Unresolved, InventoryHeld } internal static class SafeTradeInventory { private sealed class PendingAdd { internal Inventory Inventory; internal ItemData Template; internal ItemData Item; internal Vector2i Position; internal int Amount; } internal readonly struct Take { internal ItemData Item { get; } internal int Amount { get; } internal Take(ItemData item, int amount) { Item = item; Amount = amount; } } private sealed class Delta { internal ItemData Item; internal int Amount; internal Vector2i Position; internal bool Added; internal bool Repaired; internal float BeforeDurability; internal float AfterDurability; } private static readonly Dictionary<Inventory, List<Delta>> unresolved = new Dictionary<Inventory, List<Delta>>(); private const int RecoveryLimit = 64; private static PendingAdd adding; internal static bool IsBlocked(Inventory inventory) { if (inventory != null) { if (!unresolved.ContainsKey(inventory)) { return unresolved.Count >= 64; } return true; } return false; } internal static void ObserveBeforeChanged(Inventory inventory) { PendingAdd pendingAdd = adding; if (pendingAdd != null && pendingAdd.Inventory == inventory && pendingAdd.Item == null) { ItemData itemAt = inventory.GetItemAt(pendingAdd.Position.x, pendingAdd.Position.y); if (itemAt != null && itemAt.m_shared == pendingAdd.Template.m_shared && itemAt.m_quality == pendingAdd.Template.m_quality && !((Object)(object)itemAt.m_dropPrefab != (Object)(object)pendingAdd.Template.m_dropPrefab)) { pendingAdd.Item = itemAt; pendingAdd.Amount = itemAt.m_stack; } } } internal static IReadOnlyList<Take> RecoveryItems(Inventory inventory) { if (!unresolved.TryGetValue(inventory, out List<Delta> value)) { return Array.Empty<Take>(); } return (from delta in value where !delta.Added && delta.Amount > 0 select new Take(delta.Item, delta.Amount)).ToArray(); } internal static List<Take> Currency(Inventory inventory, ItemDrop currency, int amount) { List<Take> list = new List<Take>(); if (inventory == null || (Object)(object)currency == (Object)null || amount < 0) { return list; } ItemData[] array = inventory.GetAllItems().ToArray(); foreach (ItemData val in array) { if (!((Object)(object)val.m_dropPrefab == (Object)null) && !(Utils.GetPrefabName(val.m_dropPrefab) != Utils.GetPrefabName(((Component)currency).gameObject)) && !(val.m_shared.m_name != currency.m_itemData.m_shared.m_name) && val.m_worldLevel >= Game.m_worldLevel && !val.m_equipped && !OptionalTradeIntegrations.ProtectedItem(val, Player.m_localPlayer)) { int num = Math.Min(amount, val.m_stack); if (num > 0) { list.Add(new Take(val, num)); } amount -= num; if (amount == 0) { break; } } } if (amount != 0) { return new List<Take>(); } return list; } internal static ItemData Template(ItemDrop prefab, int quality, bool cheated) { ItemData val = prefab.m_itemData.Clone(); val.m_dropPrefab = ((Component)prefab).gameObject; val.m_quality = ((quality > 0) ? quality : val.m_quality); val.m_durability = val.GetMaxDurability(); val.m_worldLevel = Game.m_worldLevel; val.m_cheated = cheated; val.m_equipped = false; val.m_stack = 1; return val; } internal static bool CanFitExchange(Inventory inventory, IReadOnlyList<Take> payment, ItemData output, int amount) { if (inventory == null || payment == null || output == null || amount < 0) { return false; } long num = inventory.GetEmptySlots(); HashSet<ItemData> hashSet = new HashSet<ItemData>(); foreach (Take item in payment) { if (item.Item == null || item.Amount <= 0 || !hashSet.Add(item.Item) || !inventory.ContainsItem(item.Item) || item.Item.m_stack < item.Amount || item.Item.m_equipped || OptionalTradeIntegrations.ProtectedItem(item.Item, Player.m_localPlayer)) { return false; } if (item.Amount == item.Item.m_stack) { num++; } } long num2 = ((long)amount + (long)Math.Max(1, output.m_shared.m_maxStackSize) - 1) / Math.Max(1, output.m_shared.m_maxStackSize); return num >= num2; } internal static ExchangeResult Exchange(Inventory inventory, IReadOnlyList<Take> payment, ItemData output, int amount) { return ExchangeMany(inventory, payment, (output == null) ? null : new Take[1] { new Take(output, amount) }); } internal static ExchangeResult Repair(Inventory inventory, IReadOnlyList<Take> payment, ItemData item) { return ExchangeMany(inventory, payment, Array.Empty<Take>(), item); } internal static ExchangeResult ExchangeMany(Inventory inventory, IReadOnlyList<Take> payment, IReadOnlyList<Take> outputs, ItemData repair = null) { //IL_03a4: Unknown result type (might be due to invalid IL or missing references) //IL_03ab: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_028e: Unknown result type (might be due to invalid IL or missing references) //IL_02ea: Unknown result type (might be due to invalid IL or missing references) //IL_02ec: Unknown result type (might be due to invalid IL or missing references) //IL_030d: Unknown result type (might be due to invalid IL or missing references) //IL_0314: Unknown result type (might be due to invalid IL or missing references) if (inventory == null || outputs == null || outputs.Any((Take o) => o.Item == null || o.Amount < 0) || payment == null) { return ExchangeResult.RolledBack; } if (IsBlocked(inventory)) { return ExchangeResult.Unresolved; } if (OptionalTradeIntegrations.InventoryHeld(inventory)) { return ExchangeResult.InventoryHeld; } if (repair != null && (!inventory.ContainsItem(repair) || repair.m_stack != 1 || payment.Any((Take t) => t.Item == repair))) { return ExchangeResult.RolledBack; } float num = repair?.m_durability ?? 0f; HashSet<ItemData> hashSet = new HashSet<ItemData>(); foreach (Take item2 in payment) { if (item2.Item == null || item2.Amount <= 0 || !hashSet.Add(item2.Item) || !inventory.ContainsItem(item2.Item) || item2.Item.m_stack < item2.Amount || item2.Item.m_equipped || OptionalTradeIntegrations.ProtectedItem(item2.Item, Player.m_localPlayer)) { return ExchangeResult.RolledBack; } } List<Delta> list = new List<Delta>(); bool flag = false; try { foreach (Take item3 in payment) { if (item3.Item.m_equipped || OptionalTradeIntegrations.ProtectedItem(item3.Item, Player.m_localPlayer)) { return Rollback(inventory, list); } int stack = item3.Item.m_stack; Delta delta = new Delta { Item = item3.Item, Position = item3.Item.m_gridPos }; try { inventory.RemoveItem(item3.Item, item3.Amount); } finally { delta.Amount = Math.Max(0, stack - (inventory.ContainsItem(item3.Item) ? item3.Item.m_stack : 0)); if (delta.Amount > 0) { list.Add(delta); } } if (delta.Amount != item3.Amount) { return Refused(inventory, list); } } foreach (Take output in outputs) { int num2 = output.Amount; while (num2 > 0) { ItemData item = output.Item; Vector2i val = inventory.FindEmptySlot(false); if (val.x < 0) { return Rollback(inventory, list); } int num3 = Math.Min(num2, Math.Max(1, item.m_shared.m_maxStackSize)); ItemData val2 = item.Clone(); val2.m_stack = num3; int num4 = 0; PendingAdd pendingAdd = adding; PendingAdd pendingAdd2 = (adding = new PendingAdd { Inventory = inventory, Position = val, Template = val2 }); bool flag2 = false; bool flag3 = false; try { flag2 = inventory.AddItem(val2, num3, val.x, val.y, false); flag3 = true; } finally { adding = pendingAdd; if (pendingAdd2.Item != null) { int num5 = Math.Min(num3, pendingAdd2.Amount); num4 = (inventory.ContainsItem(pendingAdd2.Item) ? Math.Min(num5, pendingAdd2.Item.m_stack) : 0); list.Add(new Delta { Item = pendingAdd2.Item, Amount = num5, Added = true }); } else if (flag2 || !flag3 || inventory.GetItemAt(val.x, val.y) != null) { flag = true; } } if (flag) { return KeepUnresolved(inventory, list); } if (num4 != num3) { return Refused(inventory, list); } num2 -= num3; } } if (repair != null) { if (!inventory.ContainsItem(repair) || repair.m_durability != num) { return Rollback(inventory, list); } Delta delta2 = new Delta { Item = repair, Repaired = true, BeforeDurability = repair.m_durability, AfterDurability = repair.GetMaxDurability() }; list.Add(delta2); repair.m_durability = delta2.AfterDurability; inventory.Changed(false, false); if (!inventory.ContainsItem(repair) || repair.m_durability != delta2.AfterDurability) { return KeepUnresolved(inventory, list); } } return ExchangeResult.Completed; } catch (Exception) { return flag ? KeepUnresolved(inventory, list) : Rollback(inventory, list); } } private static ExchangeResult Rollback(Inventory inventory, List<Delta> deltas) { //IL_01df: 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_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_0214: 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) try { foreach (Delta item3 in deltas.Where((Delta d) => d.Repaired).Reverse()) { if (!inventory.ContainsItem(item3.Item) || item3.Item.m_durability != item3.AfterDurability) { return KeepUnresolved(inventory, deltas); } item3.Item.m_durability = item3.BeforeDurability; item3.Repaired = false; } foreach (Delta item4 in deltas.Where((Delta d) => d.Added).Reverse()) { if (!inventory.ContainsItem(item4.Item) || item4.Item.m_stack < item4.Amount) { return KeepUnresolved(inventory, deltas); } ItemData item = item4.Item; item.m_stack -= item4.Amount; if (item4.Item.m_stack == 0) { inventory.m_inventory.Remove(item4.Item); } item4.Amount = 0; } foreach (Delta item5 in deltas.Where((Delta d) => !d.Added).Reverse()) { if (inventory.ContainsItem(item5.Item)) { ItemData item2 = item5.Item; item2.m_stack += item5.Amount; } else { Vector2i val = ((inventory.GetItemAt(item5.Position.x, item5.Position.y) == null) ? item5.Position : inventory.FindEmptySlot(false)); if (val.x < 0) { return KeepUnresolved(inventory, deltas); } item5.Item.m_stack = item5.Amount; item5.Item.m_gridPos = val; inventory.m_inventory.Add(item5.Item); } item5.Amount = 0; } inventory.Changed(false, false); return ExchangeResult.RolledBack; } catch (Exception) { return KeepUnresolved(inventory, deltas); } } private static ExchangeResult Refused(Inventory inventory, List<Delta> deltas) { bool num = OptionalTradeIntegrations.InventoryHeld(inventory); ExchangeResult exchangeResult = Rollback(inventory, deltas); if (!num || exchangeResult != ExchangeResult.RolledBack) { return exchangeResult; } return ExchangeResult.InventoryHeld; } private static ExchangeResult KeepUnresolved(Inventory inventory, List<Delta> deltas) { unresolved[inventory] = deltas; return ExchangeResult.Unresolved; } } internal static class SellService { private static readonly List<SellOffer> offers = new List<SellOffer>(); private static SellOffer selected; private static bool busy; private static int pendingGeneration; internal static IReadOnlyList<SellOffer> Offers => offers; internal static SellOffer Selected => selected; internal static string Filter { get; set; } = string.Empty; internal static long Revision { get; private set; } internal static void ResetPending() { busy = false; pendingGeneration++; } internal static void Clear() { offers.Clear(); selected = null; Filter = string.Empty; Revision++; } internal static void Refresh(StoreGui store) { //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Expected O, but got Unknown //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Expected O, but got Unknown SellOffer previous = selected; offers.Clear(); selected = null; try { Trader val = (((Object)(object)store != (Object)null) ? store.m_trader : null); Player localPlayer = Player.m_localPlayer; if ((Object)(object)val == (Object)null || (Object)(object)localPlayer == (Object)null || !Plugin.Active || !TradeLists.Ready || Plugin.Settings == null) { return; } string text = TradeWorld.TraderName(val); TradeBook effective = TradeLists.Effective; bool flag = Plugin.Settings.GetFor<bool>(Lists.CommonValuables, text); SellPriceTable val2 = SellPriceTable.Build((IEnumerable<TradeEntry>)effective.Sell(text, flag), (ITradeContext)(object)TradeWorld.Instance); SellSettings val3 = new SellSettings(Purses.SellFactor(val), Plugin.Settings.GetFor<float>(Lists.QualityPriceMultiplier, text)); string traderCurrency = Currencies.NameFor(text); ItemDrop own = (ItemDrop)(((Object)(object)store.m_coinPrefab != (Object)null) ? ((object)store.m_coinPrefab) : ((object)Currencies.Vanilla())); bool flag2 = Plugin.Settings.Get<bool>(Interface.HideEquipped); string text2 = Filter ?? string.Empty; List<HeldItem> list = new List<HeldItem>(); foreach (ItemData item in ((Humanoid)localPlayer).GetInventory().GetAllItemsSortedByName()) { string text3 = TradeWorld.PrefabName(item); if (text3.Length != 0 && item.m_worldLevel >= Game.m_worldLevel && !item.m_equipped && !OptionalTradeIntegrations.ProtectedItem(item, localPlayer) && (!flag2 || (!item.m_equipped && (item.m_gridPos.y != 0 || !item.IsEquipable()))) && (text2.Length <= 0 || Localization.instance.Localize(item.m_shared.m_name).IndexOf(text2, StringComparison.OrdinalIgnoreCase) >= 0)) { list.Add(new HeldItem((object)item, text3, item.m_stack, item.m_quality, item.m_shared.m_maxStackSize)); } } offers.AddRange(SellPlanner.Plan((IEnumerable<HeldItem>)list, val2, val3, (Func<string, string>)delegate(string raw) { ItemDrop val4 = Currencies.Resolve(CurrencyRules.Effective(raw, traderCurrency), own); return (!((Object)(object)val4 != (Object)null)) ? null : Utils.GetPrefabName(((Component)val4).gameObject); })); int num = ((previous == null) ? (-1) : offers.FindIndex((SellOffer offer) => Same(offer, previous))); selected = ((num >= 0) ? offers[num] : ((offers.Count > 0) ? offers[0] : null)); } finally { Revision++; } } internal static void Select(SellOffer offer) { if (selected != offer) { selected = offer; Revision++; } } internal static int ItemsPerSale(SellOffer offer) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 if (offer != null) { if ((int)offer.Kind != 2) { if ((int)offer.Kind != 1) { return 1; } return offer.Stack; } return offer.Amount; } return 0; } internal static bool CanSell(StoreGui store, out string reason) { reason = string.Empty; SellOffer val = selected; if (busy || val == null || (Object)(object)store == (Object)null || (Object)(object)store.m_trader == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null) { return false; } if (TradeAuthority.RecoveryBlocked || SafeTradeInventory.IsBlocked(((Humanoid)Player.m_localPlayer).GetInventory())) { reason = TradeAuthority.UnavailableReason; return false; } if (OptionalTradeIntegrations.InventoryHeld(((Humanoid)Player.m_localPlayer).GetInventory())) { reason = BarterText.Text("trade_inventory_held"); return false; } if (SameCurrencyAsTrader(val, store) && !Purses.CanPay(store.m_trader, val.Price)) { reason = BarterText.Format("sell_trader_poor", store.m_trader.GetHoverName()); return false; } if (!CanReceivePayment(((Humanoid)Player.m_localPlayer).GetInventory(), val.Sources.Cast<ItemData>().ToList(), ItemsPerSale(val), val.Price, TradeWorld.Item(val.Currency) ?? Currencies.Vanilla())) { reason = BarterText.Text("sell_no_room"); return false; } return true; } internal static void Sell(StoreGui store) { if (busy) { return; } if (!CanSell(store, out string reason)) { if (reason.Length > 0) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, reason, 0, (Sprite)null, false); } } } else { SellOffer val = selected; BeginSale(store, val.Sources.Cast<ItemData>().ToList(), ItemsPerSale(val), val.Price, TradeWorld.Item(val.Currency) ?? Currencies.Vanilla(), val.Prefab, val.Quality, val.Stack, vanilla: false); } } internal static bool CanSellValuables(StoreGui store, out string reason) { reason = string.Empty; Player localPlayer = Player.m_localPlayer; if (busy || (Object)(object)store == (Object)null || (Object)(object)store.m_trader == (Object)null || (Object)(object)localPlayer == (Object)null) { return false; } Inventory inventory = ((Humanoid)localPlayer).GetInventory(); if (TradeAuthority.RecoveryBlocked || SafeTradeInventory.IsBlocked(inventory)) { reason = TradeAuthority.UnavailableReason; return false; } if (OptionalTradeIntegrations.InventoryHeld(inventory)) { reason = BarterText.Text("trade_inventory_held"); return false; } ItemData sellableItem = store.GetSellableItem(); ItemDrop val = store.m_coinPrefab ?? Currencies.Vanilla(); if (sellableItem == null || (Object)(object)val == (Object)null || sellableItem.m_stack <= 0 || sellableItem.m_shared.m_value <= 0) { return false; } long num = (long)sellableItem.m_stack * (long)sellableItem.m_shared.m_value; if (num > int.MaxValue) { reason = TradeAuthority.UnavailableReason; return false; } if (!Purses.CanPay(store.m_trader, (int)num)) { reason = BarterText.Format("sell_trader_poor", store.m_trader.GetHoverName()); return false; } if (!CanReceivePayment(inventory, (IReadOnlyList<ItemData>)(object)new ItemData[1] { sellableItem }, sellableItem.m_stack, (int)num, val)) { reason = BarterText.Text("sell_no_room"); return false; } return true; } internal static void SellValuables(StoreGui store) { if (busy || (Object)(object)store == (Object)null || (Object)(object)store.m_trader == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null) { return; } ItemData sellableItem = store.GetSellableItem(); ItemDrop val = store.m_coinPrefab ?? Currencies.Vanilla(); if (sellableItem != null && !((Object)(object)val == (Object)null) && sellableItem.m_stack > 0 && sellableItem.m_shared.m_value > 0) { long num = (long)sellableItem.m_stack * (long)sellableItem.m_shared.m_value; if (num <= int.MaxValue) { BeginSale(store, new List<ItemData> { sellableItem }, sellableItem.m_stack, (int)num, val, TradeWorld.PrefabName(sellableItem), sellableItem.m_quality, 1, vanilla: true); } } } private static void BeginSale(StoreGui store, List<ItemData> items, int needed, int price, ItemDrop currency, string prefab, int quality, int lotSize, bool vanilla) { Player player = Player.m_localPlayer; Inventory inventory = ((Humanoid)player).GetInventory(); if (OptionalTradeIntegrations.InventoryHeld(inventory)) { ((Character)player).Message((MessageType)2, BarterText.Text("trade_inventory_held"), 0, (Sprite)null, false); return; } if (TradeAuthority.RecoveryBlocked || SafeTradeInventory.IsBlocked(inventory)) { ((Character)player).Message((MessageType)2, TradeAuthority.UnavailableReason, 0, (Sprite)null, false); return; } Trader trader = store.m_trader; if ((Object)(object)currency == (Object)null || items.Count == 0 || needed <= 0 || price < 0) { return; } if (!CanReceivePayment(inventory, items, needed, price, currency)) { ((Character)player).Message((MessageType)2, BarterText.Text("sell_no_room"), 0, (Sprite)null, false); return; } TradeRequest request = new TradeRequest { Buy = false, Vanilla = vanilla, Prefab = prefab, Amount = needed, Quality = quality, Price = price, Currency = Utils.GetPrefabName(((Component)currency).gameObject), LotSize = lotSize }; bool num = Plugin.Settings.GetFor<bool>(Services.BuybackEnabled, TradeWorld.TraderName(trader)); if (num && !Plugin.Session.IsAuthority) { ((Character)player).Message((MessageType)2, BarterText.Text("service_authority_only"), 0, (Sprite)null, false); } if (num && Plugin.Session.IsAuthority) { List<SafeTradeInventory.Take> list = new List<SafeTradeInventory.Take>(); int num2 = needed; foreach (ItemData item in items) { int num3 = Math.Min(num2, item.m_stack); if (num3 > 0) { list.Add(new SafeTradeInventory.Take(item, num3)); } num2 -= num3; } if (num2 != 0) { return; } try { request.Snapshot = TradeItemSnapshot.Encode(list); } catch (Exception ex) { MeadworksLog log = Plugin.Log; if (log != null) { log.Warning("Sale snapshot refused: " + ex.GetType().Name); } return; } } if (!TradeCodec.FitsReservation(request, Plugin.Session.SessionId)) { ((Character)player).Message((MessageType)2, BarterText.Text("sell_buyback_too_large"), 0, (Sprite)null, false); return; } int[] counts = items.Select((ItemData item) => item.m_stack).ToArray(); busy = true; int attempt = ++pendingGeneration; if (!TradeAuthority.TryBegin(store, request, delegate(TradePermit permit) { //IL_02a0: Unknown result type (might be due to invalid IL or missing references) //IL_02a5: Unknown result type (might be due to invalid IL or missing references) //IL_02b3: Unknown result type (might be due to invalid IL or missing references) //IL_02b9: Unknown result type (might be due to invalid IL or missing references) try { if (attempt != pendingGeneration) { permit?.Finish(success: false); } else if (permit == null) { ((Character)player).Message((MessageType)2, TradeAuthority.UnavailableReason, 0, (Sprite)null, false); } else if (!permit.IsCurrent || (Object)(object)Player.m_localPlayer != (Object)(object)player || (Object)(object)store == (Object)null || (Object)(object)store.m_trader != (Object)(object)trader || ((Humanoid)player).GetInventory() != inventory || !StoreGui.IsVisible() || items.Where((ItemData item, int i) => !inventory.ContainsItem(item) || item.m_stack != counts[i] || item.m_equipped || OptionalTradeIntegrations.ProtectedItem(item, player) || item.m_quality != quality || TradeWorld.PrefabName(item) != prefab).Any()) { permit.Finish(success: false); } else { List<SafeTradeInventory.Take> list2 = new List<SafeTradeInventory.Take>(); int num4 = needed; foreach (ItemData item2 in items) { int num5 = Math.Min(num4, item2.m_stack); if (num5 <= 0) { break; } list2.Add(new SafeTradeInventory.Take(item2, num5)); num4 -= num5; } if (num4 != 0) { permit.Finish(success: false); } else if (request.Snapshot.Length > 0 && TradeItemSnapshot.Encode(list2) != request.Snapshot) { permit.Finish(success: false); } else { ItemData output = SafeTradeInventory.Template(currency, currency.m_itemData.m_quality, list2.Any((SafeTradeInventory.Take take) => take.Item.m_cheated)); ExchangeResult exchangeResult = SafeTradeInventory.Exchange(inventory, list2, output, price); if (exchangeResult == ExchangeResult.Unresolved) { MeadworksLog log2 = Plugin.Log; if (log2 != null) { log2.Warning("Trade inventory rollback could not be proven; the server reservation remains locked for recovery."); } } else { permit.Finish(exchangeResult == ExchangeResult.Completed); if (exchangeResult != ExchangeResult.Completed) { ((Character)player).Message((MessageType)2, BarterText.Text((exchangeResult == ExchangeResult.InventoryHeld) ? "trade_inventory_held" : "sell_no_room"), 0, (Sprite)null, false); } else { string text = ((needed > 1) ? (needed + "x ") : string.Empty) + Localization.instance.Localize(items[0].m_shared.m_name); trader.OnSold(); store.m_sellEffects.Create(((Component)store).transform.position, Quaternion.identity, (Transform)null, 1f, -1, default(ZDOID)); ((Character)player).Message((MessageType)1, BarterText.Format("sell_sold", text, price, Currencies.Name(currency)), 0, items[0].m_shared.m_icons[0], false); Gogan.LogEvent("Game", "SoldItem", text, 0L); } } } } } finally { if (attempt == pendingGeneration) { busy = false; } if ((Object)(object)store != (Object)null && (Object)(object)store.m_trader == (Object)(object)trader && StoreGui.IsVisible()) { store.FillList(); } } })) { busy = false; ((Character)player).Message((MessageType)2, TradeAuthority.UnavailableReason, 0, (Sprite)null, false); } } private static bool CanReceivePayment(Inventory inventory, IReadOnlyList<ItemData> items, int needed, int price, ItemDrop currency) { if ((Object)(object)currency == (Object)null || needed <= 0) { return false; } List<SafeTradeInventory.Take> list = new List<SafeTradeInventory.Take>(); foreach (ItemData item in items) { if (item == null) { return false; } int num = Math.Min(needed, item.m_stack); if (num > 0) { list.Add(new SafeTradeInventory.Take(item, num)); } needed -= num; if (needed == 0) { break; } } if (needed == 0) { return SafeTradeInventory.CanFitExchange(inventory, list, SafeTradeInventory.Template(currency, currency.m_itemData.m_quality, cheated: false), price); } return false; } private static bool SameCurrencyAsTrader(SellOffer offer, StoreGui store) { ItemDrop val = TradeWorld.Item(offer.Currency); ItemDrop val2 = (ItemDrop)(((Object)(object)store.m_coinPrefab != (Object)null) ? ((object)store.m_coinPrefab) : ((object)Currencies.Vanilla())); if (!((Object)(object)val == (Object)null) && !((Object)(object)val2 == (Object)null)) { return val.m_itemData.m_shared.m_name == val2.m_itemData.m_shared.m_name; } return true; } private static bool Same(SellOffer a, SellOffer b) { //IL_0001: 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_0051: Unknown result type (might be due to invalid IL or missing references) if (a.Kind == b.Kind && a.Prefab == b.Prefab && a.Stack == b.Stack && a.Quality == b.Quality && a.Currency == b.Currency) { if ((int)a.Kind == 0) { return a.Sources[0] == b.Sources[0]; } return true; } return false; } } [HarmonyPatch(typeof(EnvMan), "UpdateTriggers")] internal static class EnvMan_UpdateTriggers_Refill { private static bool IsMorning(float oldDayFraction, float newDayFraction) { if (oldDayFraction > 0.2f && oldDayFraction < 0.25f && newDayFraction >= 0.25f) { return newDayFraction < 0.3f; } return false; } private static void Postfix(float oldDayFraction, float newDayFraction) { //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: 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_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Unknown result type (might be due to invalid IL or missing references) //IL_025c: Unknown result type (might be due to invalid IL or missing references) //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_0271: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_02ae: Unknown result type (might be due to invalid IL or missing references) //IL_02b0: Unknown result type (might be due to invalid IL or missing references) //IL_02c3: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02d8: Unknown result type (might be due to invalid IL or missing references) //IL_02da: Unknown result type (might be due to invalid IL or missing references) //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Unknown result type (might be due to invalid IL or missing references) //IL_0302: Unknown result type (might be due to invalid IL or missing references) //IL_0304: Unknown result type (might be due to invalid IL or missing references) //IL_0317: Unknown result type (might be due to invalid IL or missing references) //IL_0319: Unknown result type (might be due to invalid IL or missing references) //IL_032c: Unknown result type (might be due to invalid IL or missing references) //IL_032e: Unknown result type (might be due to invalid IL or missing references) //IL_0341: Unknown result type (might be due to invalid IL or missing references) //IL_0343: Unknown result type (might be due to invalid IL or missing references) //IL_0356: Unknown result type (might be due to invalid IL or missing references) //IL_0358: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.Active || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || !IsMorning(oldDayFraction, newDayFraction)) { return; } bool flag = Plugin.Log != null && Plugin.Log.IsEnabled((LogSeverity)4); int num = (flag ? Time.frameCount : 0); bool flag2 = false; if (flag) { RefillTiming.Begin(); } try { if (Purses.Refill() && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ShowMessage", new object[2] { 1, BarterText.Token("purse_refilled") }); } flag2 = true; } catch (Exception ex) { MeadworksLog log = Plugin.Log; if (log != null) { log.Warning("Purse refill failed: " + ex.GetType().Name + ": " + ex.Message); } } finally { if (flag) { Result val = RefillTiming.End(); try { Plugin.Log.Debug(FormattableString.Invariant($"stall-timing event=morning-refill frame={num} thread={Thread.CurrentThread.ManagedThreadId} old_fraction={oldDayFraction:R} new_fraction={newDayFraction:R} day={val.Day} backend={ZNet.World?.m_fileSource} completed={flag2} world_zdos={ZDOMan.instance?.m_objectsByID.Count ?? 0} scanned={val.Scanned} traders={val.Traders} refilled={val.Refilled} frequency={Stopwatch.Frequency} total_ticks={val.TotalTicks} scan_and_rules_ticks={val.TotalTicks - val.AuthorityRead.Ticks - val.AuthorityWrite.Ticks} auth_read_count={val.AuthorityRead.Count} auth_read_ticks={val.AuthorityRead.Ticks} auth_write_count={val.AuthorityWrite.Count} auth_write_ticks={val.AuthorityWrite.Ticks} local_read_count={val.LocalRead.Count} local_read_ticks={val.LocalRead.Ticks} local_write_count={val.LocalWrite.Count} local_write_ticks={val.LocalWrite.Ticks} cloud_read_count={val.CloudRead.Count} cloud_read_ticks={val.CloudRead.Ticks} cloud_write_count={val.CloudWrite.Count} cloud_write_ticks={val.CloudWrite.Ticks} promote_count={val.Promote.Count} promote_ticks={val.Promote.Ticks} flush_count={val.Flush.Count} flush_ticks={val.Flush.Ticks} cloud_finish_count={val.CloudFinish.Count} cloud_finish_ticks={val.CloudFinish.Ticks}")); } catch { } } } } } [HarmonyPatch(typeof(Trader), "GetAvailableItems")] internal static class Trader_GetAvailableItems_Lists { [HarmonyFinalizer] [HarmonyPriority(0)] private static Exception Finalizer(Exception __exception, Trader __instance, ref List<TradeItem> __result) { if (__exception != null || !Plugin.Active) { return __exception; } try { TradeOffers.Apply(__instance, ref __result); } catch (Exception ex) { MeadworksLog log = Plugin.Log; if (log != null) { log.Warning("Trader list failed for " + (((Object)(object)__instance != (Object)null) ? ((Object)__instance).name : "?") + ": " + ex.GetType().Name + ": " + ex.Message); } } return null; } } [HarmonyPatch(typeof(StoreGui), "Show")] internal static class StoreGui_Show_Barter { private static Trader before; private static void Prefix(StoreGui __instance, Trader trader) { before = (StoreGui.IsVisible() ? __instance.m_trader : null); if (Plugin.Active) { Currencies.Apply(__instance, trader); } } private static void Postfix(StoreGui __instance, Trader trader) { StoreUi.Shown(__instance); if (Plugin.Active) { StoreUi.Opened(__instance, before != trader); } } } [HarmonyPatch(typeof(StoreGui), "Hide")] internal static class StoreGui_Hide_Barter { private static void Postfix(StoreGui __instance) { Currencies.Restore(__instance); StoreUi.Closed(__instance); } } [HarmonyPatch(typeof(StoreGui), "Update")] internal static class StoreGui_Update_Barter { private static bool Prefix(StoreGui __instance) { if (Plugin.Active) { return !StoreUi.BeforeNativeUpdate(__instance); } return true; } private static void Postfix(StoreGui __instance) { StoreUi.Tick(__instance); } } [HarmonyPatch(typeof(StoreGui), "FillList")] internal static class StoreGui_FillList_Barter { private static void Postfix(StoreGui __instance) { //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.Active || (Object)(object)__instance.m_trader == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null) { return; } List<TradeItem> availableItems = __instance.m_trader.GetAvailableItems(); Inventory inventory = ((Humanoid)Player.m_localPlayer).GetInventory(); for (int i = 0; i < availableItems.Count && i < __instance.m_itemList.Count; i++) { GameObject val = __instance.m_itemList[i]; OfferInfo offerInfo = TradeOffers.InfoOf(availableItems[i]); ItemDrop currency = TradeOffers.CurrencyOf(offerInfo, __instance); Currencies.SetIcons(val, currency); if (offerInfo == null || !offerInfo.FromList) { continue; } bool flag = availableItems[i].m_price <= Currencies.Count(inventory, currency); Transform obj = val.transform.Find("icon"); Image val2 = ((obj != null) ? ((Component)obj).GetComponent<Image>() : null); if ((Object)(object)val2 != (Object)null) { ((Graphic)val2).color = (Color)(flag ? Color.white : new Color(1f, 0f, 1f, 0f)); } Transform obj2 = val.transform.Find("name"); TMP_Text val3 = ((obj2 != null) ? ((Component)obj2).GetComponent<TMP_Text>() : null); if ((Object)(object)val3 != (Object)null) { ((Graphic)val3).color = (flag ? Color.white : Color.grey); if (offerInfo.Quality > 1) { val3.text = val3.text + " <color=#add8e6ff>(" + offerInfo.Quality + ")</color>"; } } Transform obj3 = Utils.FindChild(val.transform, "price", (IterativeSearchType)0); TMP_Text val4 = ((obj3 != null) ? ((Component)obj3).GetComponent<TMP_Text>() : null); if ((Object)(object)val4 != (Object)null) { ((Graphic)val4).color = (flag ? Color.white : Color.grey); } } SellService.Refresh(__instance); } } [HarmonyPatch(typeof(StoreGui), "CanAfford")] internal static class StoreGui_CanAfford_Barter { private static bool Prefix(StoreGui __instance, TradeItem item, ref bool __result) { OfferInfo offerInfo = (Plugin.Active ? TradeOffers.InfoOf(item) : null); if (offerInfo == null || !offerInfo.FromList || (Object)(object)Player.m_localPlayer == (Object)null) { return true; } __result = item.m_price <= Currencies.Count(((Humanoid)Player.m_localPlayer).GetInventory(), TradeOffers.CurrencyOf(offerInfo, __instance)); return false; } } [HarmonyPatch(typeof(StoreGui), "BuySelectedItem")] internal static class StoreGui_BuySelectedItem_Barter { [HarmonyPrefix] private static void NotifyHeld(StoreGui __instance) { Player localPlayer = Player.m_localPlayer; if (Plugin.Active && __instance.m_selectedItem != null && (Object)(object)localPlayer != (Object)null && OptionalTradeIntegrations.InventoryHeld(((Humanoid)localPlayer).GetInventory())) { ((Character)localPlayer).Message((MessageType)2, BarterText.Text("trade_inventory_held"), 0, (Sprite)null, false); } } private static bool Prefix(StoreGui __instance) { TradeItem selectedItem = __instance.m_selectedItem; if (Plugin.AwaitingRules) { return false; } if (!Plugin.Active) { return true; } if (selectedItem == null) { return false; } OfferInfo info = TradeOffers.InfoOf(selectedItem); if (__instance.CanAfford(selectedItem)) { BuyService.Buy(__instance, selectedItem, info); } return false; } } [HarmonyPatch(typeof(StoreGui), "GetSelectedItemIndex")] internal static class StoreGui_GetSelectedItemIndex_Barter { private static void Postfix(StoreGui __instance, ref int __result) { TradeItem selectedItem = __instance.m_selectedItem; if (!Plugin.Active || selectedItem == null || (Object)(object)__instance.m_trader == (Object)null) { return; } List<TradeItem> availableItems = __instance.m_trader.GetAvailableItems(); if (__result >= 0 && __result < availableItems.Count && availableItems[__result] == selectedItem) { return; } object obj = TradeOffers.OriginOf(selectedItem); for (int i = 0; i < availableItems.Count; i++) { if (TradeOffers.OriginOf(availableItems[i]) == obj) { __result = i; break; } } } } [HarmonyPatch(typeof(StoreGui), "UpdateSellButton")] internal static class StoreGui_UpdateSellButton_Barter { internal static bool Takeover { get { if (Plugin.Active && TradeLists.Ready) { return StoreUi.SellPanelAllowed; } return false; } } private static bool Prefix(StoreGui __instance) { string reason; if (!Takeover) { if (!Plugin.Active) { return true; } ((Selectable)__instance.m_sellButton).interactable = SellService.CanSellValuables(__instance, out reason); return false; } ((Selectable)__instance.m_sellButton).interactable = SellService.CanSell(__instance, out reason); return false; } } [HarmonyPatch(typeof(StoreGui), "SellItem")] internal static class StoreGui_SellItem_Barter { private static bool Prefix(StoreGui __instance) { if (Plugin.AwaitingRules) { return false; } if (!Plugin.Active) { return true; } if (StoreGui_UpdateSellButton_Barter.Takeover) { SellService.Sell(__instance); } else { SellService.SellValuables(__instance); } return false; } } [HarmonyPatch(typeof(Inventory), "Changed")] internal static class Inventory_Changed_Barter { [HarmonyPriority(800)] private static void Prefix(Inventory __instance) { SafeTradeInventory.ObserveBeforeChanged(__instance); } private static void Postfix(Inventory __instance) { if (__instance != null && !__instance.m_temoraryInventory && (Object)(object)Player.m_localPlayer != (Object)null && __instance == ((Humanoid)Player.m_localPlayer).GetInventory()) { StoreUi.InventoryChanged(); } } } internal sealed class TradePermit { private readonly TradeRequest request; private readonly long generation; private bool finished; internal bool IsCurrent { get { if (!finished && Plugin.Active && Plugin.IsReady && Plugin.Session.AuthorityReady) { return Plugin.Session.Generation == generation; } return false; } } internal TradePermit(TradeRequest request, long generation) { this.request = request; this.generation = generation; } internal void Finish(bool success) { if (!finished) { finished = true; TradeAuthority.Finish(request, success); } } } internal static class TradeAuthority { private sealed class Recovery { internal TradeRequest Request; internal TradeRecovery State; internal JournalEntry Journal; internal bool Reopened; internal bool CharacterSaved; } private struct ReceiptMarker { internal string Key; internal string Epoch; internal long World; internal long Sequence; internal int Day; internal bool JournalOpen; } private const string ReserveOperation = "meadworks.barter.reserve"; private const string FinishOperation = "meadworks.barter.finish"; private static readonly List<Action> observations = new List<Action>(); private static readonly List<IDisposable> registrations = new List<IDisposable>(); private static readonly Dictionary<string, bool> localEvidence = new Dictionary<string, bool>(StringComparer.Ordinal); private static Player loadedPlayer; private static readonly HashSet<string> loadedMarkers = new HashSet<string>(StringComparer.Ordinal); internal const int MaxRetiredReceiptMarkers = 64; private const string MarkerPrefix = "meadworks.barter.receipt."; private static long nextMarkerSequence = 1L; private static string prunedSession = ""; private static bool pruneSaveFailed; private static readonly Dictionary<string, Recovery> outcomes = new Dictionary<string, Recovery>(StringComparer.Ordinal); private static readonly TradeJournal journal = new TradeJournal(Path.Combine(Paths.ConfigPath, "Barter.Recovery")); private static bool journalLoaded; private static bool journalFailed; private static readonly Stopwatch clock = Stopwatch.StartNew(); private static bool recovering; internal static bool RecoveryBlocked => RecoveryStatus.Length > 0; internal static string UnavailableReason { get { string recoveryStatus = RecoveryStatus; if (recoveryStatus.Length <= 0) { return BarterText.Text("trade_unavailable"); } return recoveryStatus; } } internal static string RecoveryStatus { get { if ((Object)(object)Player.m_localPlayer == (Object)null || !Plugin.IsReady) { return ""; } string text = Path.Combine(Paths.ConfigPath, "Barter.Recovery"); if (journalFailed) { return BarterText.Format("trade_recovery_unreadable", text); } string text2 = Player.m_localPlayer.GetPlayerID().ToString(CultureInfo.InvariantCulture); foreach (Recovery value in outcomes.Values) { if (value.Journal.World == Plugin.Session.WorldId && value.Journal.Actor == text2 && !value.State.Outcome.HasValue) { return BarterText.Format("trade_recovery_pending", value.Request.Token, text); } } return ""; } } internal static bool RecoveryInFlight => recovering; private static string Marker(string token) { return "meadworks.barter.receipt." + Plugin.Session.WorldId.ToString(CultureInfo.InvariantCulture) + ":" + token; } private static int CurrentDay() { if (!((Object)(object)EnvMan.instance == (Object)null)) { return EnvMan.instance.GetCurrentDay(); } return -1; } private static bool CurrentSession(out string session) { session = ""; if (!Plugin.IsReady || !Plugin.Session.AuthorityReady) { return false; } session = Plugin.Session.SessionId ?? ""; return session.Length > 0; } private static string FormatMarker(Player player, TradeRequest request) { long num = nextMarkerSequence; if ((Object)(object)player != (Object)null) { foreach (KeyValuePair<string, string> customDatum in player.m_customData) { if (TryParseMarkerKey(customDatum.Key, out long _, out string token) && TryParseMarkerValue(customDatum.Value, out token, out long sequence, out int _, out string _) && sequence >= num) { num = sequence + 1; } } } nextMarkerSequence = num + 1; string session; string text = (CurrentSession(out session) ? session : ""); return "2:" + TradeCodec.Fingerprint(request) + "|" + num.ToString(CultureInfo.InvariantCulture) + "|" + CurrentDay().ToString(CultureInfo.InvariantCulture) + "|" + text; } private static bool TryParseMarkerKey(string key, out long world, out string token) { world = 0L; token = ""; if (key == null || !key.StartsWith("meadworks.barter.receipt.", StringComparison.Ordinal)) { return false; } string text = key.Substring("meadworks.barter.receipt.".Length); int num = text.IndexOf(':'); if (num <= 0 || !long.TryParse(text.Substring(0, num), NumberStyles.Integer, CultureInfo.InvariantCulture, out world)) {
plugins/Barter/YamlDotNet.dll
Decompiled 10 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.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using System.Threading; using Microsoft.CodeAnalysis; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Core.Tokens; using YamlDotNet.Helpers; using YamlDotNet.Serialization; using YamlDotNet.Serialization.BufferedDeserialization; using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators; using YamlDotNet.Serialization.Callbacks; using YamlDotNet.Serialization.Converters; using YamlDotNet.Serialization.EventEmitters; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; using YamlDotNet.Serialization.NodeTypeResolvers; using YamlDotNet.Serialization.ObjectFactories; using YamlDotNet.Serialization.ObjectGraphTraversalStrategies; using YamlDotNet.Serialization.ObjectGraphVisitors; using YamlDotNet.Serialization.Schemas; using YamlDotNet.Serialization.TypeInspectors; using YamlDotNet.Serialization.TypeResolvers; using YamlDotNet.Serialization.Utilities; using YamlDotNet.Serialization.ValueDeserializers; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: ComVisible(false)] [assembly: AssemblyFileVersion("13.7.1.0")] [assembly: AssemblyInformationalVersion("13.7.1")] [assembly: AssemblyTitle("YamlDotNet")] [assembly: AssemblyDescription("The YamlDotNet library.")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("YamlDotNet")] [assembly: AssemblyCopyright("Copyright (c) Antoine Aubry and contributors 2008 - 2019")] [assembly: AssemblyTrademark("")] [assembly: CLSCompliant(true)] [assembly: InternalsVisibleTo("YamlDotNet.Test, PublicKey=002400000480000094000000060200000024000052534131000400000100010065e52a453dde5c5b4be5bbe2205755727fce80244b79b894faf8793d80f7db9a96d360b51c220782db32aacee4cb5b8a91bee33aeec700e1f21895c4baadef501eeeac609220d1651603b378173811ee5bb6a002df973d38821bd2fef820c00c174a69faec326a1983b570f07ec66147026b9c8753465de3a8d0c44b613b02af")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("13.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace YamlDotNet { internal sealed class CultureInfoAdapter : CultureInfo { private readonly IFormatProvider provider; public CultureInfoAdapter(CultureInfo baseCulture, IFormatProvider provider) : base(baseCulture.Name) { this.provider = provider; } public override object GetFormat(Type formatType) { return provider.GetFormat(formatType); } } internal static class ReflectionExtensions { private static readonly Func<PropertyInfo, bool> IsInstance = (PropertyInfo property) => !(property.GetMethod ?? property.SetMethod).IsStatic; private static readonly Func<PropertyInfo, bool> IsInstancePublic = (PropertyInfo property) => IsInstance(property) && (property.GetMethod ?? property.SetMethod).IsPublic; public static Type? BaseType(this Type type) { return type.GetTypeInfo().BaseType; } public static bool IsValueType(this Type type) { return type.GetTypeInfo().IsValueType; } public static bool IsGenericType(this Type type) { return type.GetTypeInfo().IsGenericType; } public static bool IsGenericTypeDefinition(this Type type) { return type.GetTypeInfo().IsGenericTypeDefinition; } public static bool IsInterface(this Type type) { return type.GetTypeInfo().IsInterface; } public static bool IsEnum(this Type type) { return type.GetTypeInfo().IsEnum; } public static bool HasDefaultConstructor(this Type type, bool allowPrivateConstructors) { TypeInfo typeInfo = type.GetTypeInfo(); if (!typeInfo.IsValueType) { return typeInfo.DeclaredConstructors.Any((ConstructorInfo c) => (c.IsPublic || (allowPrivateConstructors && c.IsPrivate)) && !c.IsStatic && c.GetParameters().Length == 0); } return true; } public static bool IsAssignableFrom(this Type type, Type source) { return type.IsAssignableFrom(source.GetTypeInfo()); } public static bool IsAssignableFrom(this Type type, TypeInfo source) { return type.GetTypeInfo().IsAssignableFrom(source); } public static TypeCode GetTypeCode(this Type type) { if (type.IsEnum()) { type = Enum.GetUnderlyingType(type); } if (type == typeof(bool)) { return TypeCode.Boolean; } if (type == typeof(char)) { return TypeCode.Char; } if (type == typeof(sbyte)) { return TypeCode.SByte; } if (type == typeof(byte)) { return TypeCode.Byte; } if (type == typeof(short)) { return TypeCode.Int16; } if (type == typeof(ushort)) { return TypeCode.UInt16; } if (type == typeof(int)) { return TypeCode.Int32; } if (type == typeof(uint)) { return TypeCode.UInt32; } if (type == typeof(long)) { return TypeCode.Int64; } if (type == typeof(ulong)) { return TypeCode.UInt64; } if (type == typeof(float)) { return TypeCode.Single; } if (type == typeof(double)) { return TypeCode.Double; } if (type == typeof(decimal)) { return TypeCode.Decimal; } if (type == typeof(DateTime)) { return TypeCode.DateTime; } if (type == typeof(string)) { return TypeCode.String; } return TypeCode.Object; } public static bool IsDbNull(this object value) { return value?.GetType()?.FullName == "System.DBNull"; } public static Type[] GetGenericArguments(this Type type) { return type.GetTypeInfo().GenericTypeArguments; } public static PropertyInfo? GetPublicProperty(this Type type, string name) { return type.GetRuntimeProperty(name); } public static FieldInfo? GetPublicStaticField(this Type type, string name) { return type.GetRuntimeField(name); } public static IEnumerable<PropertyInfo> GetProperties(this Type type, bool includeNonPublic) { Func<PropertyInfo, bool> predicate = (includeNonPublic ? IsInstance : IsInstancePublic); if (!type.IsInterface()) { return type.GetRuntimeProperties().Where(predicate); } return new Type[1] { type }.Concat(type.GetInterfaces()).SelectMany((Type i) => i.GetRuntimeProperties().Where(predicate)); } public static IEnumerable<PropertyInfo> GetPublicProperties(this Type type) { return type.GetProperties(includeNonPublic: false); } public static IEnumerable<FieldInfo> GetPublicFields(this Type type) { return from f in type.GetRuntimeFields() where !f.IsStatic && f.IsPublic select f; } public static IEnumerable<MethodInfo> GetPublicStaticMethods(this Type type) { return from m in type.GetRuntimeMethods() where m.IsPublic && m.IsStatic select m; } public static MethodInfo GetPrivateStaticMethod(this Type type, string name) { return type.GetRuntimeMethods().FirstOrDefault((MethodInfo m) => !m.IsPublic && m.IsStatic && m.Name.Equals(name)) ?? throw new MissingMethodException("Expected to find a method named '" + name + "' in '" + type.FullName + "'."); } public static MethodInfo? GetPublicStaticMethod(this Type type, string name, params Type[] parameterTypes) { return type.GetRuntimeMethods().FirstOrDefault(delegate(MethodInfo m) { if (m.IsPublic && m.IsStatic && m.Name.Equals(name)) { ParameterInfo[] parameters = m.GetParameters(); if (parameters.Length == parameterTypes.Length) { return parameters.Zip(parameterTypes, (ParameterInfo pi, Type pt) => pi.ParameterType == pt).All((bool r) => r); } return false; } return false; }); } public static MethodInfo? GetPublicInstanceMethod(this Type type, string name) { return type.GetRuntimeMethods().FirstOrDefault((MethodInfo m) => m.IsPublic && !m.IsStatic && m.Name.Equals(name)); } public static MethodInfo? GetGetMethod(this PropertyInfo property, bool nonPublic) { MethodInfo methodInfo = property.GetMethod; if (!nonPublic && !methodInfo.IsPublic) { methodInfo = null; } return methodInfo; } public static MethodInfo GetSetMethod(this PropertyInfo property) { return property.SetMethod; } public static IEnumerable<Type> GetInterfaces(this Type type) { return type.GetTypeInfo().ImplementedInterfaces; } public static Exception Unwrap(this TargetInvocationException ex) { return ex.InnerException; } public static bool IsInstanceOf(this Type type, object o) { if (!(o.GetType() == type)) { return o.GetType().GetTypeInfo().IsSubclassOf(type); } return true; } public static Attribute[] GetAllCustomAttributes<TAttribute>(this PropertyInfo member) { List<Attribute> list = new List<Attribute>(); Type type = member.DeclaringType; while (type != null) { type.GetPublicProperty(member.Name); list.AddRange(member.GetCustomAttributes(typeof(TAttribute))); type = type.BaseType(); } return list.ToArray(); } } internal static class PropertyInfoExtensions { public static object? ReadValue(this PropertyInfo property, object target) { return property.GetValue(target, null); } } internal static class StandardRegexOptions { public const RegexOptions Compiled = RegexOptions.Compiled; } } namespace YamlDotNet.Serialization { public abstract class BuilderSkeleton<TBuilder> where TBuilder : BuilderSkeleton<TBuilder> { internal INamingConvention namingConvention = NullNamingConvention.Instance; internal ITypeResolver typeResolver; internal readonly YamlAttributeOverrides overrides; internal readonly LazyComponentRegistrationList<Nothing, IYamlTypeConverter> typeConverterFactories; internal readonly LazyComponentRegistrationList<ITypeInspector, ITypeInspector> typeInspectorFactories; internal bool ignoreFields; internal bool includeNonPublicProperties; internal Settings settings; internal YamlFormatter yamlFormatter = YamlFormatter.Default; protected abstract TBuilder Self { get; } internal BuilderSkeleton(ITypeResolver typeResolver) { overrides = new YamlAttributeOverrides(); typeConverterFactories = new LazyComponentRegistrationList<Nothing, IYamlTypeConverter> { { typeof(YamlDotNet.Serialization.Converters.GuidConverter), (Nothing _) => new YamlDotNet.Serialization.Converters.GuidConverter(jsonCompatible: false) }, { typeof(SystemTypeConverter), (Nothing _) => new SystemTypeConverter() } }; typeInspectorFactories = new LazyComponentRegistrationList<ITypeInspector, ITypeInspector>(); this.typeResolver = typeResolver ?? throw new ArgumentNullException("typeResolver"); settings = new Settings(); } public TBuilder IgnoreFields() { ignoreFields = true; return Self; } public TBuilder IncludeNonPublicProperties() { includeNonPublicProperties = true; return Self; } public TBuilder EnablePrivateConstructors() { settings.AllowPrivateConstructors = true; return Self; } public TBuilder WithNamingConvention(INamingConvention namingConvention) { this.namingConvention = namingConvention ?? throw new ArgumentNullException("namingConvention"); return Self; } public TBuilder WithTypeResolver(ITypeResolver typeResolver) { this.typeResolver = typeResolver ?? throw new ArgumentNullException("typeResolver"); return Self; } public abstract TBuilder WithTagMapping(TagName tag, Type type); public TBuilder WithAttributeOverride<TClass>(Expression<Func<TClass, object>> propertyAccessor, Attribute attribute) { overrides.Add(propertyAccessor, attribute); return Self; } public TBuilder WithAttributeOverride(Type type, string member, Attribute attribute) { overrides.Add(type, member, attribute); return Self; } public TBuilder WithTypeConverter(IYamlTypeConverter typeConverter) { return WithTypeConverter(typeConverter, delegate(IRegistrationLocationSelectionSyntax<IYamlTypeConverter> w) { w.OnTop(); }); } public TBuilder WithTypeConverter(IYamlTypeConverter typeConverter, Action<IRegistrationLocationSelectionSyntax<IYamlTypeConverter>> where) { if (typeConverter == null) { throw new ArgumentNullException("typeConverter"); } if (where == null) { throw new ArgumentNullException("where"); } where(typeConverterFactories.CreateRegistrationLocationSelector(typeConverter.GetType(), (Nothing _) => typeConverter)); return Self; } public TBuilder WithTypeConverter<TYamlTypeConverter>(WrapperFactory<IYamlTypeConverter, IYamlTypeConverter> typeConverterFactory, Action<ITrackingRegistrationLocationSelectionSyntax<IYamlTypeConverter>> where) where TYamlTypeConverter : IYamlTypeConverter { if (typeConverterFactory == null) { throw new ArgumentNullException("typeConverterFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(typeConverterFactories.CreateTrackingRegistrationLocationSelector(typeof(TYamlTypeConverter), (IYamlTypeConverter wrapped, Nothing _) => typeConverterFactory(wrapped))); return Self; } public TBuilder WithoutTypeConverter<TYamlTypeConverter>() where TYamlTypeConverter : IYamlTypeConverter { return WithoutTypeConverter(typeof(TYamlTypeConverter)); } public TBuilder WithoutTypeConverter(Type converterType) { if (converterType == null) { throw new ArgumentNullException("converterType"); } typeConverterFactories.Remove(converterType); return Self; } public TBuilder WithTypeInspector<TTypeInspector>(Func<ITypeInspector, TTypeInspector> typeInspectorFactory) where TTypeInspector : ITypeInspector { return WithTypeInspector(typeInspectorFactory, delegate(IRegistrationLocationSelectionSyntax<ITypeInspector> w) { w.OnTop(); }); } public TBuilder WithTypeInspector<TTypeInspector>(Func<ITypeInspector, TTypeInspector> typeInspectorFactory, Action<IRegistrationLocationSelectionSyntax<ITypeInspector>> where) where TTypeInspector : ITypeInspector { if (typeInspectorFactory == null) { throw new ArgumentNullException("typeInspectorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(typeInspectorFactories.CreateRegistrationLocationSelector(typeof(TTypeInspector), (ITypeInspector inner) => typeInspectorFactory(inner))); return Self; } public TBuilder WithTypeInspector<TTypeInspector>(WrapperFactory<ITypeInspector, ITypeInspector, TTypeInspector> typeInspectorFactory, Action<ITrackingRegistrationLocationSelectionSyntax<ITypeInspector>> where) where TTypeInspector : ITypeInspector { if (typeInspectorFactory == null) { throw new ArgumentNullException("typeInspectorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(typeInspectorFactories.CreateTrackingRegistrationLocationSelector(typeof(TTypeInspector), (ITypeInspector wrapped, ITypeInspector inner) => typeInspectorFactory(wrapped, inner))); return Self; } public TBuilder WithoutTypeInspector<TTypeInspector>() where TTypeInspector : ITypeInspector { return WithoutTypeInspector(typeof(TTypeInspector)); } public TBuilder WithoutTypeInspector(Type inspectorType) { if (inspectorType == null) { throw new ArgumentNullException("inspectorType"); } typeInspectorFactories.Remove(inspectorType); return Self; } public TBuilder WithYamlFormatter(YamlFormatter formatter) { yamlFormatter = formatter ?? throw new ArgumentNullException("formatter"); return Self; } protected IEnumerable<IYamlTypeConverter> BuildTypeConverters() { return typeConverterFactories.BuildComponentList(); } } public delegate TComponent WrapperFactory<TComponentBase, TComponent>(TComponentBase wrapped) where TComponent : TComponentBase; public delegate TComponent WrapperFactory<TArgument, TComponentBase, TComponent>(TComponentBase wrapped, TArgument argument) where TComponent : TComponentBase; [Flags] public enum DefaultValuesHandling { Preserve = 0, OmitNull = 1, OmitDefaults = 2, OmitEmptyCollections = 4 } public sealed class Deserializer : IDeserializer { private readonly IValueDeserializer valueDeserializer; public Deserializer() : this(new DeserializerBuilder().BuildValueDeserializer()) { } private Deserializer(IValueDeserializer valueDeserializer) { this.valueDeserializer = valueDeserializer ?? throw new ArgumentNullException("valueDeserializer"); } public static Deserializer FromValueDeserializer(IValueDeserializer valueDeserializer) { return new Deserializer(valueDeserializer); } public T Deserialize<T>(string input) { using StringReader input2 = new StringReader(input); return Deserialize<T>(input2); } public T Deserialize<T>(TextReader input) { return Deserialize<T>(new Parser(input)); } public T Deserialize<T>(IParser parser) { return (T)Deserialize(parser, typeof(T)); } public object? Deserialize(string input) { return Deserialize(input, typeof(object)); } public object? Deserialize(TextReader input) { return Deserialize(input, typeof(object)); } public object? Deserialize(IParser parser) { return Deserialize(parser, typeof(object)); } public object? Deserialize(string input, Type type) { using StringReader input2 = new StringReader(input); return Deserialize(input2, type); } public object? Deserialize(TextReader input, Type type) { return Deserialize(new Parser(input), type); } public object? Deserialize(IParser parser, Type type) { if (parser == null) { throw new ArgumentNullException("parser"); } if (type == null) { throw new ArgumentNullException("type"); } YamlDotNet.Core.Events.StreamStart @event; bool flag = parser.TryConsume<YamlDotNet.Core.Events.StreamStart>(out @event); YamlDotNet.Core.Events.DocumentStart event2; bool flag2 = parser.TryConsume<YamlDotNet.Core.Events.DocumentStart>(out event2); object result = null; if (!parser.Accept<YamlDotNet.Core.Events.DocumentEnd>(out var _) && !parser.Accept<YamlDotNet.Core.Events.StreamEnd>(out var _)) { using SerializerState serializerState = new SerializerState(); result = valueDeserializer.DeserializeValue(parser, type, serializerState, valueDeserializer); serializerState.OnDeserialization(); } if (flag2) { parser.Consume<YamlDotNet.Core.Events.DocumentEnd>(); } if (flag) { parser.Consume<YamlDotNet.Core.Events.StreamEnd>(); } return result; } } public sealed class DeserializerBuilder : BuilderSkeleton<DeserializerBuilder> { private Lazy<IObjectFactory> objectFactory; private readonly LazyComponentRegistrationList<Nothing, INodeDeserializer> nodeDeserializerFactories; private readonly LazyComponentRegistrationList<Nothing, INodeTypeResolver> nodeTypeResolverFactories; private readonly Dictionary<TagName, Type> tagMappings; private readonly Dictionary<Type, Type> typeMappings; private readonly ITypeConverter typeConverter; private bool ignoreUnmatched; private bool duplicateKeyChecking; private bool attemptUnknownTypeDeserialization; protected override DeserializerBuilder Self => this; public DeserializerBuilder() : base((ITypeResolver)new StaticTypeResolver()) { typeMappings = new Dictionary<Type, Type>(); objectFactory = new Lazy<IObjectFactory>(() => new DefaultObjectFactory(typeMappings, settings), isThreadSafe: true); tagMappings = new Dictionary<TagName, Type> { { FailsafeSchema.Tags.Map, typeof(Dictionary<object, object>) }, { FailsafeSchema.Tags.Str, typeof(string) }, { JsonSchema.Tags.Bool, typeof(bool) }, { JsonSchema.Tags.Float, typeof(double) }, { JsonSchema.Tags.Int, typeof(int) }, { DefaultSchema.Tags.Timestamp, typeof(DateTime) } }; typeInspectorFactories.Add(typeof(CachedTypeInspector), (ITypeInspector inner) => new CachedTypeInspector(inner)); typeInspectorFactories.Add(typeof(NamingConventionTypeInspector), (ITypeInspector inner) => (!(namingConvention is NullNamingConvention)) ? new NamingConventionTypeInspector(inner, namingConvention) : inner); typeInspectorFactories.Add(typeof(YamlAttributesTypeInspector), (ITypeInspector inner) => new YamlAttributesTypeInspector(inner)); typeInspectorFactories.Add(typeof(YamlAttributeOverridesInspector), (ITypeInspector inner) => (overrides == null) ? inner : new YamlAttributeOverridesInspector(inner, overrides.Clone())); typeInspectorFactories.Add(typeof(ReadableAndWritablePropertiesTypeInspector), (ITypeInspector inner) => new ReadableAndWritablePropertiesTypeInspector(inner)); nodeDeserializerFactories = new LazyComponentRegistrationList<Nothing, INodeDeserializer> { { typeof(YamlConvertibleNodeDeserializer), (Nothing _) => new YamlConvertibleNodeDeserializer(objectFactory.Value) }, { typeof(YamlSerializableNodeDeserializer), (Nothing _) => new YamlSerializableNodeDeserializer(objectFactory.Value) }, { typeof(TypeConverterNodeDeserializer), (Nothing _) => new TypeConverterNodeDeserializer(BuildTypeConverters()) }, { typeof(NullNodeDeserializer), (Nothing _) => new NullNodeDeserializer() }, { typeof(ScalarNodeDeserializer), (Nothing _) => new ScalarNodeDeserializer(attemptUnknownTypeDeserialization, typeConverter, yamlFormatter) }, { typeof(ArrayNodeDeserializer), (Nothing _) => new ArrayNodeDeserializer() }, { typeof(DictionaryNodeDeserializer), (Nothing _) => new DictionaryNodeDeserializer(objectFactory.Value, duplicateKeyChecking) }, { typeof(CollectionNodeDeserializer), (Nothing _) => new CollectionNodeDeserializer(objectFactory.Value) }, { typeof(EnumerableNodeDeserializer), (Nothing _) => new EnumerableNodeDeserializer() }, { typeof(ObjectNodeDeserializer), (Nothing _) => new ObjectNodeDeserializer(objectFactory.Value, BuildTypeInspector(), ignoreUnmatched, duplicateKeyChecking, typeConverter) } }; nodeTypeResolverFactories = new LazyComponentRegistrationList<Nothing, INodeTypeResolver> { { typeof(MappingNodeTypeResolver), (Nothing _) => new MappingNodeTypeResolver(typeMappings) }, { typeof(YamlConvertibleTypeResolver), (Nothing _) => new YamlConvertibleTypeResolver() }, { typeof(YamlSerializableTypeResolver), (Nothing _) => new YamlSerializableTypeResolver() }, { typeof(TagNodeTypeResolver), (Nothing _) => new TagNodeTypeResolver(tagMappings) }, { typeof(PreventUnknownTagsNodeTypeResolver), (Nothing _) => new PreventUnknownTagsNodeTypeResolver() }, { typeof(DefaultContainersNodeTypeResolver), (Nothing _) => new DefaultContainersNodeTypeResolver() } }; typeConverter = new ReflectionTypeConverter(); } internal ITypeInspector BuildTypeInspector() { ITypeInspector typeInspector = new WritablePropertiesTypeInspector(typeResolver, includeNonPublicProperties); if (!ignoreFields) { typeInspector = new CompositeTypeInspector(new ReadableFieldsTypeInspector(typeResolver), typeInspector); } return typeInspectorFactories.BuildComponentChain(typeInspector); } public DeserializerBuilder WithAttemptingUnquotedStringTypeDeserialization() { attemptUnknownTypeDeserialization = true; return this; } public DeserializerBuilder WithObjectFactory(IObjectFactory objectFactory) { if (objectFactory == null) { throw new ArgumentNullException("objectFactory"); } this.objectFactory = new Lazy<IObjectFactory>(() => objectFactory, isThreadSafe: true); return this; } public DeserializerBuilder WithObjectFactory(Func<Type, object> objectFactory) { if (objectFactory == null) { throw new ArgumentNullException("objectFactory"); } return WithObjectFactory(new LambdaObjectFactory(objectFactory)); } public DeserializerBuilder WithNodeDeserializer(INodeDeserializer nodeDeserializer) { return WithNodeDeserializer(nodeDeserializer, delegate(IRegistrationLocationSelectionSyntax<INodeDeserializer> w) { w.OnTop(); }); } public DeserializerBuilder WithNodeDeserializer(INodeDeserializer nodeDeserializer, Action<IRegistrationLocationSelectionSyntax<INodeDeserializer>> where) { if (nodeDeserializer == null) { throw new ArgumentNullException("nodeDeserializer"); } if (where == null) { throw new ArgumentNullException("where"); } where(nodeDeserializerFactories.CreateRegistrationLocationSelector(nodeDeserializer.GetType(), (Nothing _) => nodeDeserializer)); return this; } public DeserializerBuilder WithNodeDeserializer<TNodeDeserializer>(WrapperFactory<INodeDeserializer, TNodeDeserializer> nodeDeserializerFactory, Action<ITrackingRegistrationLocationSelectionSyntax<INodeDeserializer>> where) where TNodeDeserializer : INodeDeserializer { if (nodeDeserializerFactory == null) { throw new ArgumentNullException("nodeDeserializerFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(nodeDeserializerFactories.CreateTrackingRegistrationLocationSelector(typeof(TNodeDeserializer), (INodeDeserializer wrapped, Nothing _) => nodeDeserializerFactory(wrapped))); return this; } public DeserializerBuilder WithoutNodeDeserializer<TNodeDeserializer>() where TNodeDeserializer : INodeDeserializer { return WithoutNodeDeserializer(typeof(TNodeDeserializer)); } public DeserializerBuilder WithoutNodeDeserializer(Type nodeDeserializerType) { if (nodeDeserializerType == null) { throw new ArgumentNullException("nodeDeserializerType"); } nodeDeserializerFactories.Remove(nodeDeserializerType); return this; } public DeserializerBuilder WithTypeDiscriminatingNodeDeserializer(Action<ITypeDiscriminatingNodeDeserializerOptions> configureTypeDiscriminatingNodeDeserializerOptions, int maxDepth = -1, int maxLength = -1) { TypeDiscriminatingNodeDeserializerOptions typeDiscriminatingNodeDeserializerOptions = new TypeDiscriminatingNodeDeserializerOptions(); configureTypeDiscriminatingNodeDeserializerOptions(typeDiscriminatingNodeDeserializerOptions); TypeDiscriminatingNodeDeserializer nodeDeserializer = new TypeDiscriminatingNodeDeserializer(nodeDeserializerFactories.BuildComponentList(), typeDiscriminatingNodeDeserializerOptions.discriminators, maxDepth, maxLength); return WithNodeDeserializer(nodeDeserializer, delegate(IRegistrationLocationSelectionSyntax<INodeDeserializer> s) { s.Before<DictionaryNodeDeserializer>(); }); } public DeserializerBuilder WithNodeTypeResolver(INodeTypeResolver nodeTypeResolver) { return WithNodeTypeResolver(nodeTypeResolver, delegate(IRegistrationLocationSelectionSyntax<INodeTypeResolver> w) { w.OnTop(); }); } public DeserializerBuilder WithNodeTypeResolver(INodeTypeResolver nodeTypeResolver, Action<IRegistrationLocationSelectionSyntax<INodeTypeResolver>> where) { if (nodeTypeResolver == null) { throw new ArgumentNullException("nodeTypeResolver"); } if (where == null) { throw new ArgumentNullException("where"); } where(nodeTypeResolverFactories.CreateRegistrationLocationSelector(nodeTypeResolver.GetType(), (Nothing _) => nodeTypeResolver)); return this; } public DeserializerBuilder WithNodeTypeResolver<TNodeTypeResolver>(WrapperFactory<INodeTypeResolver, TNodeTypeResolver> nodeTypeResolverFactory, Action<ITrackingRegistrationLocationSelectionSyntax<INodeTypeResolver>> where) where TNodeTypeResolver : INodeTypeResolver { if (nodeTypeResolverFactory == null) { throw new ArgumentNullException("nodeTypeResolverFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(nodeTypeResolverFactories.CreateTrackingRegistrationLocationSelector(typeof(TNodeTypeResolver), (INodeTypeResolver wrapped, Nothing _) => nodeTypeResolverFactory(wrapped))); return this; } public DeserializerBuilder WithoutNodeTypeResolver<TNodeTypeResolver>() where TNodeTypeResolver : INodeTypeResolver { return WithoutNodeTypeResolver(typeof(TNodeTypeResolver)); } public DeserializerBuilder WithoutNodeTypeResolver(Type nodeTypeResolverType) { if (nodeTypeResolverType == null) { throw new ArgumentNullException("nodeTypeResolverType"); } nodeTypeResolverFactories.Remove(nodeTypeResolverType); return this; } public override DeserializerBuilder WithTagMapping(TagName tag, Type type) { if (tag.IsEmpty) { throw new ArgumentException("Non-specific tags cannot be maped"); } if (type == null) { throw new ArgumentNullException("type"); } if (tagMappings.TryGetValue(tag, out Type value)) { throw new ArgumentException($"Type already has a registered type '{value.FullName}' for tag '{tag}'", "tag"); } tagMappings.Add(tag, type); return this; } public DeserializerBuilder WithTypeMapping<TInterface, TConcrete>() where TConcrete : TInterface { Type typeFromHandle = typeof(TInterface); Type typeFromHandle2 = typeof(TConcrete); if (!typeFromHandle.IsAssignableFrom(typeFromHandle2)) { throw new InvalidOperationException("The type '" + typeFromHandle2.Name + "' does not implement interface '" + typeFromHandle.Name + "'."); } if (typeMappings.ContainsKey(typeFromHandle)) { typeMappings[typeFromHandle] = typeFromHandle2; } else { typeMappings.Add(typeFromHandle, typeFromHandle2); } return this; } public DeserializerBuilder WithoutTagMapping(TagName tag) { if (tag.IsEmpty) { throw new ArgumentException("Non-specific tags cannot be maped"); } if (!tagMappings.Remove(tag)) { throw new KeyNotFoundException($"Tag '{tag}' is not registered"); } return this; } public DeserializerBuilder IgnoreUnmatchedProperties() { ignoreUnmatched = true; return this; } public DeserializerBuilder WithDuplicateKeyChecking() { duplicateKeyChecking = true; return this; } public IDeserializer Build() { return Deserializer.FromValueDeserializer(BuildValueDeserializer()); } public IValueDeserializer BuildValueDeserializer() { return new AliasValueDeserializer(new NodeValueDeserializer(nodeDeserializerFactories.BuildComponentList(), nodeTypeResolverFactories.BuildComponentList(), typeConverter)); } } public sealed class EmissionPhaseObjectGraphVisitorArgs { private readonly IEnumerable<IObjectGraphVisitor<Nothing>> preProcessingPhaseVisitors; public IObjectGraphVisitor<IEmitter> InnerVisitor { get; private set; } public IEventEmitter EventEmitter { get; private set; } public ObjectSerializer NestedObjectSerializer { get; private set; } public IEnumerable<IYamlTypeConverter> TypeConverters { get; private set; } public EmissionPhaseObjectGraphVisitorArgs(IObjectGraphVisitor<IEmitter> innerVisitor, IEventEmitter eventEmitter, IEnumerable<IObjectGraphVisitor<Nothing>> preProcessingPhaseVisitors, IEnumerable<IYamlTypeConverter> typeConverters, ObjectSerializer nestedObjectSerializer) { InnerVisitor = innerVisitor ?? throw new ArgumentNullException("innerVisitor"); EventEmitter = eventEmitter ?? throw new ArgumentNullException("eventEmitter"); this.preProcessingPhaseVisitors = preProcessingPhaseVisitors ?? throw new ArgumentNullException("preProcessingPhaseVisitors"); TypeConverters = typeConverters ?? throw new ArgumentNullException("typeConverters"); NestedObjectSerializer = nestedObjectSerializer ?? throw new ArgumentNullException("nestedObjectSerializer"); } public T GetPreProcessingPhaseObjectGraphVisitor<T>() where T : IObjectGraphVisitor<Nothing> { return preProcessingPhaseVisitors.OfType<T>().Single(); } } public abstract class EventInfo { public IObjectDescriptor Source { get; } protected EventInfo(IObjectDescriptor source) { Source = source ?? throw new ArgumentNullException("source"); } } public class AliasEventInfo : EventInfo { public AnchorName Alias { get; } public bool NeedsExpansion { get; set; } public AliasEventInfo(IObjectDescriptor source, AnchorName alias) : base(source) { if (alias.IsEmpty) { throw new ArgumentNullException("alias"); } Alias = alias; } } public class ObjectEventInfo : EventInfo { public AnchorName Anchor { get; set; } public TagName Tag { get; set; } protected ObjectEventInfo(IObjectDescriptor source) : base(source) { } } public sealed class ScalarEventInfo : ObjectEventInfo { public string RenderedValue { get; set; } public ScalarStyle Style { get; set; } public bool IsPlainImplicit { get; set; } public bool IsQuotedImplicit { get; set; } public ScalarEventInfo(IObjectDescriptor source) : base(source) { Style = source.ScalarStyle; RenderedValue = string.Empty; } } public sealed class MappingStartEventInfo : ObjectEventInfo { public bool IsImplicit { get; set; } public MappingStyle Style { get; set; } public MappingStartEventInfo(IObjectDescriptor source) : base(source) { } } public sealed class MappingEndEventInfo : EventInfo { public MappingEndEventInfo(IObjectDescriptor source) : base(source) { } } public sealed class SequenceStartEventInfo : ObjectEventInfo { public bool IsImplicit { get; set; } public SequenceStyle Style { get; set; } public SequenceStartEventInfo(IObjectDescriptor source) : base(source) { } } public sealed class SequenceEndEventInfo : EventInfo { public SequenceEndEventInfo(IObjectDescriptor source) : base(source) { } } public interface IAliasProvider { AnchorName GetAlias(object target); } public interface IDeserializer { T Deserialize<T>(string input); T Deserialize<T>(TextReader input); T Deserialize<T>(IParser parser); object? Deserialize(string input); object? Deserialize(TextReader input); object? Deserialize(IParser parser); object? Deserialize(string input, Type type); object? Deserialize(TextReader input, Type type); object? Deserialize(IParser parser, Type type); } public interface IEventEmitter { void Emit(AliasEventInfo eventInfo, IEmitter emitter); void Emit(ScalarEventInfo eventInfo, IEmitter emitter); void Emit(MappingStartEventInfo eventInfo, IEmitter emitter); void Emit(MappingEndEventInfo eventInfo, IEmitter emitter); void Emit(SequenceStartEventInfo eventInfo, IEmitter emitter); void Emit(SequenceEndEventInfo eventInfo, IEmitter emitter); } public interface INamingConvention { string Apply(string value); } public interface INodeDeserializer { bool Deserialize(IParser reader, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value); } public interface INodeTypeResolver { bool Resolve(NodeEvent? nodeEvent, ref Type currentType); } public interface IObjectAccessor { void Set(string name, object target, object value); object? Read(string name, object target); } public interface IObjectDescriptor { object? Value { get; } Type Type { get; } Type StaticType { get; } ScalarStyle ScalarStyle { get; } } public static class ObjectDescriptorExtensions { public static object NonNullValue(this IObjectDescriptor objectDescriptor) { return objectDescriptor.Value ?? throw new InvalidOperationException("Attempted to use a IObjectDescriptor of type '" + objectDescriptor.Type.FullName + "' whose Value is null at a point whete it is invalid to do so. This may indicate a bug in YamlDotNet."); } } public interface IObjectFactory { object Create(Type type); object? CreatePrimitive(Type type); bool GetDictionary(IObjectDescriptor descriptor, out IDictionary? dictionary, out Type[]? genericArguments); Type GetValueType(Type type); void ExecuteOnDeserializing(object value); void ExecuteOnDeserialized(object value); void ExecuteOnSerializing(object value); void ExecuteOnSerialized(object value); } public interface IObjectGraphTraversalStrategy { void Traverse<TContext>(IObjectDescriptor graph, IObjectGraphVisitor<TContext> visitor, TContext context); } public interface IObjectGraphVisitor<TContext> { bool Enter(IObjectDescriptor value, TContext context); bool EnterMapping(IObjectDescriptor key, IObjectDescriptor value, TContext context); bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, TContext context); void VisitScalar(IObjectDescriptor scalar, TContext context); void VisitMappingStart(IObjectDescriptor mapping, Type keyType, Type valueType, TContext context); void VisitMappingEnd(IObjectDescriptor mapping, TContext context); void VisitSequenceStart(IObjectDescriptor sequence, Type elementType, TContext context); void VisitSequenceEnd(IObjectDescriptor sequence, TContext context); } public interface IPropertyDescriptor { string Name { get; } bool CanWrite { get; } Type Type { get; } Type? TypeOverride { get; set; } int Order { get; set; } ScalarStyle ScalarStyle { get; set; } T? GetCustomAttribute<T>() where T : Attribute; IObjectDescriptor Read(object target); void Write(object target, object? value); } public interface IRegistrationLocationSelectionSyntax<TBaseRegistrationType> { void InsteadOf<TRegistrationType>() where TRegistrationType : TBaseRegistrationType; void Before<TRegistrationType>() where TRegistrationType : TBaseRegistrationType; void After<TRegistrationType>() where TRegistrationType : TBaseRegistrationType; void OnTop(); void OnBottom(); } public interface ITrackingRegistrationLocationSelectionSyntax<TBaseRegistrationType> { void InsteadOf<TRegistrationType>() where TRegistrationType : TBaseRegistrationType; } public interface ISerializer { string Serialize(object? graph); string Serialize(object? graph, Type type); void Serialize(TextWriter writer, object? graph); void Serialize(TextWriter writer, object? graph, Type type); void Serialize(IEmitter emitter, object? graph); void Serialize(IEmitter emitter, object? graph, Type type); } public interface ITypeInspector { IEnumerable<IPropertyDescriptor> GetProperties(Type type, object? container); IPropertyDescriptor GetProperty(Type type, object? container, string name, [MaybeNullWhen(true)] bool ignoreUnmatched); } public interface ITypeResolver { Type Resolve(Type staticType, object? actualValue); } public interface IValueDeserializer { object? DeserializeValue(IParser parser, Type expectedType, SerializerState state, IValueDeserializer nestedObjectDeserializer); } public interface IValuePromise { event Action<object?> ValueAvailable; } public interface IValueSerializer { void SerializeValue(IEmitter emitter, object? value, Type? type); } public interface IYamlConvertible { void Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer); void Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer); } public delegate object? ObjectDeserializer(Type type); public delegate void ObjectSerializer(object? value, Type? type = null); [Obsolete("Please use IYamlConvertible instead")] public interface IYamlSerializable { void ReadYaml(IParser parser); void WriteYaml(IEmitter emitter); } public interface IYamlTypeConverter { bool Accepts(Type type); object? ReadYaml(IParser parser, Type type); void WriteYaml(IEmitter emitter, object? value, Type type); } internal sealed class LazyComponentRegistrationList<TArgument, TComponent> : IEnumerable<Func<TArgument, TComponent>>, IEnumerable { public sealed class LazyComponentRegistration { public readonly Type ComponentType; public readonly Func<TArgument, TComponent> Factory; public LazyComponentRegistration(Type componentType, Func<TArgument, TComponent> factory) { ComponentType = componentType; Factory = factory; } } public sealed class TrackingLazyComponentRegistration { public readonly Type ComponentType; public readonly Func<TComponent, TArgument, TComponent> Factory; public TrackingLazyComponentRegistration(Type componentType, Func<TComponent, TArgument, TComponent> factory) { ComponentType = componentType; Factory = factory; } } private class RegistrationLocationSelector : IRegistrationLocationSelectionSyntax<TComponent> { private readonly LazyComponentRegistrationList<TArgument, TComponent> registrations; private readonly LazyComponentRegistration newRegistration; public RegistrationLocationSelector(LazyComponentRegistrationList<TArgument, TComponent> registrations, LazyComponentRegistration newRegistration) { this.registrations = registrations; this.newRegistration = newRegistration; } void IRegistrationLocationSelectionSyntax<TComponent>.InsteadOf<TRegistrationType>() { if (newRegistration.ComponentType != typeof(TRegistrationType)) { registrations.EnsureNoDuplicateRegistrationType(newRegistration.ComponentType); } int index = registrations.EnsureRegistrationExists<TRegistrationType>(); registrations.entries[index] = newRegistration; } void IRegistrationLocationSelectionSyntax<TComponent>.After<TRegistrationType>() { registrations.EnsureNoDuplicateRegistrationType(newRegistration.ComponentType); int num = registrations.EnsureRegistrationExists<TRegistrationType>(); registrations.entries.Insert(num + 1, newRegistration); } void IRegistrationLocationSelectionSyntax<TComponent>.Before<TRegistrationType>() { registrations.EnsureNoDuplicateRegistrationType(newRegistration.ComponentType); int index = registrations.EnsureRegistrationExists<TRegistrationType>(); registrations.entries.Insert(index, newRegistration); } void IRegistrationLocationSelectionSyntax<TComponent>.OnBottom() { registrations.EnsureNoDuplicateRegistrationType(newRegistration.ComponentType); registrations.entries.Add(newRegistration); } void IRegistrationLocationSelectionSyntax<TComponent>.OnTop() { registrations.EnsureNoDuplicateRegistrationType(newRegistration.ComponentType); registrations.entries.Insert(0, newRegistration); } } private class TrackingRegistrationLocationSelector : ITrackingRegistrationLocationSelectionSyntax<TComponent> { private readonly LazyComponentRegistrationList<TArgument, TComponent> registrations; private readonly TrackingLazyComponentRegistration newRegistration; public TrackingRegistrationLocationSelector(LazyComponentRegistrationList<TArgument, TComponent> registrations, TrackingLazyComponentRegistration newRegistration) { this.registrations = registrations; this.newRegistration = newRegistration; } void ITrackingRegistrationLocationSelectionSyntax<TComponent>.InsteadOf<TRegistrationType>() { if (newRegistration.ComponentType != typeof(TRegistrationType)) { registrations.EnsureNoDuplicateRegistrationType(newRegistration.ComponentType); } int index = registrations.EnsureRegistrationExists<TRegistrationType>(); Func<TArgument, TComponent> innerComponentFactory = registrations.entries[index].Factory; registrations.entries[index] = new LazyComponentRegistration(newRegistration.ComponentType, (TArgument arg) => newRegistration.Factory(innerComponentFactory(arg), arg)); } } private readonly List<LazyComponentRegistration> entries = new List<LazyComponentRegistration>(); public int Count => entries.Count; public IEnumerable<Func<TArgument, TComponent>> InReverseOrder { get { int i = entries.Count - 1; while (i >= 0) { yield return entries[i].Factory; int num = i - 1; i = num; } } } public LazyComponentRegistrationList<TArgument, TComponent> Clone() { LazyComponentRegistrationList<TArgument, TComponent> lazyComponentRegistrationList = new LazyComponentRegistrationList<TArgument, TComponent>(); foreach (LazyComponentRegistration entry in entries) { lazyComponentRegistrationList.entries.Add(entry); } return lazyComponentRegistrationList; } public void Clear() { entries.Clear(); } public void Add(Type componentType, Func<TArgument, TComponent> factory) { entries.Add(new LazyComponentRegistration(componentType, factory)); } public void Remove(Type componentType) { for (int i = 0; i < entries.Count; i++) { if (entries[i].ComponentType == componentType) { entries.RemoveAt(i); return; } } throw new KeyNotFoundException("A component registration of type '" + componentType.FullName + "' was not found."); } public IRegistrationLocationSelectionSyntax<TComponent> CreateRegistrationLocationSelector(Type componentType, Func<TArgument, TComponent> factory) { return new RegistrationLocationSelector(this, new LazyComponentRegistration(componentType, factory)); } public ITrackingRegistrationLocationSelectionSyntax<TComponent> CreateTrackingRegistrationLocationSelector(Type componentType, Func<TComponent, TArgument, TComponent> factory) { return new TrackingRegistrationLocationSelector(this, new TrackingLazyComponentRegistration(componentType, factory)); } public IEnumerator<Func<TArgument, TComponent>> GetEnumerator() { return entries.Select((LazyComponentRegistration e) => e.Factory).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private int IndexOfRegistration(Type registrationType) { for (int i = 0; i < entries.Count; i++) { if (registrationType == entries[i].ComponentType) { return i; } } return -1; } private void EnsureNoDuplicateRegistrationType(Type componentType) { if (IndexOfRegistration(componentType) != -1) { throw new InvalidOperationException("A component of type '" + componentType.FullName + "' has already been registered."); } } private int EnsureRegistrationExists<TRegistrationType>() { int num = IndexOfRegistration(typeof(TRegistrationType)); if (num == -1) { throw new InvalidOperationException("A component of type '" + typeof(TRegistrationType).FullName + "' has not been registered."); } return num; } } internal static class LazyComponentRegistrationListExtensions { public static TComponent BuildComponentChain<TComponent>(this LazyComponentRegistrationList<TComponent, TComponent> registrations, TComponent innerComponent) { return registrations.InReverseOrder.Aggregate(innerComponent, (TComponent inner, Func<TComponent, TComponent> factory) => factory(inner)); } public static TComponent BuildComponentChain<TArgument, TComponent>(this LazyComponentRegistrationList<TArgument, TComponent> registrations, TComponent innerComponent, Func<TComponent, TArgument> argumentBuilder) { return registrations.InReverseOrder.Aggregate(innerComponent, (TComponent inner, Func<TArgument, TComponent> factory) => factory(argumentBuilder(inner))); } public static List<TComponent> BuildComponentList<TComponent>(this LazyComponentRegistrationList<Nothing, TComponent> registrations) { return registrations.Select((Func<Nothing, TComponent> factory) => factory(default(Nothing))).ToList(); } public static List<TComponent> BuildComponentList<TArgument, TComponent>(this LazyComponentRegistrationList<TArgument, TComponent> registrations, TArgument argument) { return registrations.Select((Func<TArgument, TComponent> factory) => factory(argument)).ToList(); } } [StructLayout(LayoutKind.Sequential, Size = 1)] public struct Nothing { } public sealed class ObjectDescriptor : IObjectDescriptor { public object? Value { get; private set; } public Type Type { get; private set; } public Type StaticType { get; private set; } public ScalarStyle ScalarStyle { get; private set; } public ObjectDescriptor(object? value, Type type, Type staticType) : this(value, type, staticType, ScalarStyle.Any) { } public ObjectDescriptor(object? value, Type type, Type staticType, ScalarStyle scalarStyle) { Value = value; Type = type ?? throw new ArgumentNullException("type"); StaticType = staticType ?? throw new ArgumentNullException("staticType"); ScalarStyle = scalarStyle; } } public delegate IObjectGraphTraversalStrategy ObjectGraphTraversalStrategyFactory(ITypeInspector typeInspector, ITypeResolver typeResolver, IEnumerable<IYamlTypeConverter> typeConverters, int maximumRecursion); public sealed class PropertyDescriptor : IPropertyDescriptor { private readonly IPropertyDescriptor baseDescriptor; public string Name { get; set; } public Type Type => baseDescriptor.Type; public Type? TypeOverride { get { return baseDescriptor.TypeOverride; } set { baseDescriptor.TypeOverride = value; } } public int Order { get; set; } public ScalarStyle ScalarStyle { get { return baseDescriptor.ScalarStyle; } set { baseDescriptor.ScalarStyle = value; } } public bool CanWrite => baseDescriptor.CanWrite; public PropertyDescriptor(IPropertyDescriptor baseDescriptor) { this.baseDescriptor = baseDescriptor; Name = baseDescriptor.Name; } public void Write(object target, object? value) { baseDescriptor.Write(target, value); } public T? GetCustomAttribute<T>() where T : Attribute { return baseDescriptor.GetCustomAttribute<T>(); } public IObjectDescriptor Read(object target) { return baseDescriptor.Read(target); } } public sealed class Serializer : ISerializer { private readonly IValueSerializer valueSerializer; private readonly EmitterSettings emitterSettings; public Serializer() : this(new SerializerBuilder().BuildValueSerializer(), EmitterSettings.Default) { } private Serializer(IValueSerializer valueSerializer, EmitterSettings emitterSettings) { this.valueSerializer = valueSerializer ?? throw new ArgumentNullException("valueSerializer"); this.emitterSettings = emitterSettings ?? throw new ArgumentNullException("emitterSettings"); } public static Serializer FromValueSerializer(IValueSerializer valueSerializer, EmitterSettings emitterSettings) { return new Serializer(valueSerializer, emitterSettings); } public string Serialize(object? graph) { using StringWriter stringWriter = new StringWriter(); Serialize(stringWriter, graph); return stringWriter.ToString(); } public string Serialize(object? graph, Type type) { using StringWriter stringWriter = new StringWriter(); Serialize(stringWriter, graph, type); return stringWriter.ToString(); } public void Serialize(TextWriter writer, object? graph) { Serialize(new Emitter(writer, emitterSettings), graph); } public void Serialize(TextWriter writer, object? graph, Type type) { Serialize(new Emitter(writer, emitterSettings), graph, type); } public void Serialize(IEmitter emitter, object? graph) { if (emitter == null) { throw new ArgumentNullException("emitter"); } EmitDocument(emitter, graph, null); } public void Serialize(IEmitter emitter, object? graph, Type type) { if (emitter == null) { throw new ArgumentNullException("emitter"); } if (type == null) { throw new ArgumentNullException("type"); } EmitDocument(emitter, graph, type); } private void EmitDocument(IEmitter emitter, object? graph, Type? type) { emitter.Emit(new YamlDotNet.Core.Events.StreamStart()); emitter.Emit(new YamlDotNet.Core.Events.DocumentStart()); valueSerializer.SerializeValue(emitter, graph, type); emitter.Emit(new YamlDotNet.Core.Events.DocumentEnd(isImplicit: true)); emitter.Emit(new YamlDotNet.Core.Events.StreamEnd()); } } public sealed class SerializerBuilder : BuilderSkeleton<SerializerBuilder> { private class ValueSerializer : IValueSerializer { private readonly IObjectGraphTraversalStrategy traversalStrategy; private readonly IEventEmitter eventEmitter; private readonly IEnumerable<IYamlTypeConverter> typeConverters; private readonly LazyComponentRegistrationList<IEnumerable<IYamlTypeConverter>, IObjectGraphVisitor<Nothing>> preProcessingPhaseObjectGraphVisitorFactories; private readonly LazyComponentRegistrationList<EmissionPhaseObjectGraphVisitorArgs, IObjectGraphVisitor<IEmitter>> emissionPhaseObjectGraphVisitorFactories; public ValueSerializer(IObjectGraphTraversalStrategy traversalStrategy, IEventEmitter eventEmitter, IEnumerable<IYamlTypeConverter> typeConverters, LazyComponentRegistrationList<IEnumerable<IYamlTypeConverter>, IObjectGraphVisitor<Nothing>> preProcessingPhaseObjectGraphVisitorFactories, LazyComponentRegistrationList<EmissionPhaseObjectGraphVisitorArgs, IObjectGraphVisitor<IEmitter>> emissionPhaseObjectGraphVisitorFactories) { this.traversalStrategy = traversalStrategy; this.eventEmitter = eventEmitter; this.typeConverters = typeConverters; this.preProcessingPhaseObjectGraphVisitorFactories = preProcessingPhaseObjectGraphVisitorFactories; this.emissionPhaseObjectGraphVisitorFactories = emissionPhaseObjectGraphVisitorFactories; } public void SerializeValue(IEmitter emitter, object? value, Type? type) { Type type2 = type ?? ((value != null) ? value.GetType() : typeof(object)); Type staticType = type ?? typeof(object); ObjectDescriptor graph = new ObjectDescriptor(value, type2, staticType); List<IObjectGraphVisitor<Nothing>> preProcessingPhaseObjectGraphVisitors = preProcessingPhaseObjectGraphVisitorFactories.BuildComponentList(typeConverters); foreach (IObjectGraphVisitor<Nothing> item in preProcessingPhaseObjectGraphVisitors) { traversalStrategy.Traverse(graph, item, default(Nothing)); } IObjectGraphVisitor<IEmitter> visitor = emissionPhaseObjectGraphVisitorFactories.BuildComponentChain<EmissionPhaseObjectGraphVisitorArgs, IObjectGraphVisitor<IEmitter>>(new EmittingObjectGraphVisitor(eventEmitter), (IObjectGraphVisitor<IEmitter> inner) => new EmissionPhaseObjectGraphVisitorArgs(inner, eventEmitter, preProcessingPhaseObjectGraphVisitors, typeConverters, NestedObjectSerializer)); traversalStrategy.Traverse(graph, visitor, emitter); void NestedObjectSerializer(object? v, Type? t) { SerializeValue(emitter, v, t); } } } private ObjectGraphTraversalStrategyFactory objectGraphTraversalStrategyFactory; private readonly LazyComponentRegistrationList<IEnumerable<IYamlTypeConverter>, IObjectGraphVisitor<Nothing>> preProcessingPhaseObjectGraphVisitorFactories; private readonly LazyComponentRegistrationList<EmissionPhaseObjectGraphVisitorArgs, IObjectGraphVisitor<IEmitter>> emissionPhaseObjectGraphVisitorFactories; private readonly LazyComponentRegistrationList<IEventEmitter, IEventEmitter> eventEmitterFactories; private readonly IDictionary<Type, TagName> tagMappings = new Dictionary<Type, TagName>(); private readonly IObjectFactory objectFactory; private int maximumRecursion = 50; private EmitterSettings emitterSettings = EmitterSettings.Default; private DefaultValuesHandling defaultValuesHandlingConfiguration; private ScalarStyle defaultScalarStyle; private bool quoteNecessaryStrings; private bool quoteYaml1_1Strings; protected override SerializerBuilder Self => this; public SerializerBuilder() : base((ITypeResolver)new DynamicTypeResolver()) { typeInspectorFactories.Add(typeof(CachedTypeInspector), (ITypeInspector inner) => new CachedTypeInspector(inner)); typeInspectorFactories.Add(typeof(NamingConventionTypeInspector), (ITypeInspector inner) => (!(namingConvention is NullNamingConvention)) ? new NamingConventionTypeInspector(inner, namingConvention) : inner); typeInspectorFactories.Add(typeof(YamlAttributesTypeInspector), (ITypeInspector inner) => new YamlAttributesTypeInspector(inner)); typeInspectorFactories.Add(typeof(YamlAttributeOverridesInspector), (ITypeInspector inner) => (overrides == null) ? inner : new YamlAttributeOverridesInspector(inner, overrides.Clone())); preProcessingPhaseObjectGraphVisitorFactories = new LazyComponentRegistrationList<IEnumerable<IYamlTypeConverter>, IObjectGraphVisitor<Nothing>> { { typeof(AnchorAssigner), (IEnumerable<IYamlTypeConverter> typeConverters) => new AnchorAssigner(typeConverters) } }; emissionPhaseObjectGraphVisitorFactories = new LazyComponentRegistrationList<EmissionPhaseObjectGraphVisitorArgs, IObjectGraphVisitor<IEmitter>> { { typeof(CustomSerializationObjectGraphVisitor), (EmissionPhaseObjectGraphVisitorArgs args) => new CustomSerializationObjectGraphVisitor(args.InnerVisitor, args.TypeConverters, args.NestedObjectSerializer) }, { typeof(AnchorAssigningObjectGraphVisitor), (EmissionPhaseObjectGraphVisitorArgs args) => new AnchorAssigningObjectGraphVisitor(args.InnerVisitor, args.EventEmitter, args.GetPreProcessingPhaseObjectGraphVisitor<AnchorAssigner>()) }, { typeof(DefaultValuesObjectGraphVisitor), (EmissionPhaseObjectGraphVisitorArgs args) => new DefaultValuesObjectGraphVisitor(defaultValuesHandlingConfiguration, args.InnerVisitor, new DefaultObjectFactory()) }, { typeof(CommentsObjectGraphVisitor), (EmissionPhaseObjectGraphVisitorArgs args) => new CommentsObjectGraphVisitor(args.InnerVisitor) } }; eventEmitterFactories = new LazyComponentRegistrationList<IEventEmitter, IEventEmitter> { { typeof(TypeAssigningEventEmitter), (IEventEmitter inner) => new TypeAssigningEventEmitter(inner, requireTagWhenStaticAndActualTypesAreDifferent: false, tagMappings, quoteNecessaryStrings, quoteYaml1_1Strings, defaultScalarStyle, yamlFormatter) } }; objectFactory = new DefaultObjectFactory(); objectGraphTraversalStrategyFactory = (ITypeInspector typeInspector, ITypeResolver typeResolver, IEnumerable<IYamlTypeConverter> typeConverters, int maximumRecursion) => new FullObjectGraphTraversalStrategy(typeInspector, typeResolver, maximumRecursion, namingConvention, objectFactory); } public SerializerBuilder WithQuotingNecessaryStrings(bool quoteYaml1_1Strings = false) { quoteNecessaryStrings = true; this.quoteYaml1_1Strings = quoteYaml1_1Strings; return this; } public SerializerBuilder WithDefaultScalarStyle(ScalarStyle style) { defaultScalarStyle = style; return this; } public SerializerBuilder WithMaximumRecursion(int maximumRecursion) { if (maximumRecursion <= 0) { throw new ArgumentOutOfRangeException("maximumRecursion", $"The maximum recursion specified ({maximumRecursion}) is invalid. It should be a positive integer."); } this.maximumRecursion = maximumRecursion; return this; } public SerializerBuilder WithEventEmitter<TEventEmitter>(Func<IEventEmitter, TEventEmitter> eventEmitterFactory) where TEventEmitter : IEventEmitter { return WithEventEmitter(eventEmitterFactory, delegate(IRegistrationLocationSelectionSyntax<IEventEmitter> w) { w.OnTop(); }); } public SerializerBuilder WithEventEmitter<TEventEmitter>(Func<IEventEmitter, TEventEmitter> eventEmitterFactory, Action<IRegistrationLocationSelectionSyntax<IEventEmitter>> where) where TEventEmitter : IEventEmitter { if (eventEmitterFactory == null) { throw new ArgumentNullException("eventEmitterFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(eventEmitterFactories.CreateRegistrationLocationSelector(typeof(TEventEmitter), (IEventEmitter inner) => eventEmitterFactory(inner))); return Self; } public SerializerBuilder WithEventEmitter<TEventEmitter>(WrapperFactory<IEventEmitter, IEventEmitter, TEventEmitter> eventEmitterFactory, Action<ITrackingRegistrationLocationSelectionSyntax<IEventEmitter>> where) where TEventEmitter : IEventEmitter { if (eventEmitterFactory == null) { throw new ArgumentNullException("eventEmitterFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(eventEmitterFactories.CreateTrackingRegistrationLocationSelector(typeof(TEventEmitter), (IEventEmitter wrapped, IEventEmitter inner) => eventEmitterFactory(wrapped, inner))); return Self; } public SerializerBuilder WithoutEventEmitter<TEventEmitter>() where TEventEmitter : IEventEmitter { return WithoutEventEmitter(typeof(TEventEmitter)); } public SerializerBuilder WithoutEventEmitter(Type eventEmitterType) { if (eventEmitterType == null) { throw new ArgumentNullException("eventEmitterType"); } eventEmitterFactories.Remove(eventEmitterType); return this; } public override SerializerBuilder WithTagMapping(TagName tag, Type type) { if (tag.IsEmpty) { throw new ArgumentException("Non-specific tags cannot be maped"); } if (type == null) { throw new ArgumentNullException("type"); } if (tagMappings.TryGetValue(type, out var value)) { throw new ArgumentException($"Type already has a registered tag '{value}' for type '{type.FullName}'", "type"); } tagMappings.Add(type, tag); return this; } public SerializerBuilder WithoutTagMapping(Type type) { if (type == null) { throw new ArgumentNullException("type"); } if (!tagMappings.Remove(type)) { throw new KeyNotFoundException("Tag for type '" + type.FullName + "' is not registered"); } return this; } public SerializerBuilder EnsureRoundtrip() { objectGraphTraversalStrategyFactory = (ITypeInspector typeInspector, ITypeResolver typeResolver, IEnumerable<IYamlTypeConverter> typeConverters, int maximumRecursion) => new RoundtripObjectGraphTraversalStrategy(typeConverters, typeInspector, typeResolver, maximumRecursion, namingConvention, settings, objectFactory); WithEventEmitter((IEventEmitter inner) => new TypeAssigningEventEmitter(inner, requireTagWhenStaticAndActualTypesAreDifferent: true, tagMappings, quoteNecessaryStrings, quoteYaml1_1Strings, defaultScalarStyle, yamlFormatter), delegate(IRegistrationLocationSelectionSyntax<IEventEmitter> loc) { loc.InsteadOf<TypeAssigningEventEmitter>(); }); return WithTypeInspector((ITypeInspector inner) => new ReadableAndWritablePropertiesTypeInspector(inner), delegate(IRegistrationLocationSelectionSyntax<ITypeInspector> loc) { loc.OnBottom(); }); } public SerializerBuilder DisableAliases() { preProcessingPhaseObjectGraphVisitorFactories.Remove(typeof(AnchorAssigner)); emissionPhaseObjectGraphVisitorFactories.Remove(typeof(AnchorAssigningObjectGraphVisitor)); return this; } [Obsolete("The default behavior is now to always emit default values, thefore calling this method has no effect. This behavior is now controlled by ConfigureDefaultValuesHandling.", true)] public SerializerBuilder EmitDefaults() { return ConfigureDefaultValuesHandling(DefaultValuesHandling.Preserve); } public SerializerBuilder ConfigureDefaultValuesHandling(DefaultValuesHandling configuration) { defaultValuesHandlingConfiguration = configuration; return this; } public SerializerBuilder JsonCompatible() { emitterSettings = emitterSettings.WithMaxSimpleKeyLength(int.MaxValue).WithoutAnchorName(); return WithTypeConverter(new YamlDotNet.Serialization.Converters.GuidConverter(jsonCompatible: true), delegate(IRegistrationLocationSelectionSyntax<IYamlTypeConverter> w) { w.InsteadOf<YamlDotNet.Serialization.Converters.GuidConverter>(); }).WithTypeConverter(new YamlDotNet.Serialization.Converters.DateTimeConverter(DateTimeKind.Utc, null, true)).WithEventEmitter((IEventEmitter inner) => new JsonEventEmitter(inner, yamlFormatter), delegate(IRegistrationLocationSelectionSyntax<IEventEmitter> loc) { loc.InsteadOf<TypeAssigningEventEmitter>(); }); } public SerializerBuilder WithNewLine(string newLine) { emitterSettings = emitterSettings.WithNewLine(newLine); return this; } public SerializerBuilder WithPreProcessingPhaseObjectGraphVisitor<TObjectGraphVisitor>(TObjectGraphVisitor objectGraphVisitor) where TObjectGraphVisitor : IObjectGraphVisitor<Nothing> { return WithPreProcessingPhaseObjectGraphVisitor(objectGraphVisitor, delegate(IRegistrationLocationSelectionSyntax<IObjectGraphVisitor<Nothing>> w) { w.OnTop(); }); } public SerializerBuilder WithPreProcessingPhaseObjectGraphVisitor<TObjectGraphVisitor>(TObjectGraphVisitor objectGraphVisitor, Action<IRegistrationLocationSelectionSyntax<IObjectGraphVisitor<Nothing>>> where) where TObjectGraphVisitor : IObjectGraphVisitor<Nothing> { if (objectGraphVisitor == null) { throw new ArgumentNullException("objectGraphVisitor"); } if (where == null) { throw new ArgumentNullException("where"); } where(preProcessingPhaseObjectGraphVisitorFactories.CreateRegistrationLocationSelector(typeof(TObjectGraphVisitor), (IEnumerable<IYamlTypeConverter> _) => objectGraphVisitor)); return this; } public SerializerBuilder WithPreProcessingPhaseObjectGraphVisitor<TObjectGraphVisitor>(WrapperFactory<IObjectGraphVisitor<Nothing>, TObjectGraphVisitor> objectGraphVisitorFactory, Action<ITrackingRegistrationLocationSelectionSyntax<IObjectGraphVisitor<Nothing>>> where) where TObjectGraphVisitor : IObjectGraphVisitor<Nothing> { if (objectGraphVisitorFactory == null) { throw new ArgumentNullException("objectGraphVisitorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(preProcessingPhaseObjectGraphVisitorFactories.CreateTrackingRegistrationLocationSelector(typeof(TObjectGraphVisitor), (IObjectGraphVisitor<Nothing> wrapped, IEnumerable<IYamlTypeConverter> _) => objectGraphVisitorFactory(wrapped))); return this; } public SerializerBuilder WithoutPreProcessingPhaseObjectGraphVisitor<TObjectGraphVisitor>() where TObjectGraphVisitor : IObjectGraphVisitor<Nothing> { return WithoutPreProcessingPhaseObjectGraphVisitor(typeof(TObjectGraphVisitor)); } public SerializerBuilder WithoutPreProcessingPhaseObjectGraphVisitor(Type objectGraphVisitorType) { if (objectGraphVisitorType == null) { throw new ArgumentNullException("objectGraphVisitorType"); } preProcessingPhaseObjectGraphVisitorFactories.Remove(objectGraphVisitorType); return this; } public SerializerBuilder WithObjectGraphTraversalStrategyFactory(ObjectGraphTraversalStrategyFactory objectGraphTraversalStrategyFactory) { this.objectGraphTraversalStrategyFactory = objectGraphTraversalStrategyFactory; return this; } public SerializerBuilder WithEmissionPhaseObjectGraphVisitor<TObjectGraphVisitor>(Func<EmissionPhaseObjectGraphVisitorArgs, TObjectGraphVisitor> objectGraphVisitorFactory) where TObjectGraphVisitor : IObjectGraphVisitor<IEmitter> { return WithEmissionPhaseObjectGraphVisitor(objectGraphVisitorFactory, delegate(IRegistrationLocationSelectionSyntax<IObjectGraphVisitor<IEmitter>> w) { w.OnTop(); }); } public SerializerBuilder WithEmissionPhaseObjectGraphVisitor<TObjectGraphVisitor>(Func<EmissionPhaseObjectGraphVisitorArgs, TObjectGraphVisitor> objectGraphVisitorFactory, Action<IRegistrationLocationSelectionSyntax<IObjectGraphVisitor<IEmitter>>> where) where TObjectGraphVisitor : IObjectGraphVisitor<IEmitter> { if (objectGraphVisitorFactory == null) { throw new ArgumentNullException("objectGraphVisitorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(emissionPhaseObjectGraphVisitorFactories.CreateRegistrationLocationSelector(typeof(TObjectGraphVisitor), (EmissionPhaseObjectGraphVisitorArgs args) => objectGraphVisitorFactory(args))); return this; } public SerializerBuilder WithEmissionPhaseObjectGraphVisitor<TObjectGraphVisitor>(WrapperFactory<EmissionPhaseObjectGraphVisitorArgs, IObjectGraphVisitor<IEmitter>, TObjectGraphVisitor> objectGraphVisitorFactory, Action<ITrackingRegistrationLocationSelectionSyntax<IObjectGraphVisitor<IEmitter>>> where) where TObjectGraphVisitor : IObjectGraphVisitor<IEmitter> { if (objectGraphVisitorFactory == null) { throw new ArgumentNullException("objectGraphVisitorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(emissionPhaseObjectGraphVisitorFactories.CreateTrackingRegistrationLocationSelector(typeof(TObjectGraphVisitor), (IObjectGraphVisitor<IEmitter> wrapped, EmissionPhaseObjectGraphVisitorArgs args) => objectGraphVisitorFactory(wrapped, args))); return this; } public SerializerBuilder WithoutEmissionPhaseObjectGraphVisitor<TObjectGraphVisitor>() where TObjectGraphVisitor : IObjectGraphVisitor<IEmitter> { return WithoutEmissionPhaseObjectGraphVisitor(typeof(TObjectGraphVisitor)); } public SerializerBuilder WithoutEmissionPhaseObjectGraphVisitor(Type objectGraphVisitorType) { if (objectGraphVisitorType == null) { throw new ArgumentNullException("objectGraphVisitorType"); } emissionPhaseObjectGraphVisitorFactories.Remove(objectGraphVisitorType); return this; } public SerializerBuilder WithIndentedSequences() { emitterSettings = emitterSettings.WithIndentedSequences(); return this; } public ISerializer Build() { return Serializer.FromValueSerializer(BuildValueSerializer(), emitterSettings); } public IValueSerializer BuildValueSerializer() { IEnumerable<IYamlTypeConverter> typeConverters = BuildTypeConverters(); ITypeInspector typeInspector = BuildTypeInspector(); IObjectGraphTraversalStrategy traversalStrategy = objectGraphTraversalStrategyFactory(typeInspector, typeResolver, typeConverters, maximumRecursion); IEventEmitter eventEmitter = eventEmitterFactories.BuildComponentChain(new WriterEventEmitter()); return new ValueSerializer(traversalStrategy, eventEmitter, typeConverters, preProcessingPhaseObjectGraphVisitorFactories.Clone(), emissionPhaseObjectGraphVisitorFactories.Clone()); } internal ITypeInspector BuildTypeInspector() { ITypeInspector typeInspector = new ReadablePropertiesTypeInspector(typeResolver, includeNonPublicProperties); if (!ignoreFields) { typeInspector = new CompositeTypeInspector(new ReadableFieldsTypeInspector(typeResolver), typeInspector); } return typeInspectorFactories.BuildComponentChain(typeInspector); } } public class Settings { public bool AllowPrivateConstructors { get; set; } } public abstract class StaticBuilderSkeleton<TBuilder> where TBuilder : StaticBuilderSkeleton<TBuilder> { internal INamingConvention namingConvention = NullNamingConvention.Instance; internal ITypeResolver typeResolver; internal readonly LazyComponentRegistrationList<Nothing, IYamlTypeConverter> typeConverterFactories; internal readonly LazyComponentRegistrationList<ITypeInspector, ITypeInspector> typeInspectorFactories; internal bool includeNonPublicProperties; internal Settings settings; internal YamlFormatter yamlFormatter = YamlFormatter.Default; protected abstract TBuilder Self { get; } internal StaticBuilderSkeleton(ITypeResolver typeResolver) { typeConverterFactories = new LazyComponentRegistrationList<Nothing, IYamlTypeConverter> { { typeof(YamlDotNet.Serialization.Converters.GuidConverter), (Nothing _) => new YamlDotNet.Serialization.Converters.GuidConverter(jsonCompatible: false) } }; typeInspectorFactories = new LazyComponentRegistrationList<ITypeInspector, ITypeInspector>(); this.typeResolver = typeResolver ?? throw new ArgumentNullException("typeResolver"); settings = new Settings(); } public TBuilder WithNamingConvention(INamingConvention namingConvention) { this.namingConvention = namingConvention ?? throw new ArgumentNullException("namingConvention"); return Self; } public TBuilder WithTypeResolver(ITypeResolver typeResolver) { this.typeResolver = typeResolver ?? throw new ArgumentNullException("typeResolver"); return Self; } public abstract TBuilder WithTagMapping(TagName tag, Type type); public TBuilder WithTypeConverter(IYamlTypeConverter typeConverter) { return WithTypeConverter(typeConverter, delegate(IRegistrationLocationSelectionSyntax<IYamlTypeConverter> w) { w.OnTop(); }); } public TBuilder WithTypeConverter(IYamlTypeConverter typeConverter, Action<IRegistrationLocationSelectionSyntax<IYamlTypeConverter>> where) { if (typeConverter == null) { throw new ArgumentNullException("typeConverter"); } if (where == null) { throw new ArgumentNullException("where"); } where(typeConverterFactories.CreateRegistrationLocationSelector(typeConverter.GetType(), (Nothing _) => typeConverter)); return Self; } public TBuilder WithTypeConverter<TYamlTypeConverter>(WrapperFactory<IYamlTypeConverter, IYamlTypeConverter> typeConverterFactory, Action<ITrackingRegistrationLocationSelectionSyntax<IYamlTypeConverter>> where) where TYamlTypeConverter : IYamlTypeConverter { if (typeConverterFactory == null) { throw new ArgumentNullException("typeConverterFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(typeConverterFactories.CreateTrackingRegistrationLocationSelector(typeof(TYamlTypeConverter), (IYamlTypeConverter wrapped, Nothing _) => typeConverterFactory(wrapped))); return Self; } public TBuilder WithoutTypeConverter<TYamlTypeConverter>() where TYamlTypeConverter : IYamlTypeConverter { return WithoutTypeConverter(typeof(TYamlTypeConverter)); } public TBuilder WithoutTypeConverter(Type converterType) { if (converterType == null) { throw new ArgumentNullException("converterType"); } typeConverterFactories.Remove(converterType); return Self; } public TBuilder WithTypeInspector<TTypeInspector>(Func<ITypeInspector, TTypeInspector> typeInspectorFactory) where TTypeInspector : ITypeInspector { return WithTypeInspector(typeInspectorFactory, delegate(IRegistrationLocationSelectionSyntax<ITypeInspector> w) { w.OnTop(); }); } public TBuilder WithTypeInspector<TTypeInspector>(Func<ITypeInspector, TTypeInspector> typeInspectorFactory, Action<IRegistrationLocationSelectionSyntax<ITypeInspector>> where) where TTypeInspector : ITypeInspector { if (typeInspectorFactory == null) { throw new ArgumentNullException("typeInspectorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(typeInspectorFactories.CreateRegistrationLocationSelector(typeof(TTypeInspector), (ITypeInspector inner) => typeInspectorFactory(inner))); return Self; } public TBuilder WithTypeInspector<TTypeInspector>(WrapperFactory<ITypeInspector, ITypeInspector, TTypeInspector> typeInspectorFactory, Action<ITrackingRegistrationLocationSelectionSyntax<ITypeInspector>> where) where TTypeInspector : ITypeInspector { if (typeInspectorFactory == null) { throw new ArgumentNullException("typeInspectorFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(typeInspectorFactories.CreateTrackingRegistrationLocationSelector(typeof(TTypeInspector), (ITypeInspector wrapped, ITypeInspector inner) => typeInspectorFactory(wrapped, inner))); return Self; } public TBuilder WithoutTypeInspector<TTypeInspector>() where TTypeInspector : ITypeInspector { return WithoutTypeInspector(typeof(TTypeInspector)); } public TBuilder WithoutTypeInspector(Type inspectorType) { if (inspectorType == null) { throw new ArgumentNullException("inspectorType"); } typeInspectorFactories.Remove(inspectorType); return Self; } public TBuilder WithYamlFormatter(YamlFormatter formatter) { yamlFormatter = formatter ?? throw new ArgumentNullException("formatter"); return Self; } protected IEnumerable<IYamlTypeConverter> BuildTypeConverters() { return typeConverterFactories.BuildComponentList(); } } public abstract class StaticContext { public virtual bool IsKnownType(Type type) { throw new NotImplementedException(); } public virtual ITypeResolver GetTypeResolver() { throw new NotImplementedException(); } public virtual StaticObjectFactory GetFactory() { throw new NotImplementedException(); } public virtual ITypeInspector GetTypeInspector() { throw new NotImplementedException(); } } public sealed class StaticDeserializerBuilder : StaticBuilderSkeleton<StaticDeserializerBuilder> { private readonly StaticContext context; private readonly StaticObjectFactory factory; private readonly LazyComponentRegistrationList<Nothing, INodeDeserializer> nodeDeserializerFactories; private readonly LazyComponentRegistrationList<Nothing, INodeTypeResolver> nodeTypeResolverFactories; private readonly Dictionary<TagName, Type> tagMappings; private readonly ITypeConverter typeConverter; private readonly Dictionary<Type, Type> typeMappings; private bool ignoreUnmatched; private bool duplicateKeyChecking; private bool attemptUnknownTypeDeserialization; protected override StaticDeserializerBuilder Self => this; public StaticDeserializerBuilder(StaticContext context) : base(context.GetTypeResolver()) { this.context = context; factory = context.GetFactory(); typeMappings = new Dictionary<Type, Type>(); tagMappings = new Dictionary<TagName, Type> { { FailsafeSchema.Tags.Map, typeof(Dictionary<object, object>) }, { FailsafeSchema.Tags.Str, typeof(string) }, { JsonSchema.Tags.Bool, typeof(bool) }, { JsonSchema.Tags.Float, typeof(double) }, { JsonSchema.Tags.Int, typeof(int) }, { DefaultSchema.Tags.Timestamp, typeof(DateTime) } }; typeInspectorFactories.Add(typeof(CachedTypeInspector), (ITypeInspector inner) => new CachedTypeInspector(inner)); typeInspectorFactories.Add(typeof(NamingConventionTypeInspector), (ITypeInspector inner) => (!(namingConvention is NullNamingConvention)) ? new NamingConventionTypeInspector(inner, namingConvention) : inner); typeInspectorFactories.Add(typeof(YamlAttributesTypeInspector), (ITypeInspector inner) => new YamlAttributesTypeInspector(inner)); nodeDeserializerFactories = new LazyComponentRegistrationList<Nothing, INodeDeserializer> { { typeof(YamlConvertibleNodeDeserializer), (Nothing _) => new YamlConvertibleNodeDeserializer(factory) }, { typeof(YamlSerializableNodeDeserializer), (Nothing _) => new YamlSerializableNodeDeserializer(factory) }, { typeof(TypeConverterNodeDeserializer), (Nothing _) => new TypeConverterNodeDeserializer(BuildTypeConverters()) }, { typeof(NullNodeDeserializer), (Nothing _) => new NullNodeDeserializer() }, { typeof(ScalarNodeDeserializer), (Nothing _) => new ScalarNodeDeserializer(attemptUnknownTypeDeserialization, typeConverter, yamlFormatter) }, { typeof(StaticArrayNodeDeserializer), (Nothing _) => new StaticArrayNodeDeserializer(factory) }, { typeof(StaticDictionaryNodeDeserializer), (Nothing _) => new StaticDictionaryNodeDeserializer(factory, duplicateKeyChecking) }, { typeof(StaticCollectionNodeDeserializer), (Nothing _) => new StaticCollectionNodeDeserializer(factory) }, { typeof(ObjectNodeDeserializer), (Nothing _) => new ObjectNodeDeserializer(factory, BuildTypeInspector(), ignoreUnmatched, duplicateKeyChecking, typeConverter) } }; nodeTypeResolverFactories = new LazyComponentRegistrationList<Nothing, INodeTypeResolver> { { typeof(MappingNodeTypeResolver), (Nothing _) => new MappingNodeTypeResolver(typeMappings) }, { typeof(YamlConvertibleTypeResolver), (Nothing _) => new YamlConvertibleTypeResolver() }, { typeof(YamlSerializableTypeResolver), (Nothing _) => new YamlSerializableTypeResolver() }, { typeof(TagNodeTypeResolver), (Nothing _) => new TagNodeTypeResolver(tagMappings) }, { typeof(PreventUnknownTagsNodeTypeResolver), (Nothing _) => new PreventUnknownTagsNodeTypeResolver() }, { typeof(DefaultContainersNodeTypeResolver), (Nothing _) => new DefaultContainersNodeTypeResolver() } }; typeConverter = new NullTypeConverter(); } internal ITypeInspector BuildTypeInspector() { ITypeInspector typeInspector = context.GetTypeInspector(); return typeInspectorFactories.BuildComponentChain(typeInspector); } public StaticDeserializerBuilder WithAttemptingUnquotedStringTypeDeserialization() { attemptUnknownTypeDeserialization = true; return this; } public StaticDeserializerBuilder WithNodeDeserializer(INodeDeserializer nodeDeserializer) { return WithNodeDeserializer(nodeDeserializer, delegate(IRegistrationLocationSelectionSyntax<INodeDeserializer> w) { w.OnTop(); }); } public StaticDeserializerBuilder WithNodeDeserializer(INodeDeserializer nodeDeserializer, Action<IRegistrationLocationSelectionSyntax<INodeDeserializer>> where) { if (nodeDeserializer == null) { throw new ArgumentNullException("nodeDeserializer"); } if (where == null) { throw new ArgumentNullException("where"); } where(nodeDeserializerFactories.CreateRegistrationLocationSelector(nodeDeserializer.GetType(), (Nothing _) => nodeDeserializer)); return this; } public StaticDeserializerBuilder WithNodeDeserializer<TNodeDeserializer>(WrapperFactory<INodeDeserializer, TNodeDeserializer> nodeDeserializerFactory, Action<ITrackingRegistrationLocationSelectionSyntax<INodeDeserializer>> where) where TNodeDeserializer : INodeDeserializer { if (nodeDeserializerFactory == null) { throw new ArgumentNullException("nodeDeserializerFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(nodeDeserializerFactories.CreateTrackingRegistrationLocationSelector(typeof(TNodeDeserializer), (INodeDeserializer wrapped, Nothing _) => nodeDeserializerFactory(wrapped))); return this; } public StaticDeserializerBuilder WithoutNodeDeserializer<TNodeDeserializer>() where TNodeDeserializer : INodeDeserializer { return WithoutNodeDeserializer(typeof(TNodeDeserializer)); } public StaticDeserializerBuilder WithoutNodeDeserializer(Type nodeDeserializerType) { if (nodeDeserializerType == null) { throw new ArgumentNullException("nodeDeserializerType"); } nodeDeserializerFactories.Remove(nodeDeserializerType); return this; } public StaticDeserializerBuilder WithTypeDiscriminatingNodeDeserializer(Action<ITypeDiscriminatingNodeDeserializerOptions> configureTypeDiscriminatingNodeDeserializerOptions, int maxDepth = -1, int maxLength = -1) { TypeDiscriminatingNodeDeserializerOptions typeDiscriminatingNodeDeserializerOptions = new TypeDiscriminatingNodeDeserializerOptions(); configureTypeDiscriminatingNodeDeserializerOptions(typeDiscriminatingNodeDeserializerOptions); TypeDiscriminatingNodeDeserializer nodeDeserializer = new TypeDiscriminatingNodeDeserializer(nodeDeserializerFactories.BuildComponentList(), typeDiscriminatingNodeDeserializerOptions.discriminators, maxDepth, maxLength); return WithNodeDeserializer(nodeDeserializer, delegate(IRegistrationLocationSelectionSyntax<INodeDeserializer> s) { s.Before<DictionaryNodeDeserializer>(); }); } public StaticDeserializerBuilder WithNodeTypeResolver(INodeTypeResolver nodeTypeResolver) { return WithNodeTypeResolver(nodeTypeResolver, delegate(IRegistrationLocationSelectionSyntax<INodeTypeResolver> w) { w.OnTop(); }); } public StaticDeserializerBuilder WithNodeTypeResolver(INodeTypeResolver nodeTypeResolver, Action<IRegistrationLocationSelectionSyntax<INodeTypeResolver>> where) { if (nodeTypeResolver == null) { throw new ArgumentNullException("nodeTypeResolver"); } if (where == null) { throw new ArgumentNullException("where"); } where(nodeTypeResolverFactories.CreateRegistrationLocationSelector(nodeTypeResolver.GetType(), (Nothing _) => nodeTypeResolver)); return this; } public StaticDeserializerBuilder WithNodeTypeResolver<TNodeTypeResolver>(WrapperFactory<INodeTypeResolver, TNodeTypeResolver> nodeTypeResolverFactory, Action<ITrackingRegistrationLocationSelectionSyntax<INodeTypeResolver>> where) where TNodeTypeResolver : INodeTypeResolver { if (nodeTypeResolverFactory == null) { throw new ArgumentNullException("nodeTypeResolverFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(nodeTypeResolverFactories.CreateTrackingRegistrationLocationSelector(typeof(TNodeTypeResolver), (INodeTypeResolver wrapped, Nothing _) => nodeTypeResolverFactory(wrapped))); return this; } public StaticDeserializerBuilder WithoutNodeTypeResolver<TNodeTypeResolver>() where TNodeTypeResolver : INodeTypeResolver { return WithoutNodeTypeResolver(typeof(TNodeTypeResolver)); } public StaticDeserializerBuilder WithoutNodeTypeResolver(Type nodeTypeResolverType) { if (nodeTypeResolverType == null) { throw new ArgumentNullException("nodeTypeResolverType"); } nodeTypeResolverFactories.Remove(nodeTypeResolverType); return this; } public override StaticDeserializerBuilder WithTagMapping(TagName tag, Type type) { if (tag.IsEmpty) { throw new ArgumentException("Non-specific tags cannot be maped"); } if (type == null) { throw new ArgumentNullException("type"); } if (tagMappings.TryGetValue(tag, out Type value)) { throw new ArgumentException($"Type already has a registered type '{value.FullName}' for tag '{tag}'", "tag"); } tagMappings.Add(tag, type); return this; } public StaticDeserializerBuilder WithTypeMapping<TInterface, TConcrete>() where TConcrete : TInterface { Type typeFromHandle = typeof(TInterface); Type typeFromHandle2 = typeof(TConcrete); if (!typeFromHandle.IsAssignableFrom(typeFromHandle2)) { throw new InvalidOperationException("The type '" + typeFromHandle2.Name + "' does not implement interface '" + typeFromHandle.Name + "'."); } if (typeMappings.ContainsKey(typeFromHandle)) { typeMappings[typeFromHandle] = typeFromHandle2; } else { typeMappings.Add(typeFromHandle, typeFromHandle2); } return this; } public StaticDeserializerBuilder WithoutTagMapping(TagName tag) { if (tag.IsEmpty) { throw new ArgumentException("Non-specific tags cannot be maped"); } if (!tagMappings.Remove(tag)) { throw new KeyNotFoundException($"Tag '{tag}' is not registered"); } return this; } public StaticDeserializerBuilder IgnoreUnmatchedProperties() { ignoreUnmatched = true; return this; } public StaticDeserializerBuilder WithDuplicateKeyChecking() { duplicateKeyChecking = true; return this; } public IDeserializer Build() { return Deserializer.FromValueDeserializer(BuildValueDeserializer()); } public IValueDeserializer BuildValueDeserializer() { return new AliasValueDeserializer(new NodeValueDeserializer(nodeDeserializerFactories.BuildComponentList(), nodeTypeResolverFactories.BuildComponentList(), typeConverter)); } } public sealed class StaticSerializerBuilder : StaticBuilderSkeleton<StaticSerializerBuilder> { private class ValueSerializer : IValueSerializer { private readonly IObjectGraphTraversalStrategy traversalStrategy; private readonly IEventEmitter eventEmitter; private readonly IEnumerable<IYamlTypeConverter> typeConverters; private readonly LazyComponentRegistrationList<IEnumerable<IYamlTypeConverter>, IObjectGraphVisitor<Nothing>> preProcessingPhaseObjectGraphVisitorFactories; private readonly LazyComponentRegistrationList<EmissionPhaseObjectGraphVisitorArgs, IObjectGraphVisitor<IEmitter>> emissionPhaseObjectGraphVisitorFactories; public ValueSerializer(IObjectGraphTraversalStrategy traversalStrategy, IEventEmitter eventEmitter, IEnumerable<IYamlTypeConverter> typeConverters, LazyComponentRegistrationList<IEnumerable<IYamlTypeConverter>, IObjectGraphVisitor<Nothing>> preProcessingPhaseObjectGraphVisitorFactories, LazyComponentRegistrationList<EmissionPhaseObjectGraphVisitorArgs, IObjectGraphVisitor<IEmitter>> emissionPhaseObjectGraphVisitorFactories) { this.traversalStrategy = traversalStrategy; this.eventEmitter = eventEmitter; this.typeConverters = typeConverters; this.preProcessingPhaseObjectGraphVisitorFactories = preProcessingPhaseObjectGraphVisitorFactories; this.emissionPhaseObjectGraphVisitorFactories = emissionPhaseObjectGraphVisitorFactories; } public void SerializeValue(IEmitter emitter, object? value, Type? type) { Type type2 = type ?? ((value != null) ? value.GetType() : typeof(object)); Type staticType = type ?? typeof(object); ObjectDescriptor graph = new ObjectDescriptor(value, type2, staticType); List<IObjectGraphVisitor<Nothing>> preProcessingPhaseObjectGraphVisitors = preProcessingPhaseObjectGraphVisitorFactories.BuildComponentList(typeConverters); foreach (IObjectGraphVisitor<Nothing> item in preProcessingPhaseObjectGraphVisitors) { traversalStrategy.Traverse(graph, item, default(Nothing)); } IObjectGraphVisitor<IEmitter> visitor = emissionPhaseObjectGraphVisitorFactories.BuildComponentChain<EmissionPhaseObjectGraphVisitorArgs, IObjectGraphVisitor<IEmitter>>(new EmittingObjectGraphVisitor(eventEmitter), (IObjectGraphVisitor<IEmitter> inner) => new EmissionPhaseObjectGraphVisitorArgs(inner, eventEmitter, preProcessingPhaseObjectGraphVisitors, typeConverters, NestedObjectSerializer)); traversalStrategy.Traverse(graph, visitor, emitter); void NestedObjectSerializer(object? v, Type? t) { SerializeValue(emitter, v, t); } } } private readonly StaticContext context; private readonly StaticObjectFactory factory; private ObjectGraphTraversalStrategyFactory objectGraphTraversalStrategyFactory; private readonly LazyComponentRegistrationList<IEnumerable<IYamlTypeConverter>, IObjectGraphVisitor<Nothing>> preProcessingPhaseObjectGraphVisitorFactories; private readonly LazyComponentRegistrationList<EmissionPhaseObjectGraphVisitorArgs, IObjectGraphVisitor<IEmitter>> emissionPhaseObjectGraphVisitorFactories; private readonly LazyComponentRegistrationList<IEventEmitter, IEventEmitter> eventEmitterFactories; private readonly IDictionary<Type, TagName> tagMappings = new Dictionary<Type, TagName>(); private int maximumRecursion = 50; private EmitterSettings emitterSettings = EmitterSettings.Default; private DefaultValuesHandling defaultValuesHandlingConfiguration; private bool quoteNecessaryStrings; private bool quoteYaml1_1Strings; private ScalarStyle defaultScalarStyle; protected override StaticSerializerBuilder Self => this; public StaticSerializerBuilder(StaticContext context) : base((ITypeResolver)new DynamicTypeResolver()) { this.context = context; factory = context.GetFactory(); typeInspectorFactories.Add(typeof(CachedTypeInspector), (ITypeInspector inner) => new CachedTypeInspector(inner)); typeInspectorFactories.Add(typeof(NamingConventionTypeInspector), (ITypeInspector inner) => (!(namingConvention is NullNamingConvention)) ? new NamingConventionTypeInspector(inner, namingConvention) : inner); typeInspectorFactories.Add(typeof(YamlAttributesTypeInspector), (ITypeInspector inner) => new YamlAttributesTypeInspector(inner)); preProcessingPhaseObjectGraphVisitorFactories = new LazyComponentRegistrationList<IEnumerable<IYamlTypeConverter>, IObjectGraphVisitor<Nothing>> { { typeof(AnchorAssigner), (IEnumerable<IYamlTypeConverter> typeConverters) => new AnchorAssigner(typeConverters) } }; emissionPhaseObjectGraphVisitorFactories = new LazyComponentRegistrationList<EmissionPhaseObjectGraphVisitorArgs, IObjectGraphVisitor<IEmitter>> { { typeof(CustomSerializationObjectGraphVisitor), (EmissionPhaseObjectGraphVisitorArgs args) => new CustomSerializationObjectGraphVisitor(args.InnerVisitor, args.TypeConverters, args.NestedObjectSerializer) }, { typeof(AnchorAssigningObjectGraphVisitor), (EmissionPhaseObjectGraphVisitorArgs args) => new AnchorAssigningObjectGraphVisitor(args.InnerVisitor, args.EventEmitter, args.GetPreProcessingPhaseObjectGraphVisitor<AnchorAssigner>()) }, { typeof(DefaultValuesObjectGraphVisitor), (EmissionPhaseObjectGraphVisitorArgs args) => new DefaultValuesObjectGraphVisitor(defaultValuesHandlingConfiguration, args.InnerVisitor, factory) }, { typeof(CommentsObjectGraphVisitor), (EmissionPhaseObjectGraphVisitorArgs args) => new CommentsObjectGraphVisitor(args.InnerVisitor) } }; eventEmitterFactories = new LazyComponentRegistrationList<IEventEmitter, IEventEmitter> { { typeof(TypeAssigningEventEmitter), (IEventEmitter inner) => new TypeAssigningEventEmitter(inner, requireTagWhenStaticAndActualTypesAreDifferent: false, tagMappings, quoteNecessaryStrings, quoteYaml1_1Strings, defaultScalarStyle, yamlFormatter) } }; objectGraphTraversalStrategyFactory = (ITypeInspector typeInspector, ITypeResolver typeResolver, IEnumerable<IYamlTypeConverter> typeConverters, int maximumRecursion) => new FullObjectGraphTraversalStrategy(typeInspector, typeResolver, maximumRecursion, namingConvention, factory); } public StaticSerializerBuilder WithQuotingNecessaryStrings(bool quoteYaml1_1Strings = false) { quoteNecessaryStrings = true; this.quoteYaml1_1Strings = quoteYaml1_1Strings; return this; } public StaticSerializerBuilder WithQuotingNecessaryStrings() { quoteNecessaryStrings = true; return this; } public StaticSerializerBuilder WithDefaultScalarStyle(ScalarStyle style) { defaultScalarStyle = style; return this; } public StaticSerializerBuilder WithMaximumRecursion(int maximumRecursion) { if (maximumRecursion <= 0) { throw new ArgumentOutOfRangeException("maximumRecursion", $"The maximum recursion specified ({maximumRecursion}) is invalid. It should be a positive integer."); } this.maximumRecursion = maximumRecursion; return this; } public StaticSerializerBuilder WithEventEmitter<TEventEmitter>(Func<IEventEmitter, TEventEmitter> eventEmitterFactory) where TEventEmitter : IEventEmitter { return WithEventEmitter(eventEmitterFactory, delegate(IRegistrationLocationSelectionSyntax<IEventEmitter> w) { w.OnTop(); }); } public StaticSerializerBuilder WithEventEmitter<TEventEmitter>(Func<IEventEmitter, TEventEmitter> eventEmitterFactory, Action<IRegistrationLocationSelectionSyntax<IEventEmitter>> where) where TEventEmitter : IEventEmitter { if (eventEmitterFactory == null) { throw new ArgumentNullException("eventEmitterFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(eventEmitterFactories.CreateRegistrationLocationSelector(typeof(TEventEmitter), (IEventEmitter inner) => eventEmitterFactory(inner))); return Self; } public StaticSerializerBuilder WithEventEmitter<TEventEmitter>(WrapperFactory<IEventEmitter, IEventEmitter, TEventEmitter> eventEmitterFactory, Action<ITrackingRegistrationLocationSelectionSyntax<IEventEmitter>> where) where TEventEmitter : IEventEmitter { if (eventEmitterFactory == null) { throw new ArgumentNullException("eventEmitterFactory"); } if (where == null) { throw new ArgumentNullException("where"); } where(eventEmitterFactories.CreateTrackingRegistrationLocationSelector(typeof(TEventEmitter), (IEventEmitter wrapped, IEventEmitter inner) => eventEmitterFactory(wrapped, inner))); return Self; } public StaticSerializerBuilder WithoutEventEmitter<TEventEmitter>() where TEventEmitter : IEventEmitter { return WithoutEventEmitter(typeof(TEventEmitter)); } public StaticSerializerBuilder WithoutEventEmitter(Type eventEmitterType) { if (eventEmitterType == null) { throw new ArgumentNullException("eventEmitterType"); } eventEmitterFactories.Remove(eventEmitterType); return this; } public override StaticSerializerBuilder WithTagMapping(TagName tag, Type type) { if (tag.IsEmpty) { throw new ArgumentException("Non-specific tags cannot be maped"); } if (type == null) { throw new ArgumentNullException("type"); } if (tagMappings.TryGetValue(type, out var value)) { throw new ArgumentException($"Type already has a registered tag '{value}' for type '{type.FullName}'", "type"); } tagMappings.Add(type, tag); return this; } public StaticSerializerBuilder WithoutTagMapping(Type type) { if (type == null) { throw new ArgumentNullException("type"); } if (!tagMappings.Remove(type)) { throw new KeyNotFoundException("Tag for type '" + type.FullName + "' is not registered"); } return this; } public StaticSerializerBuilder EnsureRoundtrip() { objectGraphTraversalStrategyFactory = (ITypeInspector typeInspector, ITypeResolver typeResolver, IEnumerable<IYamlTypeConverter> typeConverters, int maximumRecursion) => new RoundtripObjectGraphTraversalStrategy(typeConverters, typeInspector, typeResolver, maximumRecursion, namingConvention, settings, factory); WithEventEmitter((IEventEmitter inner) => new TypeAssigningEventEmitter(inner, requireTagWhenStaticAndActualTypesAreDifferent: true, tagMappings, quoteNecessaryStrings), delegate(IRegistrationLocationSelectionSyntax<IEventEmitter> loc) { loc.InsteadOf<TypeAssigningEventEmitter>(); }); return WithTypeInspector((ITypeInspector inner) => new ReadableAndWritablePropertiesTypeInspector(inner), delegate(IRegistrationLocationSelectionSyntax<ITypeInspector> loc) { loc.OnBottom(); }); } public StaticSerializerBuilder DisableAliases() { preProcessingPhaseObjectGraphVisitorFactories.Remove(typeof(AnchorAssigner)); emissionPhaseObjectGraphVisitorFactories.Remove(typeof(AnchorAssigningObjectGraphVisitor)); return this; } [Obsolete("The default behavior is now to always emit default values, thefore calling this method has no effect. This behavior is now controlled by ConfigureDefaultValuesHandling.", true)] public StaticSerializerBuilder EmitDefaults() { return ConfigureDefaultValuesHandling(DefaultValuesHandling.Preserve); } public StaticSerializerBuilder ConfigureDefaultValuesHandling(DefaultValuesHandling configuration) { defaultValuesHandlingConfiguration = configuration; return this; } public StaticSerializerBuilder JsonCompatible() { emitterSettings = emitterSettings.WithMaxSimpleKeyLength(int.MaxValue).WithoutAnchorName(); return WithTypeConverter(new YamlDotNet.Serialization.Converters.GuidConverter(jsonCompatible: true), delegate(IRegistrationLocationSelectionSyntax<IYamlTypeConverter> w) { w.InsteadOf<YamlDotNet.Serialization.Converters.GuidConverter>(); }).WithTypeConverter(new YamlDotNet.Serialization.Converters.DateTimeConverter(DateTimeKind.Utc, null, true)).WithEventEmitter((IEventEmitter inner) => new JsonEventEmitter(inner, yamlFormatter), delegate(IRegistrationLocationSelectionSyntax<IEventEmitter> loc) { loc.InsteadOf<TypeAssigningEventEmitter>(); }); } public StaticSerializerBuilder WithNewLine(string newLine) { emitterSettings = emitterSettings.WithNewLine(newLine); return this; } public StaticSerializerBuilder WithPreProcessingPhaseObjectGraphVisitor<TObjectGraphVisitor>(TObjectGraphVisitor objectGraphVisitor) where TObjectGraphVisitor : IObjectGraphVisitor<Nothing> { return WithPreProcessingPhaseObjectGraphVisitor(objectGraphVisitor, delegate(IRegistrationLocationSelectionSyntax<IObjectGraphVisitor<Nothing>> w) { w.OnTop(); }); } public StaticSerializerBuilder WithPreProcessingPhaseObjectGraphVisitor<TObjectGraphVisitor>(TObjectGraphVisitor objectGraphVisitor, A