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 YAVSOS v1.0.0
plugins/YAVSOS.Core.dll
Decompiled 13 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization.Json; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; using Microsoft.CodeAnalysis; using YAVSOS.Core.Catalog; using YAVSOS.Core.Classification; using YAVSOS.Core.Interface; using YAVSOS.Core.Policy; using YAVSOS.Core.Transfers; [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("BastionNtB")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (C) 2026 BastionNtB")] [assembly: AssemblyDescription("Game-independent domain and policy layer for YAVSOS.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+d5aaa1b9ccbb9dd00e376b293e718c38397c8336")] [assembly: AssemblyProduct("Yet Another Valheim Storage Organization System")] [assembly: AssemblyTitle("YAVSOS.Core")] [assembly: AssemblyVersion("1.0.0.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 YAVSOS.Core { public static class ProjectIdentity { public const string Guid = "com.bastionntb.yavsos"; public const string Name = "Yet Another Valheim Storage Organization System"; public const string Version = "1.0.0"; public const string MinimumManagedVersion = "1.0.0"; } } namespace YAVSOS.Core.Transfers { public sealed class CleanupChest { public string Id { get; } public string RulePayload { get; } public TransferInventorySnapshot Inventory { get; } public bool LocallyOwned { get; } public bool OptOut { get; } public double Distance { get; } public CleanupChest(string id, string rulePayload, TransferInventorySnapshot inventory, bool locallyOwned, bool optOut = false, double distance = 0.0) { Id = TransferCanonical.Text(id, 256, empty: false); RulePayload = rulePayload ?? throw new ArgumentNullException("rulePayload"); Inventory = inventory ?? throw new ArgumentNullException("inventory"); if (double.IsNaN(distance) || distance < 0.0) { throw new ArgumentOutOfRangeException("distance"); } LocallyOwned = locallyOwned; OptOut = optOut; Distance = distance; } } public sealed class CleanupInput { public ClassificationTaxonomy Taxonomy { get; } public ICategoryAnswers Answers { get; } public IReadOnlyList<CleanupChest> Chests { get; } public Func<string, int>? CategorySize { get; } public bool MucAvailable { get; } public Func<string, TransferItemIdentity, bool>? AzuAllows { get; } public IReadOnlyCollection<Guid> Held { get; } public Func<TransferStack, IReadOnlyList<FacetEvidence>>? InstanceFacets { get; } public CleanupInput(ClassificationTaxonomy taxonomy, ICategoryAnswers answers, IEnumerable<CleanupChest> chests, Func<string, int>? categorySize = null, bool mucAvailable = false, Func<string, TransferItemIdentity, bool>? azuAllows = null, IEnumerable<Guid>? held = null, Func<TransferStack, IReadOnlyList<FacetEvidence>>? instanceFacets = null) { InstanceFacets = instanceFacets; Taxonomy = taxonomy ?? throw new ArgumentNullException("taxonomy"); Answers = answers ?? throw new ArgumentNullException("answers"); Chests = Array.AsReadOnly((chests ?? throw new ArgumentNullException("chests")).ToArray()); if (Chests.Any((CleanupChest x) => x == null)) { throw new ArgumentException("Null cleanup chest."); } if (Chests.Select((CleanupChest x) => x.Id).Distinct<string>(StringComparer.Ordinal).Count() != Chests.Count) { throw new ArgumentException("Duplicate cleanup chest id."); } if (Chests.Select((CleanupChest x) => x.Inventory.Stamp.InventoryId).Distinct<string>(StringComparer.Ordinal).Count() != Chests.Count) { throw new ArgumentException("Two cleanup chests share one inventory."); } CategorySize = categorySize; MucAvailable = mucAvailable; AzuAllows = azuAllows; Held = new HashSet<Guid>(held ?? Array.Empty<Guid>()); } } public enum CleanupRoute { Direct, MucAdd, MucRemove } public enum CleanupKind { Rejected, Unruled, Resort } public enum CleanupReason { NoDestination, NoCapacity, AzuExcluded, OtherOwnerNoMuc, BothRemote, OtherOwnerFormatUnknown, MucBusy, Unsupported, Held, Budget, ChestLimit, CategoriesPending } public sealed class CleanupMove { public string SourceId { get; } public string DestinationId { get; } public Guid Handle { get; } public TransferItemIdentity Identity { get; } public int Units { get; } public int DestinationX { get; } public int DestinationY { get; } public Guid? MergeHandle { get; } public CleanupRoute Route { get; } public CleanupKind Kind { get; } public bool MergesPlanned { get; } internal CleanupMove(string source, string destination, Guid handle, TransferItemIdentity identity, int units, int x, int y, Guid? merge, CleanupRoute route, CleanupKind kind, bool mergesPlanned = false) { MergesPlanned = mergesPlanned; SourceId = source; DestinationId = destination; Handle = handle; Identity = identity; Units = units; DestinationX = x; DestinationY = y; MergeHandle = merge; Route = route; Kind = kind; } public bool TryMergeTarget(TransferInventorySnapshot source, TransferInventorySnapshot destination, out Guid? merge) { return TransferGrid.TryMergeTarget(MergesPlanned, MergeHandle, source, Handle, destination, DestinationX, DestinationY, out merge); } } public sealed class CleanupRemainder { public string SourceId { get; } public Guid Handle { get; } public TransferItemIdentity Identity { get; } public int Units { get; } public CleanupReason Reason { get; } public string? DestinationId { get; } internal CleanupRemainder(string source, Guid handle, TransferItemIdentity identity, int units, CleanupReason reason, string? destination) { SourceId = source; Handle = handle; Identity = identity; Units = units; Reason = reason; DestinationId = destination; } } public sealed class CleanupPlan { public string PolicyStamp { get; } public bool CategoriesPending { get; } public IReadOnlyList<CleanupMove> Moves { get; } public IReadOnlyList<CleanupRemainder> Remainders { get; } public int SkippedChests { get; } internal CleanupPlan(string stamp, bool pending, IReadOnlyList<CleanupMove> moves, IReadOnlyList<CleanupRemainder> remainders, int skipped) { PolicyStamp = stamp; CategoriesPending = pending; Moves = moves; Remainders = remainders; SkippedChests = skipped; } } public static class CleanupPlanner { internal sealed class Destination { internal readonly CleanupChest Chest; internal readonly RankedDestination Ranked; internal readonly CleanupGate Gate; internal readonly CleanupRoute Via; internal readonly CleanupReason? Blocked; internal Destination(CleanupChest chest, RankedDestination ranked, CleanupGate gate, CleanupRoute via = CleanupRoute.Direct, CleanupReason? blocked = null) { Chest = chest; Ranked = ranked; Gate = gate; Via = via; Blocked = blocked; } } internal sealed class Assessment { internal ChestRuleDecision Decision; internal CleanupKind? Kind; internal ClassificationResult? Item; internal bool Stays; internal RankedDestination? Current; internal List<Destination> Destinations = new List<Destination>(); } public const int MaximumChests = 256; public const int MaximumSources = 64; public const int MaximumMoves = 512; public static CleanupPlan Plan(CleanupInput input, ChestRuleEvaluator? evaluator = null) { if (input == null) { throw new ArgumentNullException("input"); } if (evaluator == null) { evaluator = new ChestRuleEvaluator(); } ClassificationTaxonomy taxonomy = input.Taxonomy; ICategoryAnswers answers = input.Answers; string stamp = taxonomy.Fingerprint + ":" + answers.Generation; List<CleanupMove> list = new List<CleanupMove>(); List<CleanupRemainder> list2 = new List<CleanupRemainder>(); if (!answers.CategoriesReady) { return new CleanupPlan(stamp, pending: true, list.AsReadOnly(), list2.AsReadOnly(), 0); } List<CleanupChest> list3 = Considered(input.Chests); int num = input.Chests.Count - list3.Count; HashSet<string> hashSet = Sources(list3); Dictionary<string, TransferGrid> dictionary = list3.ToDictionary<CleanupChest, string, TransferGrid>((CleanupChest cleanupChest2) => cleanupChest2.Id, (CleanupChest cleanupChest2) => new TransferGrid(cleanupChest2.Inventory, mergePlanned: true), StringComparer.Ordinal); List<(CleanupChest, DestinationCandidate)> ruled = Receivers(list3, evaluator, taxonomy); HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal); foreach (CleanupChest item in list3) { if (item.OptOut || item.Inventory.Stacks.Count == 0) { continue; } if (!hashSet.Contains(item.Id)) { num++; continue; } TransferGrid transferGrid = dictionary[item.Id]; foreach (TransferStack stack in item.Inventory.Stacks) { Assessment assessment = Assess(input, evaluator, ruled, item, stack); if (!assessment.Kind.HasValue) { continue; } CleanupKind value = assessment.Kind.Value; if (assessment.Item == null && assessment.Destinations.Count == 0 && !assessment.Stays) { if (value == CleanupKind.Rejected) { list2.Add(new CleanupRemainder(item.Id, stack.Handle, stack.Identity, stack.Quantity, CleanupReason.NoDestination, null)); } } else { if (assessment.Stays) { continue; } List<(CleanupChest, CleanupRoute)> list4 = (from destination in assessment.Destinations where destination.Gate == CleanupGate.Usable select (Chest: destination.Chest, Route: destination.Via)).ToList(); CleanupReason? cleanupReason = assessment.Destinations.FirstOrDefault((Destination destination) => destination.Gate == CleanupGate.Blocked)?.Blocked; string text = assessment.Destinations.FirstOrDefault((Destination destination) => destination.Blocked == CleanupReason.AzuExcluded)?.Chest.Id; if (list4.Count == 0) { CleanupReason valueOrDefault = cleanupReason.GetValueOrDefault(); if (value != CleanupKind.Resort && (cleanupReason.HasValue || value == CleanupKind.Rejected)) { list2.Add(new CleanupRemainder(item.Id, stack.Handle, stack.Identity, stack.Quantity, valueOrDefault, (valueOrDefault == CleanupReason.AzuExcluded) ? text : null)); } continue; } if (stack.Protected || !stack.TransferSupported || item.Inventory.ProtectedCells.Contains(stack.Y * item.Inventory.Width + stack.X)) { list2.Add(new CleanupRemainder(item.Id, stack.Handle, stack.Identity, stack.Quantity, CleanupReason.Unsupported, null)); continue; } if (input.Held.Contains(stack.Handle)) { list2.Add(new CleanupRemainder(item.Id, stack.Handle, stack.Identity, stack.Quantity, CleanupReason.Held, null)); continue; } int num2 = stack.Quantity; bool flag = false; foreach (var (cleanupChest, cleanupRoute) in list4) { if (num2 == 0) { break; } string text2 = cleanupRoute switch { CleanupRoute.MucRemove => item.Id, CleanupRoute.MucAdd => cleanupChest.Id, _ => null, }; if (text2 != null && hashSet2.Contains(text2)) { flag = true; continue; } TransferGrid transferGrid2 = dictionary[cleanupChest.Id]; int x; int y; Guid? merge; int room; bool planned; while (num2 > 0 && list.Count < 512 && transferGrid2.TryFind(stack.MergeKey, stack.MaximumStack, out x, out y, out merge, out room, out planned)) { int num3 = Math.Min(num2, room); transferGrid2.Add(x, y, stack.MergeKey, stack.MaximumStack, num3); transferGrid.Remove(stack.X, stack.Y, num3); list.Add(new CleanupMove(item.Id, cleanupChest.Id, stack.Handle, stack.Identity, num3, x, y, merge, cleanupRoute, value, planned)); num2 -= num3; if (text2 != null) { hashSet2.Add(text2); break; } } if (list.Count >= 512) { break; } } if (num2 > 0) { CleanupReason cleanupReason2 = ((list.Count >= 512) ? CleanupReason.Budget : ((!flag) ? CleanupReason.NoCapacity : CleanupReason.MucBusy)); if (value != CleanupKind.Resort || cleanupReason2 == CleanupReason.Budget) { list2.Add(new CleanupRemainder(item.Id, stack.Handle, stack.Identity, num2, cleanupReason2, null)); } } } } } return new CleanupPlan(stamp, pending: false, list.AsReadOnly(), list2.AsReadOnly(), num); } public static bool ResortStillBetter(ClassificationResult? item, string prefab, ChestRules source, ChestRules destination, IEnumerable<FacetEvidence>? facets, ClassificationTaxonomy taxonomy) { if (source == null || destination == null || source.State != ChestRuleState.Configured || destination.State != ChestRuleState.Configured) { return false; } DestinationEligibility eligibility = new DestinationEligibility(accessible: true, hasCapacity: true, protectionAllows: true); DestinationCandidate[] candidates = new DestinationCandidate[1] { new DestinationCandidate("source", source, eligibility) }; DestinationCandidate[] candidates2 = new DestinationCandidate[1] { new DestinationCandidate("destination", destination, eligibility) }; FacetEvidence[] currentInstanceFacets = facets?.ToArray(); RankedDestination rankedDestination = ((item == null) ? DestinationRouting.ExactOnly(prefab, candidates).FirstOrDefault() : DestinationRouting.Rank(item, prefab, candidates, currentInstanceFacets, taxonomy).FirstOrDefault()); RankedDestination rankedDestination2 = ((item == null) ? DestinationRouting.ExactOnly(prefab, candidates2).FirstOrDefault() : DestinationRouting.Rank(item, prefab, candidates2, currentInstanceFacets, taxonomy).FirstOrDefault()); if (rankedDestination != null && rankedDestination2 != null) { return Better(rankedDestination2, rankedDestination); } return false; } public static bool Better(RankedDestination candidate, RankedDestination current) { int num = Compare<bool>(current.Candidate.Rules.Overflow, candidate.Candidate.Rules.Overflow); if (num != 0) { return num > 0; } num = Compare<int>(candidate.Candidate.Rules.Priority, current.Candidate.Rules.Priority); if (num != 0) { return num > 0; } num = Compare<int>((int)candidate.Match, (int)current.Match); if (num != 0) { return num > 0; } return Compare<int>(current.Generality, candidate.Generality) > 0; static int Compare<T>(T a, T b) where T : notnull, IComparable<T> { return a.CompareTo(b); } } internal static List<(CleanupChest Chest, DestinationCandidate Candidate)> Receivers(IEnumerable<CleanupChest> chests, ChestRuleEvaluator evaluator, ClassificationTaxonomy taxonomy) { List<(CleanupChest, DestinationCandidate)> list = new List<(CleanupChest, DestinationCandidate)>(); foreach (CleanupChest chest in chests) { DestinationCandidate destinationCandidate = StoreOrder.Candidate(chest.Id, chest.RulePayload, evaluator, taxonomy); if (destinationCandidate != null) { list.Add((chest, destinationCandidate)); } } return list; } internal static List<CleanupChest> Considered(IEnumerable<CleanupChest> chests) { return chests.OrderBy((CleanupChest x) => x.Distance).ThenBy<CleanupChest, string>((CleanupChest x) => x.Id, StringComparer.Ordinal).Take(256) .OrderBy<CleanupChest, string>((CleanupChest x) => x.Id, StringComparer.Ordinal) .ToList(); } internal static HashSet<string> Sources(IEnumerable<CleanupChest> considered) { return new HashSet<string>(from x in (from x in considered where !x.OptOut && x.Inventory.Stacks.Count != 0 orderby x.Distance select x).ThenBy<CleanupChest, string>((CleanupChest x) => x.Id, StringComparer.Ordinal).Take(64) select x.Id, StringComparer.Ordinal); } internal static Assessment Assess(CleanupInput input, ChestRuleEvaluator evaluator, List<(CleanupChest Chest, DestinationCandidate Candidate)> ruled, CleanupChest source, TransferStack stack) { ClassificationTaxonomy taxonomy = input.Taxonomy; ICategoryAnswers answers = input.Answers; string prefab = stack.Identity.Prefab; IReadOnlyList<FacetEvidence> facets = input.InstanceFacets?.Invoke(stack); Assessment result = new Assessment { Decision = evaluator.Evaluate(source.RulePayload, prefab, taxonomy, answers, facets) }; Assessment assessment = result; assessment.Kind = result.Decision switch { ChestRuleDecision.Reject => CleanupKind.Rejected, ChestRuleDecision.NoRule => CleanupKind.Unruled, ChestRuleDecision.Accept => CleanupKind.Resort, _ => null, }; if (!result.Kind.HasValue) { return result; } ClassificationResult classificationResult = (result.Item = answers.Resolve(prefab)); if (classificationResult == null) { if (result.Kind == CleanupKind.Resort) { DestinationCandidate item = ruled.FirstOrDefault<(CleanupChest, DestinationCandidate)>(((CleanupChest Chest, DestinationCandidate Candidate) x) => x.Chest == source).Item2; result.Current = ((item == null) ? null : DestinationRouting.ExactOnly(prefab, new DestinationCandidate[1] { item }).FirstOrDefault()); if (result.Current == null) { result.Stays = true; return result; } } foreach (RankedDestination item3 in DestinationRouting.ExactOnly(prefab, from x in ruled where x.Chest != source select x.Candidate)) { AddDestination(item3); } return result; } if (result.Kind == CleanupKind.Resort) { DestinationCandidate item2 = ruled.FirstOrDefault<(CleanupChest, DestinationCandidate)>(((CleanupChest Chest, DestinationCandidate Candidate) x) => x.Chest == source).Item2; result.Current = ((item2 == null) ? null : DestinationRouting.Rank(classificationResult, prefab, new DestinationCandidate[1] { item2 }, facets, taxonomy).FirstOrDefault()); if (result.Current == null) { result.Stays = true; return result; } } foreach (RankedDestination item4 in DestinationRouting.Rank(classificationResult, prefab, from x in ruled where x.Chest != source select x.Candidate, facets, taxonomy, input.CategorySize)) { AddDestination(item4); } return result; void AddDestination(RankedDestination row) { CleanupChest chest; if (!row.ExistingItemFallback) { chest = ruled.First<(CleanupChest, DestinationCandidate)>(((CleanupChest Chest, DestinationCandidate Candidate) x) => x.Candidate == row.Candidate).Item1; if (result.Current != null && !Better(row, result.Current)) { result.Destinations.Add(Gate(CleanupGate.NotBetter)); } else if (evaluator.Evaluate(chest.RulePayload, prefab, taxonomy, answers, facets) != ChestRuleDecision.Accept) { result.Destinations.Add(Gate(CleanupGate.RuleMismatch)); } else if (input.AzuAllows != null && !input.AzuAllows(chest.Id, stack.Identity)) { result.Destinations.Add(Gate(CleanupGate.Blocked, CleanupReason.AzuExcluded)); } else if (source.LocallyOwned && chest.LocallyOwned) { result.Destinations.Add(Gate(CleanupGate.Usable)); } else if (!source.LocallyOwned && !chest.LocallyOwned) { result.Destinations.Add(Gate(CleanupGate.Blocked, CleanupReason.BothRemote)); } else if (!input.MucAvailable) { result.Destinations.Add(Gate(CleanupGate.Blocked, CleanupReason.OtherOwnerNoMuc)); } else { List<Destination> destinations = result.Destinations; CleanupRoute via = (source.LocallyOwned ? CleanupRoute.MucAdd : CleanupRoute.MucRemove); destinations.Add(Gate(CleanupGate.Usable, null, via)); } } Destination Gate(CleanupGate gate, CleanupReason? reason = null, CleanupRoute via2 = CleanupRoute.Direct) { return new Destination(chest, row, gate, via2, reason); } } } } internal enum CleanupGate { Usable, NotBetter, RuleMismatch, Blocked } public sealed class CleanupReport { private sealed class Tally { internal string Item = ""; internal int Stacks; internal int Units; } private readonly Dictionary<CleanupReason, int> remaining = new Dictionary<CleanupReason, int>(); private readonly Dictionary<TransferRefusal, int> refused = new Dictionary<TransferRefusal, int>(); private readonly string title; private readonly string verb; private readonly string left; public const int MaximumNamesPerLine = 16; private readonly Dictionary<(ResultKind Kind, int Reason), List<Tally>> names = new Dictionary<(ResultKind, int), List<Tally>>(); private static readonly CleanupReason[] Left = new CleanupReason[9] { CleanupReason.NoCapacity, CleanupReason.NoDestination, CleanupReason.AzuExcluded, CleanupReason.Held, CleanupReason.Unsupported, CleanupReason.MucBusy, CleanupReason.Budget, CleanupReason.ChestLimit, CleanupReason.CategoriesPending }; private static readonly CleanupReason[] Skipped = new CleanupReason[3] { CleanupReason.OtherOwnerNoMuc, CleanupReason.BothRemote, CleanupReason.OtherOwnerFormatUnknown }; public int MovedStacks { get; private set; } public int MovedUnits { get; private set; } public int MucMovedStacks { get; private set; } public int Unconfirmed { get; private set; } public int MucUnconfirmed { get; private set; } public int Refused => refused.Values.Sum(); public int Remaining => remaining.Values.Sum(); public int ChestsInRange { get; private set; } public int ChestsHandled { get; private set; } public int Count(CleanupReason reason) { if (!remaining.TryGetValue(reason, out var value)) { return 0; } return value; } public int Count(TransferRefusal reason) { if (!refused.TryGetValue(reason, out var value)) { return 0; } return value; } public CleanupReport() : this("Cleanup", "moved", "left in place") { } private CleanupReport(string title, string verb, string left) { this.title = title; this.verb = verb; this.left = left; } public static CleanupReport ForStash() { return new CleanupReport("Stash", "stored", "left in your inventory"); } public void Range(int inRange, int handled) { if (inRange < 0 || handled < 0 || handled > inRange) { throw new ArgumentOutOfRangeException("handled"); } ChestsInRange = inRange; ChestsHandled = handled; } public void Remain(CleanupReason reason, string? item = null) { remaining[reason] = Count(reason) + 1; Name(ResultKind.Remaining, (int)reason, item, 1); } public void Refuse(TransferRefusal reason, string? item = null) { if (reason == TransferRefusal.None || reason == TransferRefusal.Unconfirmed) { throw new ArgumentException("Not a refusal.", "reason"); } refused[reason] = Count(reason) + 1; Name(ResultKind.Refused, (int)reason, item, 1); } public void Moved(int units, bool viaMuc = false, string? item = null) { if (units < 1) { throw new ArgumentOutOfRangeException("units"); } MovedStacks++; MovedUnits += units; if (viaMuc) { MucMovedStacks++; } Name(ResultKind.Moved, 0, item, units); } public void Unconfirm(bool viaMuc = false, string? item = null) { Unconfirmed++; if (viaMuc) { MucUnconfirmed++; } Name(ResultKind.Unconfirmed, 0, item, 1); } private void Name(ResultKind kind, int reason, string? item, int units) { if (item == null) { return; } if (!names.TryGetValue((kind, reason), out List<Tally> value)) { names.Add((kind, reason), value = new List<Tally>()); } Tally tally = value.Find((Tally x) => StringComparer.Ordinal.Equals(x.Item, item)); if (tally == null) { if (value.Count == 16) { return; } value.Add(tally = new Tally { Item = item }); } tally.Stacks++; tally.Units += units; } public ResultSummary Detail(Func<string, string>? itemName = null, int maximumLines = 8, int namesPerLine = 4) { if (maximumLines < 1) { throw new ArgumentOutOfRangeException("maximumLines"); } if (namesPerLine < 0) { throw new ArgumentOutOfRangeException("namesPerLine"); } List<ResultLine> lines = new List<ResultLine>(); Add(ResultKind.Moved, 0, MovedStacks, $"{verb} {MovedStacks} stack{Plural(MovedStacks)} ({MovedUnits} item{Plural(MovedUnits)})" + ((MucMovedStacks == 0) ? "" : $", {MucMovedStacks} via MultiUserChest")); foreach (CleanupReason item in Left.Concat(Skipped)) { Add(ResultKind.Remaining, (int)item, Count(item), Count(item) + " " + (Skipped.Contains(item) ? "skipped" : left) + " (" + Describe(item) + ")"); } foreach (KeyValuePair<TransferRefusal, int> item2 in refused.OrderBy((KeyValuePair<TransferRefusal, int> x) => x.Key)) { Add(ResultKind.Refused, (int)item2.Key, item2.Value, item2.Value + " refused (" + Describe(item2.Key) + ")"); } Add(ResultKind.Unconfirmed, 0, Unconfirmed, Unconfirmed + " unconfirmed" + ((MucUnconfirmed == 0) ? "" : $" ({MucUnconfirmed} via MultiUserChest)") + ", not retried"); List<ResultLine> list = lines.Take(maximumLines).ToList(); int more = lines.Count - list.Count; return new ResultSummary(title, Summary(), list, more); void Add(ResultKind kind, int reason, int count, string label) { if (count != 0) { List<Tally> value; Tally[] source = (names.TryGetValue((kind, reason), out value) ? value.Take(namesPerLine).ToArray() : Array.Empty<Tally>()); string[] items = source.Select(delegate(Tally x) { string text = ResultSummary.Bound(Display(itemName, x.Item), 40); int num = ((kind == ResultKind.Moved) ? x.Units : x.Stacks); return text + ((num > 1) ? (" ×" + num) : ""); }).ToArray(); lines.Add(new ResultLine(kind, (kind == ResultKind.Remaining) ? new CleanupReason?((CleanupReason)reason) : ((CleanupReason?)null), (kind == ResultKind.Refused) ? new TransferRefusal?((TransferRefusal)reason) : ((TransferRefusal?)null), count, label, items, Math.Max(0, count - source.Sum((Tally x) => x.Stacks)))); } } } private static string Display(Func<string, string>? itemName, string item) { string text = itemName?.Invoke(item); if (!string.IsNullOrWhiteSpace(text)) { return text; } return item; } public string Summary() { List<string> list = new List<string>(); list.Add($"{verb} {MovedStacks} stack{Plural(MovedStacks)} ({MovedUnits} item{Plural(MovedUnits)})" + ((MucMovedStacks == 0) ? "" : $", {MucMovedStacks} via MultiUserChest")); List<string> list2 = list; CleanupReason[] array = new CleanupReason[9] { CleanupReason.NoCapacity, CleanupReason.NoDestination, CleanupReason.AzuExcluded, CleanupReason.Held, CleanupReason.Unsupported, CleanupReason.MucBusy, CleanupReason.Budget, CleanupReason.ChestLimit, CleanupReason.CategoriesPending }; List<string> list3 = (from x in array where Count(x) != 0 select Count(x) + " " + Describe(x)).ToList(); if (list3.Count != 0) { list2.Add(LeftTotal(array) + " " + left + " (" + string.Join(", ", list3) + ")"); } foreach (CleanupReason item in new CleanupReason[3] { CleanupReason.OtherOwnerNoMuc, CleanupReason.BothRemote, CleanupReason.OtherOwnerFormatUnknown }.Where((CleanupReason x) => Count(x) != 0)) { list2.Add(Count(item) + " skipped (" + Describe(item) + ")"); } if (Refused != 0) { list2.Add(Refused + " refused (" + string.Join(", ", from x in refused orderby x.Key select x.Value + " " + Describe(x.Key)) + ")"); } if (Unconfirmed != 0) { list2.Add(Unconfirmed + " unconfirmed" + ((MucUnconfirmed == 0) ? "" : $" ({MucUnconfirmed} via MultiUserChest)")); } if (ChestsInRange > ChestsHandled) { list2.Add($"{ChestsInRange} chests in range; only the nearest {ChestsHandled} could be handled"); } return title + ": " + string.Join(" · ", list2); } private int LeftTotal(IEnumerable<CleanupReason> reasons) { return reasons.Sum((Func<CleanupReason, int>)Count); } private static string Plural(int count) { if (count != 1) { return "s"; } return ""; } public static string Describe(CleanupReason reason) { return reason switch { CleanupReason.NoCapacity => "no room", CleanupReason.NoDestination => "no chest takes it", CleanupReason.AzuExcluded => "AzuAutoStore's config excludes it", CleanupReason.Held => "in use", CleanupReason.Unsupported => "protected", CleanupReason.MucBusy => "other player's chest busy, run again", CleanupReason.Budget => "run limit reached, run again", CleanupReason.ChestLimit => "in a chest farther than the nearest " + 64 + " with items; clean up again closer to it", CleanupReason.OtherOwnerNoMuc => "another player's chest, no MultiUserChest", CleanupReason.BothRemote => "both chests are other players'", CleanupReason.OtherOwnerFormatUnknown => "another player's chest, their MultiUserChest format is unknown", CleanupReason.CategoriesPending => "waiting for categories to load, run again shortly", _ => reason.ToString(), }; } public static string Describe(TransferRefusal reason) { return reason switch { TransferRefusal.InventoryChanged => "chest changed", TransferRefusal.OwnerChanged => "a chest changed owner, run again", TransferRefusal.PolicyChanged => "rules or categories changed, run again", TransferRefusal.RuleMismatch => "rules changed, run again", TransferRefusal.AccessDenied => "no access", TransferRefusal.NoCapacity => "no room", TransferRefusal.Protected => "protected", TransferRefusal.Disconnected => "left the world", TransferRefusal.Expired => "too far or too late", TransferRefusal.UnsupportedCapability => "unsupported item", TransferRefusal.UnsupportedAuthority => "not the chest owner", TransferRefusal.Busy => "MultiUserChest refused or refunded it", TransferRefusal.TurnedOff => "turned off on this server", _ => reason.ToString(), }; } } public enum ResultKind { Moved, Remaining, Refused, Unconfirmed } public sealed class ResultLine { public ResultKind Kind { get; } public CleanupReason? Reason { get; } public TransferRefusal? Refusal { get; } public int Count { get; } public string Label { get; } public IReadOnlyList<string> Items { get; } public int More { get; } public string Text { get; } internal ResultLine(ResultKind kind, CleanupReason? reason, TransferRefusal? refusal, int count, string label, IReadOnlyList<string> items, int more) { Kind = kind; Reason = reason; Refusal = refusal; Count = count; Label = label; Items = items; More = more; string text = string.Join(", ", items) + ((more == 0) ? "" : (((items.Count == 0) ? "" : " ") + "+" + more + " more")); Text = ResultSummary.Bound(label + ((text.Length == 0) ? "" : (": " + text)), 160); } } public sealed class ResultSummary { public const int MaximumText = 160; public string Title { get; } public string Headline { get; } public IReadOnlyList<ResultLine> Lines { get; } public int MoreLines { get; } public bool Empty => Lines.Count == 0; internal ResultSummary(string title, string headline, IReadOnlyList<ResultLine> lines, int more) { Title = title; Headline = headline; Lines = lines; MoreLines = more; } internal static string Bound(string text, int maximum) { if (text.Length > maximum) { return text.Substring(0, maximum - 1) + "…"; } return text; } } public enum ContainerPolicyReadState { Absent, Supported, Unsupported } public sealed class ContainerPolicyRead { public ContainerPolicyReadState State { get; } public string OriginalPayload { get; } public ulong Revision { get; } public ChestRules? Rules { get; } public bool HasRule { get { if (State == ContainerPolicyReadState.Supported) { ChestRules? rules = Rules; if (rules == null) { return false; } return rules.State == ChestRuleState.Configured; } return false; } } internal ContainerPolicyRead(ContainerPolicyReadState state, string payload, ulong revision = 0uL, ChestRules? rules = null) { State = state; OriginalPayload = payload; Revision = revision; Rules = rules; } } public sealed class ChestRuleEdit { internal sealed class Alternative { internal string Key = ""; internal bool HasCategories; internal IReadOnlyDictionary<string, IReadOnlyList<string>> Filters; internal IEnumerable<string> Known = Array.Empty<string>(); internal IEnumerable<string> Unknown = Array.Empty<string>(); internal ClassificationFilterRule? Full; internal ChestRuleAlternative? Preserved; } public const int MaximumPriority = 999; public IReadOnlyList<string> Categories { get; } public IReadOnlyList<string> Includes { get; } public IReadOnlyList<string> Excludes { get; } public IReadOnlyList<string> UnknownCategories { get; } public int HiddenParts { get; private set; } public int? Priority { get; } public IReadOnlyDictionary<string, IReadOnlyList<string>> Filters { get; } public IReadOnlyDictionary<string, IReadOnlyList<string>> EditedFilters { get; } public ChestRuleEdit(IEnumerable<string>? categories, IEnumerable<string>? includes, IEnumerable<string>? excludes, IEnumerable<string>? unknownCategories = null, int? priority = null, IEnumerable<KeyValuePair<string, IEnumerable<string>>>? filters = null, IEnumerable<KeyValuePair<string, IEnumerable<string>>>? editedFilters = null) { Categories = Own(categories, 256); Includes = Own(includes, 2048); Excludes = Own(excludes, 2048); UnknownCategories = Own(unknownCategories, 256); bool flag; if (priority.HasValue) { int valueOrDefault = priority.GetValueOrDefault(); if (valueOrDefault < -999 || valueOrDefault > 999) { flag = true; goto IL_0072; } } flag = false; goto IL_0072; IL_0072: if (flag) { throw new ArgumentOutOfRangeException("priority"); } Priority = priority; Filters = OwnFilters(filters); EditedFilters = OwnFilters(editedFilters); } public static ChestRuleEdit From(ChestRules rules, bool withFilters = false) { if (rules == null) { throw new ArgumentNullException("rules"); } Alternative[] array = Alternatives(rules).ToArray(); string key = ""; int value = Math.Max(-999, Math.Min(999, rules.Priority)); if (withFilters && !array.Any((Alternative x) => x.Key.Length == 0 && x.HasCategories)) { Alternative[] array2 = array.Where((Alternative x) => x.Key.Length != 0).ToArray(); string[] array3 = array2.Select((Alternative x) => x.Key).Distinct<string>(StringComparer.Ordinal).ToArray(); if (array3.Length == 1 && (array2.All((Alternative x) => x.HasCategories) || array2.Length == 1)) { key = array3[0]; } } Alternative[] array4 = array.Where((Alternative x) => Edited(x, key)).ToArray(); KeyValuePair<string, IEnumerable<string>>[] array5 = ((key.Length == 0) ? null : array4[0].Filters.Select<KeyValuePair<string, IReadOnlyList<string>>, KeyValuePair<string, IEnumerable<string>>>((KeyValuePair<string, IReadOnlyList<string>> x) => new KeyValuePair<string, IEnumerable<string>>(x.Key, x.Value)).ToArray()); return new ChestRuleEdit(array4.SelectMany((Alternative x) => x.Known), rules.Includes, rules.Excludes, array4.SelectMany((Alternative x) => x.Unknown), value, array5, array5) { HiddenParts = array.Length - array4.Length + (rules.Overflow ? 1 : 0) }; } internal static IEnumerable<Alternative> Alternatives(ChestRules rules) { return rules.FullRules.Select((ClassificationFilterRule x) => new Alternative { Key = Key(x.Filters), HasCategories = (x.Categories.Count != 0), Filters = x.Filters, Known = x.Categories, Full = x }).Concat(rules.Preserved.Select(delegate(ChestRuleAlternative x) { Alternative obj = new Alternative { Key = Key(x.Filters), HasCategories = true, Filters = x.Filters }; IEnumerable<string> enumerable = x.Effective?.Categories; obj.Known = enumerable ?? Array.Empty<string>(); obj.Unknown = x.UnknownCategories; obj.Preserved = x; return obj; })); } internal static bool Edited(Alternative row, string key) { if (row.Key == key) { if (!row.HasCategories) { return key.Length != 0; } return true; } return false; } internal static string Key(IReadOnlyDictionary<string, IReadOnlyList<string>> filters) { return string.Join(";", from x in filters.Where<KeyValuePair<string, IReadOnlyList<string>>>((KeyValuePair<string, IReadOnlyList<string>> x) => x.Value.Count != 0).OrderBy<KeyValuePair<string, IReadOnlyList<string>>, string>((KeyValuePair<string, IReadOnlyList<string>> x) => x.Key, StringComparer.Ordinal) select x.Key + "=" + string.Join(",", x.Value.OrderBy<string, string>((string v) => v, StringComparer.Ordinal))); } private static IReadOnlyDictionary<string, IReadOnlyList<string>> OwnFilters(IEnumerable<KeyValuePair<string, IEnumerable<string>>>? input) { SortedDictionary<string, IReadOnlyList<string>> sortedDictionary = new SortedDictionary<string, IReadOnlyList<string>>(StringComparer.Ordinal); KeyValuePair<string, IEnumerable<string>>[] array = ClassificationValidation.Bounded(input ?? Array.Empty<KeyValuePair<string, IEnumerable<string>>>(), 32); for (int i = 0; i < array.Length; i++) { KeyValuePair<string, IEnumerable<string>> keyValuePair = array[i]; string[] array2 = ClassificationValidation.Bounded(keyValuePair.Value ?? throw new ArgumentNullException("input"), 64).Distinct<string>(StringComparer.Ordinal).OrderBy<string, string>((string x) => x, StringComparer.Ordinal) .ToArray(); ClassificationFilters.ValidateRuleFilter(keyValuePair.Key, array2); if (sortedDictionary.ContainsKey(keyValuePair.Key)) { throw new ArgumentException("Duplicate filter dimension."); } if (array2.Length != 0) { sortedDictionary.Add(keyValuePair.Key, Array.AsReadOnly(array2)); } } return new ReadOnlyDictionary<string, IReadOnlyList<string>>(sortedDictionary); } private static IReadOnlyList<string> Own(IEnumerable<string>? input, int limit) { string[] array = ClassificationValidation.Bounded(input ?? Array.Empty<string>(), limit); string[] array2 = array; for (int i = 0; i < array2.Length; i++) { ClassificationValidation.Text(array2[i], 2048); } return Array.AsReadOnly(array.Distinct<string>(StringComparer.Ordinal).OrderBy<string, string>((string x) => x, StringComparer.Ordinal).ToArray()); } } public static class ContainerPolicyEnvelope { public const string ZdoKey = "com.bastionntb.yavsos.container-policy"; private const string RuleFormat = "yavsos-chest-rules-xml-1"; public static string Serialize(ulong revision, ChestRules rules) { if (rules == null) { throw new ArgumentNullException("rules"); } XElement xElement = new XElement("root", new XAttribute("type", "object"), Value("schemaVersion", "number", "1"), Value("revision", "string", revision.ToString(CultureInfo.InvariantCulture)), new XElement("policy", new XAttribute("type", "object"), Value("format", "string", "yavsos-chest-rules-xml-1"), Value("rulesXml", "string", ChestRuleCodec.Serialize(rules)))); using BoundedTransferStream boundedTransferStream = new BoundedTransferStream(65536); using (XmlDictionaryWriter writer = JsonReaderWriterFactory.CreateJsonWriter(boundedTransferStream, TransferCanonical.Utf8, ownsStream: false)) { xElement.WriteTo(writer); } return TransferCanonical.Utf8.GetString(boundedTransferStream.ToArray()); } public static ContainerPolicyRead Read(string payload, ClassificationTaxonomy taxonomy) { if (payload == null) { throw new ArgumentNullException("payload"); } if (taxonomy == null) { throw new ArgumentNullException("taxonomy"); } if (payload.Length == 0) { return new ContainerPolicyRead(ContainerPolicyReadState.Absent, payload, 0uL); } try { TransferCanonical.Text(payload, 65536, empty: false); ValidateJsonSyntax(payload); byte[] bytes = TransferCanonical.Utf8.GetBytes(payload); XmlDictionaryReaderQuotas quotas = new XmlDictionaryReaderQuotas { MaxDepth = 8, MaxStringContentLength = 65536, MaxArrayLength = 65536, MaxBytesPerRead = 65536, MaxNameTableCharCount = 1024 }; using XmlDictionaryReader reader = JsonReaderWriterFactory.CreateJsonReader(bytes, 0, bytes.Length, TransferCanonical.Utf8, quotas, null); XElement xElement = XElement.Load(reader, LoadOptions.None); Object(xElement, "schemaVersion", "revision", "policy"); if (Leaf(xElement, "schemaVersion", "number") != "1") { throw new InvalidDataException("Unknown policy envelope schema."); } string text = Leaf(xElement, "revision", "string"); if (!ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result.ToString(CultureInfo.InvariantCulture) != text) { throw new InvalidDataException("Invalid policy revision."); } XElement xElement2 = xElement.Element("policy"); Object(xElement2, "format", "rulesXml"); if (Leaf(xElement2, "format", "string") != "yavsos-chest-rules-xml-1") { throw new InvalidDataException("Unsupported policy rules format."); } ChestRules rules = ChestRuleCodec.Parse(Leaf(xElement2, "rulesXml", "string"), taxonomy); return new ContainerPolicyRead(ContainerPolicyReadState.Supported, payload, result, rules); } catch (Exception ex) when (ex is ArgumentException || ex is XmlException || ex is InvalidDataException || ex is FormatException) { return new ContainerPolicyRead(ContainerPolicyReadState.Unsupported, payload, 0uL); } } public static TransferRefusal TryEdit(string currentPayload, string expectedPayload, ClassificationTaxonomy taxonomy, ChestRuleEdit? edit, out string? updatedPayload) { updatedPayload = null; if (!StringComparer.Ordinal.Equals(currentPayload, expectedPayload)) { return TransferRefusal.PolicyChanged; } ContainerPolicyRead containerPolicyRead = Read(currentPayload, taxonomy); if (edit == null) { updatedPayload = ""; return TransferRefusal.None; } if (containerPolicyRead.State == ContainerPolicyReadState.Unsupported || containerPolicyRead.Revision == ulong.MaxValue) { return TransferRefusal.UnsupportedCapability; } ChestRules rules = containerPolicyRead.Rules; try { string key = ChestRuleEdit.Key(edit.EditedFilters); ChestRuleEdit.Alternative[] array = ((rules == null) ? Array.Empty<ChestRuleEdit.Alternative>() : (from x in ChestRuleEdit.Alternatives(rules) where !ChestRuleEdit.Edited(x, key) select x).ToArray()); IEnumerable<ClassificationFilterRule> enumerable = from x in array where x.Full != null select x.Full; IEnumerable<ChestRuleAlternative> enumerable2 = from x in array where x.Preserved != null select x.Preserved; foreach (string category in edit.Categories) { taxonomy.Require(category); } KeyValuePair<string, IEnumerable<string>>[] array2 = edit.Filters.Select<KeyValuePair<string, IReadOnlyList<string>>, KeyValuePair<string, IEnumerable<string>>>((KeyValuePair<string, IReadOnlyList<string>> x) => new KeyValuePair<string, IEnumerable<string>>(x.Key, x.Value)).ToArray(); (ClassificationFilterRule, ChestRuleAlternative) tuple = ((edit.Categories.Count + edit.UnknownCategories.Count != 0) ? ChestRuleCodec.Alternative(taxonomy, edit.Categories.Concat(edit.UnknownCategories), array2) : (((ClassificationFilterRule? Rule, ChestRuleAlternative? Preserved))((array2.Length == 0) ? (null, null) : (new ClassificationFilterRule(taxonomy, Array.Empty<string>(), array2), null)))); bool flag = rules != null && rules.State == ChestRuleState.Configured && rules.Overflow; if (tuple.Item1 == null && tuple.Item2 == null && array.Length == 0 && edit.Includes.Count + edit.Excludes.Count == 0 && !flag) { updatedPayload = ""; return TransferRefusal.None; } ChestRules rules2 = new ChestRules(taxonomy, ChestRuleState.Configured, (tuple.Item1 == null) ? enumerable : enumerable.Concat(new ClassificationFilterRule[1] { tuple.Item1 }), edit.Includes, edit.Excludes, edit.Priority ?? rules?.Priority ?? 0, flag, existingItemFallback: false, (tuple.Item2 == null) ? enumerable2 : enumerable2.Concat(new ChestRuleAlternative[1] { tuple.Item2 })); updatedPayload = Serialize(containerPolicyRead.Revision + 1, rules2); return TransferRefusal.None; } catch (Exception ex) when (ex is ArgumentException || ex is InvalidDataException) { updatedPayload = null; return TransferRefusal.UnsupportedCapability; } } private static XElement Value(string name, string type, string value) { return new XElement(name, new XAttribute("type", type), value); } private static void ValidateJsonSyntax(string text) { int at = 0; ObjectSyntax(1); Space(); if (at != text.Length) { throw new InvalidDataException("Trailing policy JSON content."); } bool Digit() { if (at < text.Length && text[at] >= '0') { return text[at] <= '9'; } return false; } void Need(char value) { if (!Take(value)) { throw new InvalidDataException("Incomplete policy JSON."); } } void Number() { if (at < text.Length && text[at] == '-') { at++; } if (!Digit()) { throw new InvalidDataException("Invalid policy JSON number."); } if (text[at] == '0') { at++; } else { while (Digit()) { at++; } } if (at < text.Length && text[at] == '.') { at++; if (!Digit()) { throw new InvalidDataException("Invalid policy JSON fraction."); } while (Digit()) { at++; } } if (at < text.Length && (text[at] == 'e' || text[at] == 'E')) { at++; if (at < text.Length && (text[at] == '+' || text[at] == '-')) { at++; } if (!Digit()) { throw new InvalidDataException("Invalid policy JSON exponent."); } while (Digit()) { at++; } } } void ObjectSyntax(int depth) { if (depth > 8) { throw new InvalidDataException("Policy JSON depth bound."); } Need('{'); if (!Take('}')) { while (true) { String(); Need(':'); Space(); if (at >= text.Length) { throw new InvalidDataException("Incomplete policy JSON value."); } if (text[at] == '{') { ObjectSyntax(depth + 1); } else if (text[at] == '"') { String(); } else { Number(); } if (Take('}')) { break; } Need(','); } } } void Space() { for (; at < text.Length && (text[at] == ' ' || text[at] == '\t' || text[at] == '\r' || text[at] == '\n'); at++) { } } void String() { Need('"'); while (at < text.Length) { char c = text[at++]; if (c == '"') { return; } if (c < ' ') { throw new InvalidDataException("Unescaped policy JSON control character."); } if (c == '\\') { if (at >= text.Length) { throw new InvalidDataException("Incomplete policy JSON escape."); } switch (text[at++]) { case 'u': { for (int i = 0; i < 4; i++) { if (at >= text.Length || !Uri.IsHexDigit(text[at++])) { throw new InvalidDataException("Invalid policy JSON Unicode escape."); } } break; } default: throw new InvalidDataException("Invalid policy JSON escape."); case '"': case '/': case '\\': case 'b': case 'f': case 'n': case 'r': case 't': break; } } } throw new InvalidDataException("Incomplete policy JSON string."); } bool Take(char value) { Space(); if (at >= text.Length || text[at] != value) { return false; } at++; return true; } } private static void Object(XElement element, params string[] fields) { if ((string?)element.Attribute("type") != "object" || element.Attributes().Count() != 1 || element.Elements().Count() != fields.Length || fields.Any((string f) => element.Elements(f).Count() != 1) || element.Nodes().Any((XNode n) => !(n is XElement))) { throw new InvalidDataException("Unsupported policy object shape."); } } private static string Leaf(XElement parent, string name, string type) { XElement xElement = parent.Element(name) ?? throw new InvalidDataException("Missing policy field."); if ((string?)xElement.Attribute("type") != type || xElement.Attributes().Count() != 1 || xElement.HasElements) { throw new InvalidDataException("Invalid policy field type."); } return xElement.Value; } } public enum DelegatedMoveState { Pending, Moved, Refused, Unconfirmed } public sealed class DelegatedMoveObserver { public const double DefaultWindowSeconds = 40.0; private readonly int destinationBefore; private readonly int otherDestinationBefore; private int sourceBefore; private bool sourceUnknown; private readonly double deadline; private bool taken; public CleanupRoute Route { get; } public int Units { get; } public DelegatedMoveState State { get; private set; } public int MovedUnits { get; private set; } public string Detail { get; private set; } = ""; public DelegatedMoveObserver(CleanupRoute route, int units, int sourceBefore, int destinationBefore, int otherDestinationBefore, double now, double windowSeconds = 40.0) { if (route == CleanupRoute.Direct) { throw new ArgumentException("Direct moves are verified synchronously.", "route"); } if (units < 1 || sourceBefore < units || destinationBefore < 0 || otherDestinationBefore < 0) { throw new ArgumentOutOfRangeException("units"); } if (double.IsNaN(now) || double.IsInfinity(now) || windowSeconds <= 0.0) { throw new ArgumentOutOfRangeException("now"); } Route = route; Units = units; this.sourceBefore = sourceBefore; this.destinationBefore = destinationBefore; this.otherDestinationBefore = otherDestinationBefore; deadline = now + windowSeconds; } public void SourceTaken(int units) { if (units < 0) { throw new ArgumentOutOfRangeException("units"); } sourceBefore -= units; } public void SourceUnknown() { sourceUnknown = true; } public DelegatedMoveState Observe(double now, int source, int destination, int otherDestination, bool sessionCurrent) { if (State != DelegatedMoveState.Pending) { return State; } if (!sessionCurrent) { return Finish(DelegatedMoveState.Unconfirmed, 0, "the session ended while MultiUserChest was moving it"); } int num = Math.Max(0, destination - destinationBefore); int num2 = sourceBefore - source; if (Route == CleanupRoute.MucAdd) { if (num2 > 0) { taken = true; } if (num >= Units) { return Finish(DelegatedMoveState.Moved, Units, "arrived"); } if (sourceUnknown) { if (now >= deadline) { return Finish(DelegatedMoveState.Unconfirmed, 0, $"after {40.0:0} s: destination +{num}; another move changed the source"); } return State; } if (taken && num2 <= 0 && num == 0) { return Finish(DelegatedMoveState.Refused, 0, "refunded by MultiUserChest"); } if (taken && num > 0 && num2 == num) { return Finish(DelegatedMoveState.Moved, num, "partly arrived; the rest was refunded"); } } else { if (otherDestination > otherDestinationBefore) { return Finish(DelegatedMoveState.Unconfirmed, 0, "a different item arrived"); } if (num >= Units) { return Finish(DelegatedMoveState.Moved, Units, "arrived"); } } if (now >= deadline) { return Finish(DelegatedMoveState.Unconfirmed, 0, $"after {40.0:0} s: source −{num2}, destination +{num}"); } return State; } private DelegatedMoveState Finish(DelegatedMoveState state, int moved, string detail) { State = state; MovedUnits = moved; Detail = detail; return state; } } public enum TransferRefusal { None, AccessDenied, Busy, UnsupportedAuthority, UnsupportedCapability, Protected, PolicyChanged, InventoryChanged, OwnerChanged, NoCapacity, Expired, Disconnected, ProtocolMismatch, InvariantFailed, Unconfirmed, RuleMismatch, TurnedOff } public sealed class TransferInventoryStamp : IEquatable<TransferInventoryStamp> { public string InventoryId { get; } public Guid InstanceGeneration { get; } public ulong ChangeEpoch { get; } public ulong LayoutProtectionRevision { get; } public long OwnerPeer { get; } public ushort NativeOwnerRevision { get; } public string AuthoritativePayloadFingerprint { get; } public TransferInventoryStamp(string inventoryId, Guid instanceGeneration, ulong changeEpoch, ulong layoutProtectionRevision, long ownerPeer, ushort nativeOwnerRevision, string authoritativePayloadFingerprint) { InventoryId = TransferCanonical.Text(inventoryId, 2048, empty: false); if (instanceGeneration == Guid.Empty) { throw new ArgumentException("Missing adapter instance generation."); } InstanceGeneration = instanceGeneration; ChangeEpoch = changeEpoch; LayoutProtectionRevision = layoutProtectionRevision; OwnerPeer = ownerPeer; NativeOwnerRevision = nativeOwnerRevision; AuthoritativePayloadFingerprint = TransferCanonical.Text(authoritativePayloadFingerprint, 128, empty: false); } public bool Equals(TransferInventoryStamp? other) { if (other != null && StringComparer.Ordinal.Equals(InventoryId, other.InventoryId) && InstanceGeneration == other.InstanceGeneration && ChangeEpoch == other.ChangeEpoch && LayoutProtectionRevision == other.LayoutProtectionRevision && OwnerPeer == other.OwnerPeer && NativeOwnerRevision == other.NativeOwnerRevision) { return StringComparer.Ordinal.Equals(AuthoritativePayloadFingerprint, other.AuthoritativePayloadFingerprint); } return false; } public override bool Equals(object? obj) { if (obj is TransferInventoryStamp other) { return Equals(other); } return false; } public override int GetHashCode() { return StringComparer.Ordinal.GetHashCode(InventoryId); } } public sealed class TransferStack { public Guid Handle { get; } public int X { get; } public int Y { get; } public int Quantity { get; } public int MaximumStack { get; } public TransferItemIdentity Identity { get; } public bool Protected { get; } public bool TransferSupported { get; } public string? MergeClass { get; } public string MergeKey { get { if (MaximumStack <= 1 || MergeClass == null) { return "item:" + Identity.Fingerprint; } return "class:" + MergeClass; } } public TransferStack(Guid handle, int x, int y, int quantity, int maximumStack, TransferItemIdentity identity, bool isProtected = false, bool transferSupported = true, string? mergeClass = null) { if (handle == Guid.Empty || x < 0 || y < 0 || maximumStack < 1 || quantity < 1 || quantity > maximumStack) { throw new ArgumentException("Invalid selected stack."); } Handle = handle; X = x; Y = y; Quantity = quantity; MaximumStack = maximumStack; Identity = identity ?? throw new ArgumentNullException("identity"); Protected = isProtected; TransferSupported = transferSupported; if (mergeClass != null) { MergeClass = TransferCanonical.Text(mergeClass, 256, empty: false); } } } public sealed class TransferInventorySnapshot { public TransferInventoryStamp Stamp { get; } public int Width { get; } public int Height { get; } public IReadOnlyList<TransferStack> Stacks { get; } public IReadOnlyList<int> ProtectedCells { get; } public string ContentFingerprint { get; } public TransferInventorySnapshot(TransferInventoryStamp stamp, int width, int height, IEnumerable<TransferStack> stacks, IEnumerable<int> protectedCells) { Stamp = stamp ?? throw new ArgumentNullException("stamp"); if (width < 1 || height < 1 || (long)width * (long)height > 65536) { throw new ArgumentException("Unsupported grid bounds."); } Width = width; Height = height; if (stacks == null || protectedCells == null) { throw new ArgumentNullException(); } List<TransferStack> list = new List<TransferStack>(); HashSet<int> hashSet = new HashSet<int>(); HashSet<Guid> hashSet2 = new HashSet<Guid>(); foreach (TransferStack stack in stacks) { if (stack == null || stack.X >= width || stack.Y >= height || !hashSet.Add(stack.Y * width + stack.X) || !hashSet2.Add(stack.Handle)) { throw new ArgumentException("Invalid or duplicate stack/cell."); } list.Add(stack); } Stacks = Array.AsReadOnly((from s in list orderby s.Y, s.X select s).ToArray()); HashSet<int> hashSet3 = new HashSet<int>(); foreach (int protectedCell in protectedCells) { if (protectedCell < 0 || protectedCell >= width * height) { throw new ArgumentException("Protected cell outside grid."); } hashSet3.Add(protectedCell); } ProtectedCells = Array.AsReadOnly(hashSet3.OrderBy((int x) => x).ToArray()); ContentFingerprint = TransferCanonical.Hash(WriteCanonical, 1048576); } private void WriteCanonical(BinaryWriter w) { w.Write("yavsos-transfer-inventory-1"); w.Write(Width); w.Write(Height); w.Write(ProtectedCells.Count); foreach (int protectedCell in ProtectedCells) { w.Write(protectedCell); } w.Write(Stacks.Count); foreach (TransferStack stack in Stacks) { w.Write(stack.Handle.ToByteArray()); w.Write(stack.X); w.Write(stack.Y); w.Write(stack.Quantity); w.Write(stack.MaximumStack); w.Write(stack.Protected); w.Write(stack.TransferSupported); w.Write(stack.MergeClass ?? ""); stack.Identity.WriteCanonical(w); } } } public sealed class DepositPlan { public Guid OperationId { get; } public Guid SessionNonce { get; } public string PolicyStamp { get; } public string RuleStamp { get; } public TransferInventorySnapshot Source { get; } public TransferInventorySnapshot Destination { get; } public TransferStack Selection { get; } public int RequestedUnits { get; } public int PlannedUnits { get; } public int DestinationX { get; } public int DestinationY { get; } public Guid? MergeHandle { get; } public double AdmissionDeadline { get; } private DepositPlan(Guid operationId, Guid sessionNonce, string policyStamp, string ruleStamp, TransferInventorySnapshot source, TransferInventorySnapshot destination, TransferStack selection, int requestedUnits, int plannedUnits, int x, int y, Guid? mergeHandle, double deadline) { OperationId = operationId; SessionNonce = sessionNonce; PolicyStamp = policyStamp; RuleStamp = ruleStamp; Source = source; Destination = destination; Selection = selection; RequestedUnits = requestedUnits; PlannedUnits = plannedUnits; DestinationX = x; DestinationY = y; MergeHandle = mergeHandle; AdmissionDeadline = deadline; } public static TransferRefusal TryCreateAt(Guid operationId, Guid sessionNonce, string policyStamp, string ruleStamp, TransferInventorySnapshot source, TransferInventorySnapshot destination, Guid selection, int requestedUnits, int x, int y, Guid? mergeHandle, double now, double admissionDeadline, out DepositPlan? plan) { plan = null; if (operationId == Guid.Empty || sessionNonce == Guid.Empty || requestedUnits < 1 || source == null || destination == null || source.Stamp.InventoryId == destination.Stamp.InventoryId || double.IsNaN(now) || double.IsInfinity(now) || now < 0.0 || double.IsNaN(admissionDeadline) || double.IsInfinity(admissionDeadline) || admissionDeadline <= now) { return TransferRefusal.Expired; } TransferCanonical.Text(policyStamp, 2048, empty: false); TransferCanonical.Text(ruleStamp, 2048, empty: false); TransferStack transferStack = source.Stacks.SingleOrDefault((TransferStack s) => s.Handle == selection); if (transferStack == null) { return TransferRefusal.InventoryChanged; } if (transferStack.Protected || source.ProtectedCells.Contains(transferStack.Y * source.Width + transferStack.X)) { return TransferRefusal.Protected; } if (!transferStack.TransferSupported) { return TransferRefusal.UnsupportedCapability; } if (x < 0 || y < 0 || x >= destination.Width || y >= destination.Height) { return TransferRefusal.NoCapacity; } int value = y * destination.Width + x; if (destination.ProtectedCells.Contains(value)) { return TransferRefusal.NoCapacity; } TransferStack transferStack2 = destination.Stacks.SingleOrDefault((TransferStack s) => s.X == x && s.Y == y); int num; if (!mergeHandle.HasValue) { if (transferStack2 != null) { return TransferRefusal.NoCapacity; } num = transferStack.MaximumStack; goto IL_0218; } if (transferStack2 != null) { Guid handle = transferStack2.Handle; Guid? guid = mergeHandle; if (guid.HasValue && !(handle != guid.GetValueOrDefault()) && !transferStack2.Protected && transferStack2.TransferSupported && transferStack2.MaximumStack == transferStack.MaximumStack && StringComparer.Ordinal.Equals(transferStack.MergeKey, transferStack2.MergeKey)) { num = transferStack2.MaximumStack - transferStack2.Quantity; if (num < 1) { return TransferRefusal.NoCapacity; } goto IL_0218; } } return TransferRefusal.InventoryChanged; IL_0218: int num2 = Math.Min(Math.Min(requestedUnits, transferStack.Quantity), num); plan = new DepositPlan(operationId, sessionNonce, policyStamp, ruleStamp, source, destination, transferStack, num2, num2, x, y, mergeHandle, admissionDeadline); return TransferRefusal.None; } } public readonly struct DirectMoveObservation { public int Decrease { get; } public int Increase { get; } public bool OthersUnchanged { get; } public bool CellHoldsKey { get; } public bool NothingChanged { get; } internal DirectMoveObservation(int decrease, int increase, bool others, bool cell, bool nothing) { Decrease = decrease; Increase = increase; OthersUnchanged = others; CellHoldsKey = cell; NothingChanged = nothing; } } public static class MoveVerification { public static string MergeKey(TransferInventorySnapshot snapshot, Guid handle, TransferItemIdentity identity) { if (snapshot == null || identity == null) { throw new ArgumentNullException("snapshot"); } return snapshot.Stacks.FirstOrDefault((TransferStack x) => x.Handle == handle)?.MergeKey ?? identity.Fingerprint; } public static Dictionary<string, int> Totals(TransferInventorySnapshot snapshot) { if (snapshot == null) { throw new ArgumentNullException("snapshot"); } Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal); foreach (TransferStack stack in snapshot.Stacks) { dictionary[stack.MergeKey] = Get(dictionary, stack.MergeKey) + stack.Quantity; } return dictionary; } public static int Get(IReadOnlyDictionary<string, int> totals, string key) { if (!totals.TryGetValue(key, out var value)) { return 0; } return value; } public static bool SameExcept(IReadOnlyDictionary<string, int> before, IReadOnlyDictionary<string, int> after, string key) { foreach (KeyValuePair<string, int> item in before) { if (item.Key != key && Get(after, item.Key) != item.Value) { return false; } } foreach (KeyValuePair<string, int> item2 in after) { if (item2.Key != key && Get(before, item2.Key) != item2.Value) { return false; } } return true; } public static DirectMoveObservation Observe(TransferInventorySnapshot sourceBefore, TransferInventorySnapshot destinationBefore, TransferInventorySnapshot sourceAfter, TransferInventorySnapshot destinationAfter, string key, int x, int y) { if (sourceBefore == null || destinationBefore == null || sourceAfter == null || destinationAfter == null || key == null) { throw new ArgumentNullException("sourceBefore"); } Dictionary<string, int> dictionary = Totals(sourceBefore); Dictionary<string, int> dictionary2 = Totals(sourceAfter); Dictionary<string, int> dictionary3 = Totals(destinationBefore); Dictionary<string, int> dictionary4 = Totals(destinationAfter); TransferStack transferStack = destinationAfter.Stacks.FirstOrDefault((TransferStack s) => s.X == x && s.Y == y); return new DirectMoveObservation(Get(dictionary, key) - Get(dictionary2, key), Get(dictionary4, key) - Get(dictionary3, key), SameExcept(dictionary, dictionary2, key) && SameExcept(dictionary3, dictionary4, key), transferStack != null && StringComparer.Ordinal.Equals(transferStack.MergeKey, key), StringComparer.Ordinal.Equals(sourceBefore.ContentFingerprint, sourceAfter.ContentFingerprint) && StringComparer.Ordinal.Equals(destinationBefore.ContentFingerprint, destinationAfter.ContentFingerprint)); } public static (int Source, int Destination, int OtherDestination) Counts(TransferInventorySnapshot source, TransferInventorySnapshot destination, string key) { if (source == null || destination == null || key == null) { throw new ArgumentNullException("source"); } Dictionary<string, int> dictionary = Totals(destination); return (Source: Get(Totals(source), key), Destination: Get(dictionary, key), OtherDestination: dictionary.Where((KeyValuePair<string, int> x) => x.Key != key).Sum((KeyValuePair<string, int> x) => x.Value)); } } public enum MultiUserChestCopy { None, Standalone, AzuAutoStore, Yavsos } public static class MultiUserChestCopies { public const string StandaloneGuid = "com.maxsch.valheim.MultiUserChest"; public const string AzuGuid = "Azumatt.AzuAutoStore"; public static readonly string[] Conflicting = new string[3] { "org.bepinex.plugins.valheim.quick_stack", "aedenthorn.SimpleSort", "aedenthorn.QuickStore" }; public static MultiUserChestCopy Choose(Func<string, bool> loaded) { if (loaded == null) { throw new ArgumentNullException("loaded"); } if (loaded("com.maxsch.valheim.MultiUserChest")) { return MultiUserChestCopy.Standalone; } if (Conflicting.Any(loaded)) { return MultiUserChestCopy.None; } if (!loaded("Azumatt.AzuAutoStore")) { return MultiUserChestCopy.Yavsos; } return MultiUserChestCopy.AzuAutoStore; } } public sealed class NativeTransferPrefabLimits { public int MaximumStack { get; } public int MaximumQuality { get; } public int Variants { get; } public NativeTransferPrefabLimits(int maximumStack, int maximumQuality, int variants) { MaximumStack = maximumStack; MaximumQuality = maximumQuality; Variants = variants; } } public sealed class NativeTransferRecord { private readonly byte[] identity; public int X { get; } public int Y { get; } public int Stack { get; } internal NativeTransferRecord(int x, int y, int stack, byte[] identity) { X = x; Y = y; Stack = stack; this.identity = identity; } public byte[] CopyIdentity() { return (byte[])identity.Clone(); } public bool SameIdentity(NativeTransferRecord other) { if (other != null) { return identity.SequenceEqual(other.identity); } return false; } } public static class NativeTransferInventoryCodec { private sealed class Cursor { private readonly byte[] bytes; private int offset; private readonly int end; internal int Offset => offset; internal Cursor(byte[] bytes, int start, int end) { this.bytes = bytes; offset = start; this.end = end; } private void Require(int count) { if (count < 0 || count > end - offset) { throw new InvalidDataException("Truncated native inventory."); } } internal byte Byte() { Require(1); return bytes[offset++]; } internal int UShort() { return Byte() | (Byte() << 8); } internal int Int() { return Byte() | (Byte() << 8) | (Byte() << 16) | (Byte() << 24); } internal long Long() { return (uint)Int() | ((long)Int() << 32); } internal int NumItems() { byte b = Byte(); if (b < 128) { return b; } int num = ((b & 0x7F) << 8) | Byte(); if (num < 128) { throw new InvalidDataException("Noncanonical native metadata count."); } return num; } internal string? Text(int maximum, bool retain) { uint num = 0u; int num2 = 0; while (true) { byte b = Byte(); if (num2 == 28 && (b & 0xF8) != 0) { throw new InvalidDataException("Native text length overflow."); } num |= (uint)((b & 0x7F) << num2); if ((b & 0x80) == 0) { if (num2 == 0 || b != 0) { break; } throw new InvalidDataException("Noncanonical native text length."); } num2 += 7; if (num2 > 28) { throw new InvalidDataException("Native text length overflow."); } } if (num > maximum) { throw new InvalidDataException("Native text exceeds bound."); } Require((int)num); try { Utf8.GetCharCount(bytes, offset, (int)num); string result = (retain ? Utf8.GetString(bytes, offset, (int)num) : null); offset += (int)num; return result; } catch (DecoderFallbackException) { throw new InvalidDataException("Malformed native UTF-8."); } } } public const int MaximumBytes = 1048576; private static readonly UTF8Encoding Utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); public static void Validate(byte[] payload, int width, int height, Func<int, NativeTransferPrefabLimits?> currentPrefab) { if (payload == null) { throw new ArgumentNullException("payload"); } if (payload.Length > 1048576) { throw new InvalidDataException("Unsupported bounded native inventory."); } ValidateCore(payload, 0, width, height, currentPrefab, requireEnd: true, null); } public static IReadOnlyList<NativeTransferRecord> Decode(byte[] payload, int width, int height, Func<int, NativeTransferPrefabLimits?> currentPrefab) { if (payload == null) { throw new ArgumentNullException("payload"); } if (payload.Length > 1048576) { throw new InvalidDataException("Unsupported bounded native inventory."); } List<NativeTransferRecord> list = new List<NativeTransferRecord>(); ValidateCore(payload, 0, width, height, currentPrefab, requireEnd: true, list); return list.AsReadOnly(); } public static int ValidatePrefix(byte[] payload, int offset, int width, int height, Func<int, NativeTransferPrefabLimits?> currentPrefab) { return ValidateCore(payload, offset, width, height, currentPrefab, requireEnd: false, null); } private static int ValidateCore(byte[] payload, int start, int width, int height, Func<int, NativeTransferPrefabLimits?> currentPrefab, bool requireEnd, List<NativeTransferRecord>? decoded) { if (payload == null) { throw new ArgumentNullException("payload"); } if (currentPrefab == null) { throw new ArgumentNullException("currentPrefab"); } if (start < 0 || start > payload.Length || width < 1 || height < 1 || width > 256 || height > 256) { throw new InvalidDataException("Unsupported bounded native inventory."); } Cursor cursor = new Cursor(payload, start, (int)Math.Min(payload.Length, (long)start + 1048576L)); if (cursor.Int() != 110) { throw new InvalidDataException("Unknown native inventory format."); } int num = cursor.UShort(); if (num > width * height) { throw new InvalidDataException("Unsupported native item count."); } HashSet<int> hashSet = new HashSet<int>(); List<(int, int, int, int)> list = new List<(int, int, int, int)>(num); List<(int, int, int, int, int, int)> list2 = new List<(int, int, int, int, int, int)>(num); for (int i = 0; i < num; i++) { int offset = cursor.Offset; int num2 = cursor.Int(); float num3 = (float)num2 * 0.01f * 100f; if (num2 < 0 || (double)num3 > 2147483647.0 || (int)num3 != num2) { throw new InvalidDataException("Native durability would normalize."); } byte b = cursor.Byte(); byte b2 = cursor.Byte(); cursor.Byte(); if (b >= width || b2 >= height || !hashSet.Add(b2 * width + b)) { throw new InvalidDataException("Invalid or duplicate native item cell."); } byte b3 = cursor.Byte(); int num4 = (((b3 & 4) == 0) ? 1 : cursor.UShort()); int item = (((b3 & 8) == 0) ? (-1) : cursor.Offset); int num5 = (((b3 & 8) == 0) ? 1 : cursor.UShort()); int num6 = (((b3 & 0x10) != 0) ? cursor.Int() : 0); if (num4 < 1 || num5 < 1 || num6 < 0) { throw new InvalidDataException("Invalid native item scalar."); } if (((b3 & 4) != 0 && num4 == 1) || ((b3 & 8) != 0 && num5 == 1) || ((b3 & 0x10) != 0 && num6 == 0)) { throw new InvalidDataException("Noncanonical native scalar flags."); } if ((b3 & 0x20) != 0) { if (cursor.Long() == 0L) { throw new InvalidDataException("Noncanonical native crafter flag."); } cursor.Text(16384, retain: false); } if ((b3 & 0x40) == 0) { throw new InvalidDataException("Native item lacks prefab identity."); } int num7 = cursor.Int(); if (num7 == 0) { throw new InvalidDataException("Native item has no registered identity."); } if ((b3 & 0x80) != 0) { int num8 = cursor.NumItems(); if (num8 < 1 || num8 > 2048) { throw new InvalidDataException("Unsupported native metadata count."); } HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal); for (int j = 0; j < num8; j++) { if (!hashSet2.Add(cursor.Text(16384, retain: true))) { throw new InvalidDataException("Duplicate native metadata key."); } cursor.Text(65536, retain: false); } } if ((cursor.Byte() & -2) != 0) { throw new InvalidDataException("Unknown native cheat flags."); } list.Add((num7, num5, num4, num6)); list2.Add((offset, cursor.Offset, b, b2, num5, item)); } if (requireEnd && cursor.Offset != payload.Length) { throw new InvalidDataException("Trailing native inventory data."); } foreach (var item2 in list) { NativeTransferPrefabLimits nativeTransferPrefabLimits = currentPrefab(item2.Item1); if (nativeTransferPrefabLimits == null || nativeTransferPrefabLimits.MaximumStack < 1 || nativeTransferPrefabLimits.MaximumStack > 65535 || nativeTransferPrefabLimits.MaximumQuality < 1 || nativeTransferPrefabLimits.MaximumQuality > 65535 || nativeTransferPrefabLimits.Variants < 0 || item2.Item2 > nativeTransferPrefabLimits.MaximumStack || item2.Item3 > nativeTransferPrefabLimits.MaximumQuality || (item2.Item4 != 0 && item2.Item4 >= nativeTransferPrefabLimits.Variants)) { throw new InvalidDataException("Native item is not supported by its current registered prefab."); } } if (decoded != null) { foreach (var item3 in list2) { List<byte> list3 = new List<byte>(item3.Item2 - item3.Item1); var (k, _, _, _, _, _) = item3; for (; k < item3.Item2; k++) { int num9 = k - item3.Item1; if (num9 != 4 && num9 != 5 && (item3.Item6 < 0 || (k != item3.Item6 && k != item3.Item6 + 1))) { list3.Add((num9 == 7) ? ((byte)(payload[k] & -9)) : payload[k]); } } decoded.Add(new NativeTransferRecord(item3.Item3, item3.Item4, item3.Item5, list3.ToArray())); } } return cursor.Offset - start; } } public enum PlacementOutcome { Goes, NoRoom, Blocked, NoChest, Stays, Kept, NotReady, OptedOut, Skipped } public enum ChestVerdict { Chosen, Backup, Full, Current, NotBetter, RuleRejects, Never, NoRule, RuleUnreadable, RuleCleared, OtherOwnerNoMuc, OtherOwnerFormatUnknown, BothRemote, AzuExcluded, CategoriesPending, OutOfRange, OverLimit, NotConsidered } public sealed class ChestExplanation { public string ChestId { get; } public ChestVerdict Verdict { get; } public string Text { get; } public RankedDestination? Rank { get; } internal ChestExplanation(string id, ChestVerdict verdict, string text, RankedDestination? rank) { ChestId = id; Verdict = verdict; Text = text; Rank = rank; } } public sealed class PlacementExplanation { public string Prefab { get; } public PlacementOutcome Outcome { get; } public string? ChosenId { get; } public CleanupReason? Blocked { get; } public string Summary { get; } public IReadOnlyList<ChestExplanation> Chests { get; } internal PlacementExplanation(string prefab, PlacementOutcome outcome, string? chosen, CleanupReason? blocked, string summary, List<ChestExplanation> chests) { Prefab = prefab; Outcome = outcome; ChosenId = chosen; Blocked = blocked; Summary = summary; Chests = chests.AsReadOnly(); } public ChestExplanation? For(string chestId) { return Chests.FirstOrDefault((ChestExplanation x) => StringComparer.Ordinal.Equals(x.ChestId, chestId)); } } public static class PlacementExplainer { private sealed class Names { private readonly ClassificationTaxonomy taxonomy; private readonly Func<string, string>? chest; private readonly Func<string, string>? item; private readonly Func<string, string, string>? open; internal Names(ClassificationTaxonomy taxonomy, Func<string, string>? chest, Func<string, string>? item, Func<string, string, string>? open) { this.taxonomy = taxonomy; this.chest = chest; this.item = item; this.open = open; } internal string Chest(string id) { return Bounded(chest?.Invoke(id), id); } internal string Item(string prefab) { return Bounded(item?.Invoke(prefab), prefab); } internal string Category(string id) { if (!taxonomy.Categories.TryGetValue(id, out ClassificationCategory value)) { return id; } return value.Name; } internal static string Value(string dimension, string value, Func<string, string, string>? open) { if (!(value != "unknown") || ClassificationFilters.Values(dimension) != null || open == null) { return RuleText.ValueName(dimension, value); } return open(dimension, value); } internal static string Dimension(string id) { return id switch { "mod" => "origin mod", "related-mod" => "related mod", "construction-mod" => "building mod", "food-stat" => "food stat", "eaten-by" => "eaten by", _ => id, }; } private static string Bounded(string? text, string fallback) { if (string.IsNullOrWhiteSpace(text)) { return fallback; } if (text.Length > 64) { return text.Substring(0, 63) + "…"; } return text; } internal string Why(RankedDestination rank, ClassificationResult item, FacetEvidence[] facets) { ChestRules rules = rank.Candidate.Rules; string text; if (rank.ExistingItemFallback) { text = "already holds this item"; } else if (rank.Match == AcceptanceMatch.ExactItem) { text = "lists it under Always"; } else { ClassificationFilterRule classificationFilterRule = rank.Rule ?? rules.Acceptance.Rules.FirstOrDefault(delegate(ClassificationFilterRule x) { bool flag = x.Categories.Count == 0 && ClassificationFilters.MatchesValidated(item, x, facets); if (!flag) { AcceptanceMatch match = rank.Match; bool flag2 = (uint)(match - 3) <= 1u; flag = flag2 && x.Categories.Count != 0 && ClassificationFilters.MatchesValidated(item, x, facets); } return flag; }); string text2 = rank.Category ?? classificationFilterRule?.Categories.FirstOrDefault((string c) => item.Memberships.Any((MembershipEvidence m) => m.Category == c)); text = ((text2 != null) ? ("category " + Category(text2)) : ((rank.Match == AcceptanceMatch.Any) ? "takes anything" : "its filters")); string text3 = ((classificationFilterRule == null) ? "" : RuleText.Short(classificationFilterRule.Filters, open)); if (text3.Length != 0) { text = text + ((text2 != null) ? " › " : " ") + text3; } if (rank.Generality > 0) { text = text + ", " + rank.Generality + " level" + ((rank.Generality == 1) ? "" : "s") + " above the item's own category"; } } if (rules.Priority != 0) { text = text + ", " + ChestPriority.Short(rules.Priority); } if (rules.Overflow) { text += ", overflow"; } return text; } } public static PlacementExplanation Stash(StashInput input, Guid handle, ChestRuleEvaluator? evaluator = null, Func<string, string>? chestName = null, Func<string, string>? itemName = null, IEnumerable<string>? outOfRange = null, Func<string, string, string>? openName = null, bool detail = true) { if (input == null) { throw new ArgumentNullException("input"); } TransferStack transferStack = input.Player.Stacks.FirstOrDefault((TransferStack x) => x.Handle == handle) ?? throw new ArgumentException("Unknown player stack.", "handle"); if (evaluator == null) { evaluator = new ChestRuleEvaluator(); } Names names = new Names(input.Taxonomy, chestName, itemName, openName); string prefab = transferStack.Identity.Prefab; string text = names.Item(prefab); List<ChestExplanation> rows = new List<ChestExplanation>(); List<StashChest> list = input.Chests.Take(256).ToList(); (List<(StashChest Chest, DestinationCandidate Candidate)> Ruled, List<StashChest> Unruled) tuple = StashPlanner.Receivers(list, evaluator, input.Taxonomy); List<(StashChest, DestinationCandidate)> item = tuple.Ruled; List<StashChest> item2 = tuple.Unruled; bool flag = input.Only != null && !input.Only.Contains(transferStack.Handle); bool flag2 = flag || StashPlanner.Kept(input, transferStack); List<StashPlanner.Route> list2 = (flag2 ? new List<StashPlanner.Route>() : StashPlanner.Routes(input, evaluator, item, item2, transferStack)); if (!input.Answers.CategoriesReady && list2.Count == 0 && !flag2) { rows.AddRange(input.Chests.Select((StashChest x) => new ChestExplanation(x.Id, ChestVerdict.CategoriesPending, "categories not ready yet", null))); return Done(PlacementOutcome.NotReady, text + " stays: categories are not ready yet, try again shortly"); } ClassificationResult resolved = input.Answers.Resolve(prefab); IReadOnlyList<FacetEvidence> readOnlyList = input.InstanceFacets?.Invoke(transferStack); FacetEvidence[] facets = ((resolved == null) ? null : ClassificationFilters.InstanceFacets(resolved, readOnlyList)); StashPlanner.Route route = null; HashSet<StashPlanner.Route> hashSet = new HashSet<StashPlanner.Route>(); CleanupReason? cleanupReason = null; PlacementOutcome placementOutcome; string summary; if (flag2) { placementOutcome = PlacementOutcome.Kept; summary = text + " stays: " + (flag ? "not selected" : "protected (equipped, quest item, hotbar or quick slot)"); } else if (list2.Count == 0) { placementOutcome = PlacementOutcome.NoChest; summary = text + " stays: no chest in range takes it"; } else if (!transferStack.TransferSupported) { placementOutcome = PlacementOutcome.Kept; summary = text + " stays: " + CleanupReport.Describe(CleanupReason.Unsupported); } else if (input.Held.Contains(transferStack.Handle)) { placementOutcome = PlacementOutcome.Kept; summary = text + " stays: " + CleanupReport.Describe(CleanupReason.Held); } else { foreach (StashPlanner.Route item3 in list2) { if (item3.Blocked.HasValue) { CleanupReason? cleanupReason2 = cleanupReason; if (!cleanupReason2.HasValue) { cleanupReason = item3.Blocked; } continue; } if (Room(item3.Chest.Inventory, transferStack)) { route = item3; break; } hashSet.Add(item3); } if (route != null) { placementOutcome = PlacementOutcome.Goes; summary = text + " → " + names.Chest(route.Chest.Id) + ": " + Why(route); cleanupReason = null; } else if (cleanupReason.HasValue) { placementOutcome = PlacementOutcome.Blocked; summary = text + " stays: " + CleanupReport.Describe(cleanupReason.Value); } else { placementOutcome = PlacementOutcome.NoRoom; summary = text + " stays: the chests that take it are full"; } } IEnumerable<StashChest> enumerable; if (!detail) { enumerable = Enumerable.Empty<StashChest>(); } else { IEnumerable<StashChest> chests = input.Chests; enumerable = chests; } foreach (StashChest chest in enumerable) { if (!list.Contains(chest)) { rows.Add(new ChestExplanation(chest.Id, ChestVerdict.OverLimit, "beyond this run's chest limit", null)); continue; } StashPlanner.Route route2 = list2.FirstOrDefault((StashPlanner.Route x) => x.Chest == chest); if (route2 != null) { rows.Add((placementOutcome == PlacementOutcome.Kept) ? new ChestExplanation(chest.Id, ChestVerdict.NotConsidered, Why(route2) + "; but the item stays", route2.Ranked) : Routed(chest.Id, route2.Blocked, route2 == route, route != null && !hashSet.Contains(route2), Why(route2), route2.Ranked)); } else if (flag2) { rows.Add(new ChestExplanation(chest.Id, ChestVerdict.NotConsidered, "not considered", null)); } else if (evaluator.Unruled(chest.RulePayload, input.Taxonomy)) { rows.Add(new ChestExplanation(chest.Id, ChestVerdict.NoRule, "no rule, and it holds no " + text, null)); } else { rows.Add(Refusal(chest.Id, chest.RulePayload, prefab, resolved, input.Taxonomy, input.Answers, evaluator, readOnlyList, openName)); } } return Done(placementOutcome, summary, route?.Chest.Id, cleanupReason); PlacementExplanation Done(PlacementOutcome outcome, string summary2, string? chosen = null, CleanupReason? blocked = null) { if (!detail) { rows.Clear(); } else { foreach (string item4 in outOfRange ?? Array.Empty<string>()) { rows.Add(new ChestExplanation(item4, ChestVerdict.OutOfRange, "out of range", null)); } } return new PlacementExplanation(prefab, outcome, chosen, blocked, summary2, rows); } string Why(StashPlanner.Route route3) { if (route3.Tier != StashTier.Holder) { return names.Why(route3.Ranked, resolved, facets); } return "no rule, already holds this item"; } } public static PlacementExplanation Cleanup(CleanupInput input, string sourceId, Guid handle, ChestRuleEvaluator? evaluator = null, Func<string, string>? chestName = null, Func<string, string>? itemName = null, IEnumerable<string>? outOfRange = null, Func<string, string, string>? openName = null, bool detail = true) { if (input == null) { throw new ArgumentNullException("input"); } CleanupChest source = input.Chests.FirstOrDefault((CleanupChest x) => StringComparer.Ordinal.Equals(x.Id, sourceId)) ?? throw new ArgumentException("Unknown source chest.", "sourceId"); TransferStack transferStack = source.Inventory.Stacks.FirstOrDefault((TransferStack x) => x.Handle == handle) ?? throw new ArgumentException("Unknown source stack.", "handle"); if (evaluator == null) { evaluator = new ChestRuleEvaluator(); } Names names = new Names(input.Taxonomy, chestName, itemName, openName); string prefab = transferStack.Identity.Prefab; string text = names.Item(prefab); List<ChestExplanation> rows = new List<ChestExplanation>(); if (!input.Answers.CategoriesReady) { Rest(ChestVerdict.CategoriesPending, "categories not ready yet"); return Done(PlacementOutcome.NotReady, text + " stays: categories are not ready yet, try again shortly"); } if (source.OptOut) { Rest(ChestVerdict.NotConsidered, "not considered"); return Done(PlacementOutcome.OptedOut, text + " stays: its chest is set to \"Don't clean up\""); } List<CleanupChest> list = CleanupPlanner.Considered(input.Chests); if (!list.Contains(source) || !CleanupPlanner.Sources(list).Contains(source.Id)) { Rest(ChestVerdict.NotConsidered, "not considered"); return Done(PlacementOutcome.Skipped, text + " stays: " + CleanupReport.Describe(CleanupReason.ChestLimit)); } List<(CleanupChest, DestinationCandidate)> ruled = CleanupPlanner.Receivers(list, evaluator, input.Taxonomy); CleanupPlanner.Assessment assessment = CleanupPlanner.Assess(input, evaluator, ruled, source, transferStack); ClassificationResult item = assessment.Item; FacetEvidence[] facets = ((item == null) ? null : ClassificationFilters.InstanceFacets(item, input.InstanceFacets?.Invoke(transferStack))); List<CleanupPlanner.Destination> list2 = assessment.Destinations.Where((CleanupPlanner.Destination x) => x.Gate == CleanupGate.Usable).ToList(); CleanupPlanner.Destination destination = assessment.Destinations.FirstOrDefault((CleanupPlanner.Destination x) => x.Gate == CleanupGate.Blocked); CleanupPlanner.Destination destination2 = null; HashSet<CleanupPlanner.Destination> hashSet = new HashSet<CleanupPlanner.Destination>(); CleanupReason? blocked = null; bool flag = true; string text2 = names.Chest(source.Id); PlacementOutcome placementOutcome; string summary; if (!assessment.Kind.HasValue) { placementOutcome = PlacementOutcome.Stays; summary = text + " stays: the rule of " + text2 + " can't be read by this version"; flag = false; } else if (item == null && assessment.Destinations.Count == 0 && !assessment.Stays) { placementOutcome = ((assessment.Kind == CleanupKind.Rejected) ? PlacementOutcome.NoChest : PlacementOutcome.Stays); summary = text + " stays: it has no categories, so no rule but an exact one takes it"; flag = false; } else if (assessment.Stays) { placementOutcome = PlacementOutcome.Stays; flag = false; summary = text + " stays in " + text2 + ": its rule takes it"; } else if (list2.Count == 0) { if (assessment.Kind == CleanupKind.Resort) { placementOutcome = PlacementOutcome.Stays; summary = text + " stays in " + text2 + ": no better chest takes it"; } else if (destination != null) { placementOutcome = PlacementOutcome.Blocked; blocked = destination.Blocked; summary = text + " stays: " + CleanupReport.Describe(blocked.Value); } else { placementOutcome = PlacementOutcome.NoChest; summary = text + " stays: no chest in range takes it"; } } else if (transferStack.Protected || !transferStack.TransferSupported || source.Inventory.ProtectedCells.Contains(transferStack.Y * source.Inventory.Width + transferStack.X)) { placementOutcome = PlacementOutcome.Kept; summary = text + " stays: " + CleanupReport.Describe(CleanupReason.Unsupported); } else if (input.Held.Contains(transferStack.Handle)) { placementOutcome = PlacementOutcome.Kept; summary = text + " stays: " + CleanupReport.Describe(CleanupReason.Held); } else { foreach (CleanupPlanner.Destination item2 in list2) { if (Room(item2.Chest.Inventory, transferStack)) { destination2 = item2; break; } hashSet.Add(item2); } if (destination2 != null) { placementOutcome = PlacementOutcome.Goes; summary = text + " → " + names.Chest(destination2.Chest.Id) + ": " + names.Why(destination2.Ranked, item, facets); } else if (assessment.Kind == CleanupKind.Resort) { placementOutcome = PlacementOutcome.Stays; summary = text + " stays in " + text2 + ": the better chests are full"; } else { placementOutcome = PlacementOutcome.NoRoom; summary = text + " stays: the chests that take it are full"; } } IEnumerable<CleanupChest> enumerable; if (!detail) { enumerable = Enumerable.Empty<CleanupChest>(); } else { IEnumerable<CleanupChest> chests = input.Chests; enumerable = chests; } foreach (CleanupChest chest in enumerable) { if (chest == source) { string text3; switch (assessment.Kind) { case CleanupKind.Rejected: text3 = "where it is; its rule doesn't take it"; break; case CleanupKind.Unruled: text3 = "where it is; no rule"; break; case CleanupKind.Resort: if (assessment.Current != null) { text3 = "where it is; " + names.Why(assessment.Current, item, facets); break; } goto default; default: text3 = "where it is"; break; } string text4 = text3; rows.Add(new ChestExplanation(chest.Id, ChestVerdict.Current, text4, assessment.Current)); } else if (!list.Contains(chest)) { rows.Add(new ChestExplanation(chest.Id, ChestVerdict.OverLimit, "beyond this run's chest limit", null)); } else { CleanupPlanner.Destination destination3 = assessment.Destinations.FirstOrDefault((CleanupPlanner.Destination x) => x.Chest == chest); if (destination3 != null) { string text5 = names.Why(destination3.Ranked, item, facets); List<ChestExplanation> list3 = rows; list3.Add(destination3.Gate switch { CleanupGate.NotBetter => new ChestExplanation(chest.Id, ChestVerdict.NotBetter, text5 + "; no better than " + text2, destination3.Ranked), CleanupGate.RuleMismatch => new ChestExplanation(chest.Id, ChestVerdict.RuleRejects, "its rule doesn't take it", destination3.Ranked), CleanupGate.Blocked => Routed(chest.Id, destination3.Blocked, chosen: false, backup: false, text5, destination3.Ranked), _ => (placementOutcome == PlacementOutcome.Kept) ? new ChestExplanation(chest.Id, ChestVerdict.NotConsidered, text5 + "; but the item stays", destination3.Ranked) : Routed(chest.Id, null, destination3 == destination2, destination2 != null && !hashSet.Contains(destination3), text5, destination3.Ranked), }); } else if (!flag) { rows.Add(new ChestExplanation(chest.Id, ChestVerdict.NotConsidered, "not considered", null)); } else if (evaluator.Unruled(chest.RulePayload, input.Taxonomy)) { rows.Add(new ChestExplanation(chest.Id, ChestVerdict.NoRule, "no rule; cleanup only fills ruled chests", null)); } else { rows.Add(Refusal(chest.Id, chest.RulePayload, prefab, item, input.Taxonomy, input.Answers, evaluator, input.InstanceFacets?.Invoke(transferStack), openName)); } } } return Done(placementOutcome, summary, destination2?.Chest.Id, blocked); PlacementExplanation Done(PlacementOutcome outcome, string summary2, string? chosen = null, CleanupReason? blocked2 = null) { if (!detail) { rows.Clear(); } else { foreach (string item3 in outOfRange ?? Array.Empty<string>()) { rows.Add(new ChestExplanation(item3, ChestVerdict.OutOfRange, "out of range", null)); } } return new PlacementExplanation(prefab, outcome, chosen, blocked2, summary2, rows); } void Rest(ChestVerdict verdict, string text6) { foreach (CleanupChest chest2 in input.Chests) { rows.Add(new ChestExplanation(chest2.Id, (chest2 == source) ? ChestVerdict.Current : verdict, (chest2 == source) ? "where it is" : text6, null)); } } } private static ChestExplanation Routed(string id, CleanupReason? blocked, bool chosen, bool backup, string why, RankedDestination? rank) { if (blocked.HasValue) { return new ChestExplanation(id, blocked switch { CleanupReason.OtherOwnerNoMuc => ChestVerdict.OtherOwnerNoMuc, CleanupReason.OtherOwnerFormatUnknown => ChestVerdict.OtherOwnerFormatUnknown, CleanupReason.BothRemote => ChestVerdict.BothRemote, CleanupReason.AzuExcluded => ChestVerdict.AzuExcluded, _ => ChestVerdict.RuleRejects, }, why + "; but " + CleanupReport.Describe(blocked.Value), rank); } if (chosen) { return new ChestExplanation(id, ChestVerdict.Chosen, why, rank); } if (!backup) { return new ChestExplanation(id, ChestVerdict.Full, why + "; no room", rank); } return new ChestExplanation(id, ChestVerdict.Backup, why + "; ranked lower", rank); } private static ChestExplanation Refusal(string id, string payload, string prefab, ClassificationResult? item, ClassificationTaxonomy taxonomy, ICategoryAnswers answers, ChestRuleEvaluator evaluator, IReadOnlyList<FacetEvidence>? facets, Func<string, string, string>? openName) { ContainerPolicyRead containerPolicyRead = evaluator.Read(payload, taxonomy); if (containerPolicyRead.State == ContainerPolicyReadState.Unsupported) { return new ChestExplanation(id, ChestVerdict.RuleUnreadable, "its rule can't be read by this version", null); } if (containerPolicyRead.Rules == null || containerPolicyRead.Rules.State == ChestRuleState.Unconfigured) { return new ChestExplanation(id, ChestVerdict.RuleCleared, "its rule was cleared", null); } if (containerPolicyRead.Rules.Excludes.Contains<string>(prefab, StringComparer.Ordinal)) { return new ChestExplanation(id, ChestVerdict.Never, "lists it under Never", null); } if (!answers.CategoriesReady && containerPolicyRead.Rules.Acceptance.Rules.Count != 0) { return new ChestExplanation(id, ChestVerdict.CategoriesPending, "categories not ready yet", null); } if (item == null) { return new ChestExplanation(id, ChestVerdict.RuleRejects, "this item has no categories", null); } if (evaluator.Evaluate(payload, prefab, taxonomy, answers, facets) != ChestRuleDecision.Accept) { return new ChestExplanation(id, ChestVerdict.RuleRejects, Missed(containerPolicyRead.Rules, item, ClassificationFilters.InstanceFacets(item, facets), taxonomy, openName), null); } return new ChestExplanation(id, ChestVerdict.RuleRejects, "its rule takes it, but routing ranked it out", null); } private static string Missed(ChestRules rules, ClassificationResult item, FacetEvidence[] facets, ClassificationTaxonomy taxonomy, Func<string, string, string>? openName) { foreach (ClassificationFilterRule rule in rules.Acceptance.Rules) { if (rule.Categories.Count != 0 && !item.Memberships.Any((MembershipEvidence x) => rule.Categories.Contains<string>(x.Category, StringComparer.Ordinal))) { continue; } foreach (KeyValuePair<string, IReadOnlyList<string>> filter in rule.Filters.Where<KeyValuePair<string, IReadOn
plugins/YAVSOS.dll
Decompiled 13 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Buffers; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Serialization; 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 HarmonyLib; using JetBrains.Annotations; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using ServerSync; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; using YAVSOS.Catalog; using YAVSOS.Classification; using YAVSOS.Cleanup; using YAVSOS.Configuration; using YAVSOS.Core.Catalog; using YAVSOS.Core.Classification; using YAVSOS.Core.Interface; using YAVSOS.Core.Policy; using YAVSOS.Core.Transfers; using YAVSOS.Integrations; using YAVSOS.Interface; using YAVSOS.Networking; using YAVSOS.Sources; using YAVSOS.Transfers; using YAVSOS.Vendor.MultiUserChest; using YAVSOS.Vendor.MultiUserChest.Patches; [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("BastionNtB")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (C) 2026 BastionNtB")] [assembly: AssemblyDescription("Valheim plugin entry point and game adapters for YAVSOS.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+d5aaa1b9ccbb9dd00e376b293e718c38397c8336")] [assembly: AssemblyProduct("Yet Another Valheim Storage Organization System")] [assembly: AssemblyTitle("YAVSOS")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.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 YAVSOS { [BepInPlugin("com.bastionntb.yavsos", "Yet Another Valheim Storage Organization System", "1.0.0")] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { private ConfigSync sync; private PluginSettings settings; private PolicyHost policy; private Harmony harmony; private ZNet? activeNet; private ZNetPeer? activeServerPeer; private readonly PeerIdentityGate peerGate = new PeerIdentityGate(); private readonly ServerSyncSettingsBridge settingsBridge = new ServerSyncSettingsBridge(); private Action<long, ConfigEntryBase[]> broadcastSettings; private Action publishSettings; private ConfigEntryBase[] sharedEntries; private LoadedCatalogHost catalog; private ClassificationHost classification; private LoadedSourceHost sources; private AssignmentHost assignments; private OptionalEpicLootRegistrationHost? epicLootRegistration; private readonly ClassificationTaxonomy defaultTaxonomy = DefaultClassificationTaxonomy.Create(); private CategoryPolicy? taxonomyPolicy; private ClassificationTaxonomy? policyTaxonomy; private RuleControls? ruleControls; internal static Plugin? Instance { get; private set; } internal ChestRuleStore ChestRules { get; private set; } internal ChestRuleEvaluator RuleEvaluator { get; } = new ChestRuleEvaluator(256, 65536); internal RuleControls? RuleEditor => ruleControls; internal CleanupRunner Cleanup { get; private set; } internal StashRunner Stash { get; private set; } internal RunResults Results { get; } = new RunResults(); internal ChestHighlight Highlight { get; private set; } internal PluginSettings Settings => settings; internal bool IsManagedServer { get { if ((Object)(object)activeNet != (Object)null) { return activeNet.IsServer(); } return false; } } internal bool ServerWithoutYavsos => (int)policy.State.Mode == 2; internal CatalogSnapshot? LoadedCatalog => catalog?.Current; internal ICategoryAnswers? CategoryAnswers => assignments?.Answers; internal bool EpicLootRarityOffered => sources?.EpicLootRarityOffered ?? false; internal OptionalCapabilityState EpicLootState => (OptionalCapabilityState)(((??)sources?.EpicLootState) ?? 0); internal EpicLootRegistrationState EpicLootRegistration => epicLootRegistration?.State ?? EpicLootRegistrationState.Absent; internal string? CategoriesGaveUp { get { object obj = catalog?.GaveUp; if (obj == null) { obj = sources?.GaveUp; if (obj == null) { if (!PolicyFromServer) { return classification?.GaveUp; } obj = null; } } return (string?)obj; } } internal bool PolicyFromServer => (int)policy.State.Mode == 5; internal bool CategoriesRebuilding => assignments?.CategoriesRebuilding ?? false; internal ClassificationTaxonomy RuleTaxonomy { get { CategoryPolicy val = (policy.State.PolicyReady ? policy.State.Effective : null); if (val == null) { return defaultTaxonomy; } if (val != taxonomyPolicy) { taxonomyPolicy = val; policyTaxonomy = defaultTaxonomy.WithPolicy(val); } return policyTaxonomy; } } internal bool ClassificationEvidenceReady { get { AssignmentHost assignmentHost = assignments; if (assignmentHost == null) { return false; } return assignmentHost.Answers.CategoriesReady; } } private void Awake() { //IL_02af: Unknown result type (might be due to invalid IL or missing references) //IL_02b9: Expected O, but got Unknown sync = new ConfigSync("com.bastionntb.yavsos") { DisplayName = "Yet Another Valheim Storage Organization System", CurrentVersion = "1.0.0", MinimumRequiredVersion = "1.0.0", ModRequired = false }; policy = new PolicyHost(sync, Paths.ConfigPath, ((BaseUnityPlugin)this).Logger); catalog = new LoadedCatalogHost(((BaseUnityPlugin)this).Logger); sources = new LoadedSourceHost(((BaseUnityPlugin)this).Logger); classification = new ClassificationHost((Action<string>)((BaseUnityPlugin)this).Logger.LogInfo, (Action<string>)((BaseUnityPlugin)this).Logger.LogWarning, policy.State, (Func<CatalogSnapshot?>)(() => catalog.Current), (ClassificationCache?)null, (Func<ClassificationSourceSnapshot?>?)(() => sources.Current), requireSources: true); assignments = new AssignmentHost(sync, policy.State, ((BaseUnityPlugin)this).Logger, () => catalog.Current, () => classification.Current, () => sources.Current); ChestRules = new ChestRuleStore(() => RuleTaxonomy, () => policy.State.PolicyReady); settings = new PluginSettings(((BaseUnityPlugin)this).Config, sync, (Action)policy.State.SharedSettingsChanged); sharedEntries = settings.SharedEntries; Cleanup = new CleanupRunner(settings, ((BaseUnityPlugin)this).Logger, Results); Stash = new StashRunner(settings, ((BaseUnityPlugin)this).Logger, Results); Highlight = new ChestHighlight(((BaseUnityPlugin)this).Logger); ruleControls = new RuleControls(settings.ShowRuleControls, settings.ShowResultPanel, settings.RulePanelOffsetX, settings.RulePanelOffsetY, Cleanup, Stash, Results, Highlight, ((BaseUnityPlugin)this).Logger); MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ConfigSync), "Broadcast", new Type[2] { typeof(long), typeof(ConfigEntryBase[]) }, (Type[])null); if (methodInfo == null || methodInfo.ReturnType != typeof(void)) { throw new NotSupportedException("Pinned ServerSync settings-publication contract is unavailable."); } broadcastSettings = (Action<long, ConfigEntryBase[]>)Delegate.CreateDelegate(typeof(Action<long, ConfigEntryBase[]>), sync, methodInfo); publishSettings = delegate { broadcastSettings(0L, sharedEntries); }; Instance = this; harmony = new Harmony("com.bastionntb.yavsos.policy-lifecycle"); harmony.PatchAll(typeof(NetPolicyPatches)); harmony.PatchAll(typeof(ServerPolicyRpcGuard)); harmony.PatchAll(typeof(ServerPolicyRoutedGuard)); harmony.PatchAll(typeof(ServerSettingsAcceptedPatch)); harmony.PatchAll(typeof(LocalSharedSettingsSavePatch)); PatchView(typeof(CatalogRegistryPatches), "Catalog refresh on ObjectDB.UpdateRegisters"); PatchView(typeof(CleanupConsoleCommand), "The \"yavsos cleanup\" console command"); ContainerRegistry.Initialize(harmony, ((BaseUnityPlugin)this).Logger); ChestLabels.Log = ((BaseUnityPlugin)this).Logger; PatchView(typeof(ChestLabels), "Chest labels"); ItemTooltips.Log = ((BaseUnityPlugin)this).Logger; PatchView(typeof(ItemTooltips), "Item category tooltips"); PatchView(typeof(WhereTooltip), "\"Where would it go?\" in item tooltips"); PatchView(typeof(ItemOrigins), "Base-game item origin"); PatchView(typeof(BaseGameCopy), "Base-game item origin (menu copy)"); epicLootRegistration = new OptionalEpicLootRegistrationHost(InvalidateLoadedCatalog, ((BaseUnityPlugin)this).Logger); AzuAutoStoreIntegration.Initialize(((BaseUnityPlugin)this).Logger); RossItemDrawersIntegration.Initialize(((BaseUnityPlugin)this).Logger); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Yet Another Valheim Storage Organization System 1.0.0 loaded. Policy synchronization enabled; chest rules: " + AzuAutoStoreIntegration.Status)); } private void Start() { MucRoute.Initialize(((BaseUnityPlugin)this).Logger); if (!ServerYavsosProbe.Supported) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Server detection turned off: the bundled ServerSync changed. On a server without YAVSOS chest rules stay off."); } ruleControls?.Probe(); _ = Highlight.Supported; } private void Update() { long started = UiCost.Start(); UpdateCore(); UiCost.Stop("plugin update", started); } private void UpdateCore() { policy?.Update(Time.unscaledTime); epicLootRegistration?.Update(); OptionalEpicLootRegistrationHost? optionalEpicLootRegistrationHost = epicLootRegistration; if (optionalEpicLootRegistrationHost == null || !optionalEpicLootRegistrationHost.Pending) { catalog?.Update(); } sources?.Update(catalog?.Current); classification?.Update(); assignments?.Update(); if ((Object)(object)activeNet != (Object)null) { NativeInventoryProbe.Update((Action<string>)((BaseUnityPlugin)this).Logger.LogInfo, (Action<string>)((BaseUnityPlugin)this).Logger.LogWarning); } MucRoute.Update(); Cleanup?.Update(); Stash?.Update(); if (Cleanup != null && !Cleanup.Running && CleanupTriggers.Pressed(settings.CleanupShortcut)) { Cleanup.Start(); } else if (Stash != null && !Stash.Running) { StashTriggers.Poll(settings, Stash); } settingsBridge.Publish(IsManagedServer, ConfigSync.ProcessingServerUpdate, publishSettings); } private void PatchView(Type patch, string name) { try { harmony.PatchAll(patch); } catch (Exception ex) when (OptionalFailure.Recoverable(ex)) { ((BaseUnityPlugin)this).Logger.LogWarning((object)(name + " turned off: " + ex.GetType().Name + ": " + ex.Message)); } } private void LateUpdate() { UiCost.Frame(); long started = UiCost.Start(); ruleControls?.LateUpdate(); UiCost.Stop("panel frame", started); } internal void StartWorld(ZNet net) { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) if (activeNet != net) { activeNet = net; catalog.BeginSession(isActive: true); classification.BeginSession(active: true); sources.BeginSession(isActive: true); assignments.BeginSession(active: true); settingsBridge.BeginSession(); NativeInventoryProbe.BeginSession(); sync.ModRequired = net.IsServer() && !ZNet.IsSinglePlayer; PolicyMode mode = (PolicyMode)((!net.IsServer()) ? 3 : (ZNet.IsSinglePlayer ? 1 : 4)); policy.BeginSession(mode); } } internal void EndWorld(ZNet net) { if (activeNet == net) { activeNet = null; Cleanup?.EndWorld(); Stash?.EndWorld(); Results.Dismiss(); Highlight?.Clear(); catalog.BeginSession(isActive: false); classification.BeginSession(active: false); sources.BeginSession(isActive: false); assignments.BeginSession(active: false); settingsBridge.BeginSession(); activeServerPeer = null; peerGate.Clear(); policy.BeginSession((PolicyMode)0); sync.ModRequired = false; } } internal void StartConnection(ZNet net, ZNetPeer peer) { StartWorld(net); if (!net.IsServer() && activeServerPeer != peer) { activeServerPeer = peer; catalog.BeginSession(isActive: true); classification.BeginSession(active: true); sources.BeginSession(isActive: true); assignments.BeginSession(active: true); peerGate.Replace(peer.m_rpc); policy.BeginSession((PolicyMode)3); } } internal void ServerPeerInfo(ZNet net, ZRpc rpc) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Invalid comparison between Unknown and I4 //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Invalid comparison between Unknown and I4 //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_007e: Invalid comparison between Unknown and I4 //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Invalid comparison between Unknown and I4 //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) if (activeNet != net || net.IsServer() || activeServerPeer == null || !peerGate.Accepts(rpc)) { return; } if ((int)policy.State.Mode == 5) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Server has YAVSOS: its category settings already arrived."); } else { if ((int)policy.State.Mode != 3) { return; } bool received; ServerYavsos val = ServerDetection.AtHandshake(ServerYavsosProbe.TryVersionReceived(out received), received, !sync.IsSourceOfTruth); if ((int)val != 1) { if ((int)val == 2) { PolicyLifecycle state = policy.State; PolicyStamp stamp = policy.State.Stamp; if (state.ServerLacksYavsos(((PolicyStamp)(ref stamp)).Session)) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Server without YAVSOS: this game's own categories and settings apply (D044)."); } } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Cannot tell whether the server has YAVSOS (ServerSync changed); chest rules wait for server settings."); } } else { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Server has YAVSOS: waiting for its category settings."); } } } internal void AdvertiseClientVersion(ZNet net, ZNetPeer peer) { if (activeNet == net && activeServerPeer == peer && peerGate.Accepts(peer.m_rpc) && ServerSyncVersionAdvertisement.TrySend(net.IsServer(), sync.ModRequired, "1.0.0", "1.0.0", delegate(string rpcName, byte[] payload) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown peer.m_rpc.Invoke(rpcName, new object[1] { (object)new ZPackage(payload) }); })) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Advertising YAVSOS version 1.0.0, minimum 1.0.0 to the server; unmanaged-server use remains optional."); } } internal bool IsServerConnection(ZRpc rpc) { if ((Object)(object)activeNet != (Object)null && !activeNet.IsServer()) { return peerGate.Accepts(rpc); } return false; } internal bool IsServerSender(long sender) { if ((Object)(object)activeNet != (Object)null && !activeNet.IsServer() && activeServerPeer != null && activeServerPeer.m_uid != 0L && activeServerPeer.m_uid == sender && policy.IncomingRpc != null) { return peerGate.Accepts(policy.IncomingRpc); } return false; } internal bool IsActiveServerPeer(ZNetPeer peer) { return activeServerPeer == peer; } internal void InvalidateLoadedCatalog() { catalog?.Invalidate(); sources?.Invalidate(); classification?.InvalidateCatalog(); assignments?.Invalidate(); } internal IReadOnlyList<FacetEvidence> InstanceFacets(ItemData? item, ICategoryAnswers answers) { GameObject val = item?.m_dropPrefab; if (item == null || !Object.op_Implicit((Object)(object)val) || answers == null) { return Array.Empty<FacetEvidence>(); } ClassificationResult val2 = null; try { val2 = answers.Resolve(((Object)val).name); if (val2 == null) { return Array.Empty<FacetEvidence>(); } List<FacetEvidence> list = new List<FacetEvidence>(4); if (val2.InstanceRarity) { IReadOnlyList<FacetEvidence> collection; if (sources == null) { IReadOnlyList<FacetEvidence> readOnlyList = (IReadOnlyList<FacetEvidence>)(object)new FacetEvidence[1] { ClassificationFilters.UnreadableRow("rarity") }; collection = readOnlyList; } else { collection = sources.FilterEquipmentRarity(val2, item); } list.AddRange(collection); } SharedData shared = item.m_shared; list.AddRange(ClassificationFilters.Condition(Math.Max(1, item.m_quality), shared?.m_useDurability ?? false, item.m_durability, (shared != null) ? item.GetMaxDurability() : 0f, UpgradeRecipes.Can(((Object)val).name))); FacetEvidence val3 = ClassificationFilters.Crafter(item.m_crafterID); if (val3 != null) { list.Add(val3); } return list; } catch (Exception ex) when (!(ex is OutOfMemoryException)) { IReadOnlyList<FacetEvidence> result; if (val2 != null) { result = ClassificationFilters.UnreadableInstance(val2); } else { IReadOnlyList<FacetEvidence> readOnlyList = Array.Empty<FacetEvidence>(); result = readOnlyList; } return result; } } internal static IReadOnlyList<FacetEvidence> UnreadableFacets(string prefab, ICategoryAnswers answers) { ClassificationResult val = ((answers != null) ? answers.Resolve(prefab) : null); if (val != null) { return ClassificationFilters.UnreadableInstance(val); } return Array.Empty<FacetEvidence>(); } internal IReadOnlyList<FacetEvidence>? InstanceFacetsFor(string payload, string prefab, ItemData? item, ICategoryAnswers answers) { if (RuleEvaluator.UsesInstanceFilters(payload, RuleTaxonomy)) { if (item == null) { return UnreadableFacets(prefab, answers); } return InstanceFacets(item, answers); } return null; } internal ClassificationResult? ResolveCategories(string prefab) { AssignmentHost assignmentHost = assignments; if (assignmentHost == null) { return null; } return assignmentHost.Answers.Resolve(prefab); } internal int CategoryMemberCount(string category) { return assignments?.CategoryMemberCount(category) ?? 0; } internal long? CaptureSettingsUpdate(long sender, ZPackage package) { ZRpc incoming = policy.IncomingRpc; bool currentPeer = (Object)(object)activeNet != (Object)null && incoming != null && activeNet.GetPeers().Any((ZNetPeer peer) => peer.m_uid != 0L && peer.m_uid == sender && peer.m_rpc == incoming); long? result = settingsBridge.CaptureUpdate(IsManagedServer, clientUpdate: true, currentPeer); if (!result.HasValue) { return null; } if (!ServerSyncSettingsBridge.ValidateRequest(package.GetArray(), sharedEntries.Length, settings.SharedType, out string diagnostic)) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Client settings request rejected: " + diagnostic)); return null; } return result; } internal void CompleteSettingsUpdate(long? captured, bool accepted) { settingsBridge.CompleteUpdate(captured, accepted); } internal bool TrySerializeLocalShared(ConfigEntryBase entry, out string serialized) { serialized = ""; if (entry.ConfigFile != ((BaseUnityPlugin)this).Config) { return false; } OwnConfigEntryBase ownConfigEntryBase = entry.Description.Tags.OfType<OwnConfigEntryBase>().FirstOrDefault(); if (ownConfigEntryBase != null) { return ServerSyncSettingsBridge.TrySerializeLocal(!sync.IsSourceOfTruth, ownConfigEntryBase.SynchronizedConfig, ownConfigEntryBase.LocalBaseValue, entry.SettingType, (Func<object, Type, string>)TomlTypeConverter.ConvertToString, out serialized); } return false; } private void OnDestroy() { Instance = null; epicLootRegistration?.Dispose(); policy?.Dispose(); catalog?.Dispose(); classification?.Dispose(); sources?.Dispose(); assignments?.Dispose(); Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } BundledCopy.Deactivate(); } } internal static class NetPolicyPatches { [HarmonyPatch(typeof(ZNet), "Awake")] [HarmonyPostfix] [HarmonyPriority(800)] private static void WorldStarted(ZNet __instance) { Plugin.Instance?.StartWorld(__instance); } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] [HarmonyPrefix] [HarmonyPriority(800)] private static void Reconnecting(ZNet __instance, ZNetPeer peer) { Plugin.Instance?.StartConnection(__instance, peer); } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] [HarmonyPrefix] [HarmonyPriority(0)] private static void AdvertiseVersion(ZNet __instance, ZNetPeer peer) { Plugin.Instance?.AdvertiseClientVersion(__instance, peer); } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] [HarmonyPostfix] private static void PeerInfo(ZNet __instance, ZRpc rpc) { Plugin.Instance?.ServerPeerInfo(__instance, rpc); } [HarmonyPatch(typeof(ZNet), "Shutdown")] [HarmonyPrefix] private static void WorldStopping(ZNet __instance) { Plugin.Instance?.EndWorld(__instance); } [HarmonyPatch(typeof(ZNet), "OnDestroy")] [HarmonyPrefix] private static void WorldDestroyed(ZNet __instance) { Plugin.Instance?.EndWorld(__instance); } [HarmonyPatch(typeof(ZNet), "Disconnect")] [HarmonyPrefix] private static void PeerDisconnected(ZNet __instance, ZNetPeer peer) { if (!__instance.IsServer()) { Plugin? instance = Plugin.Instance; if (instance != null && instance.IsActiveServerPeer(peer)) { Plugin.Instance.EndWorld(__instance); } } } } [HarmonyPatch(typeof(ConfigSync), "RPC_FromServerConfigSync")] internal static class ServerPolicyRpcGuard { [HarmonyPrefix] private static bool FromCurrentServer(ConfigSync __instance, ZRpc rpc) { if (!(__instance.Name != "com.bastionntb.yavsos")) { return Plugin.Instance?.IsServerConnection(rpc) ?? false; } return true; } } [HarmonyPatch(typeof(ConfigSync), "RPC_FromOtherClientConfigSync")] internal static class ServerPolicyRoutedGuard { [HarmonyPrefix] private static bool FromCurrentSession(ConfigSync __instance, long sender) { if (!(__instance.Name != "com.bastionntb.yavsos")) { Plugin? instance = Plugin.Instance; if (instance == null || !instance.IsManagedServer) { return Plugin.Instance?.IsServerSender(sender) ?? false; } } return true; } } [HarmonyPatch(typeof(ConfigSync), "HandleConfigSyncRPC")] internal static class ServerSettingsAcceptedPatch { [HarmonyPrefix] private static bool Before(ConfigSync __instance, long sender, ZPackage package, bool clientUpdate, out long? __state) { __state = null; if (!(__instance.Name != "com.bastionntb.yavsos") && clientUpdate) { Plugin? instance = Plugin.Instance; if (instance != null && instance.IsManagedServer) { __state = Plugin.Instance.CaptureSettingsUpdate(sender, package); return __state.HasValue; } } return true; } [HarmonyPostfix] private static void After(ConfigSync __instance, bool __result, long? __state) { if (__instance.Name == "com.bastionntb.yavsos") { Plugin.Instance?.CompleteSettingsUpdate(__state, __result); } } } [HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")] internal static class LocalSharedSettingsSavePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool PreserveLocal(ConfigEntryBase __instance, ref string __result) { Plugin instance = Plugin.Instance; if ((Object)(object)instance == (Object)null || !instance.TrySerializeLocalShared(__instance, out string serialized)) { return true; } __result = serialized; return false; } } } namespace YAVSOS.Transfers { internal enum RuleEligibility { Eligible, Missing, Tombstone, ItemDrawer, NotPlayerBuilt, OnCharacter } internal sealed class ChestRuleResult { internal TransferRefusal Reason { get; } internal string Message { get; } internal ContainerPolicyRead? Policy { get; } internal bool Succeeded => (int)Reason == 0; internal ChestRuleResult(TransferRefusal reason, string message, ContainerPolicyRead? policy = null) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) Reason = reason; Message = message; Policy = policy; } } internal sealed class ChestRuleStore { private const string RossDrawerType = "DrawerComponent"; private static readonly FieldRef<Container, ZNetView>? ViewField; private static readonly Func<Container, long, bool>? CheckAccess; private readonly Func<ClassificationTaxonomy> taxonomy; private readonly Func<bool> policyReady; internal const string CleanupOptOutKey = "com.bastionntb.yavsos.cleanup-optout"; internal static string BindingFailure { get; } static ChestRuleStore() { BindingFailure = ""; try { ViewField = AccessTools.FieldRefAccess<Container, ZNetView>("m_nview"); CheckAccess = AccessTools.MethodDelegate<Func<Container, long, bool>>(AccessTools.DeclaredMethod(typeof(Container), "CheckAccess", new Type[1] { typeof(long) }, (Type[])null) ?? throw new MissingMethodException("Container", "CheckAccess"), (object)null, true); } catch (Exception ex) { ViewField = null; CheckAccess = null; BindingFailure = "Chest rules unavailable: " + ex.GetType().Name; } } internal static ZNetView? View(Container container) { if (!Object.op_Implicit((Object)(object)container) || ViewField == null) { return null; } return ViewField.Invoke(container); } internal ChestRuleStore(Func<ClassificationTaxonomy> taxonomy, Func<bool> policyReady) { this.taxonomy = taxonomy ?? throw new ArgumentNullException("taxonomy"); this.policyReady = policyReady ?? throw new ArgumentNullException("policyReady"); } internal static RuleEligibility Eligibility(Container? container) { if (Object.op_Implicit((Object)(object)container) && IsRossDrawer(container)) { return RuleEligibility.ItemDrawer; } ZNetView val = View(container); if (!Object.op_Implicit((Object)(object)val) || !val.IsValid() || val.GetZDO() == null || container.GetInventory() == null) { return RuleEligibility.Missing; } if (Object.op_Implicit((Object)(object)((Component)container).GetComponentInParent<TombStone>())) { return RuleEligibility.Tombstone; } Character componentInParent = ((Component)container).GetComponentInParent<Character>(); if (Object.op_Implicit((Object)(object)componentInParent) && (Object)(object)componentInParent != (Object)(object)Player.m_localPlayer) { return RuleEligibility.OnCharacter; } if (val.GetZDO().GetLong(ZDOVars.s_creator, 0L) == 0L) { return RuleEligibility.NotPlayerBuilt; } return RuleEligibility.Eligible; } internal static bool IsRossDrawer(Container container) { Type type = ((object)container).GetType(); while (type != null && type != typeof(Container)) { if (type.Name == "DrawerComponent") { return true; } type = type.BaseType; } return false; } internal static string Describe(RuleEligibility eligibility) { return eligibility switch { RuleEligibility.Eligible => "", RuleEligibility.Tombstone => "Tombstones cannot have YAVSOS rules.", RuleEligibility.ItemDrawer => "Item drawers use their own item assignment instead of a YAVSOS rule.", RuleEligibility.NotPlayerBuilt => "Only player-built containers can have YAVSOS rules.", RuleEligibility.OnCharacter => "Containers carried by a creature cannot have YAVSOS rules.", _ => "This container is not available.", }; } internal ChestRuleResult Read(Container container) { RuleEligibility ruleEligibility = Eligibility(container); if (ruleEligibility != RuleEligibility.Eligible) { return new ChestRuleResult((TransferRefusal)4, Describe(ruleEligibility)); } string text = Payload(container); return new ChestRuleResult((TransferRefusal)0, "", ContainerPolicyEnvelope.Read(text, taxonomy())); } internal static string Payload(Container container) { ZNetView? obj = View(container); object obj2; if (obj == null) { obj2 = null; } else { ZDO zDO = obj.GetZDO(); obj2 = ((zDO != null) ? zDO.GetString("com.bastionntb.yavsos.container-policy", "") : null); } if (obj2 == null) { obj2 = ""; } return (string)obj2; } internal static bool CleanupOptOut(Container container) { ZNetView? obj = View(container); if (obj == null) { return false; } ZDO zDO = obj.GetZDO(); return ((zDO != null) ? new bool?(zDO.GetBool("com.bastionntb.yavsos.cleanup-optout", false)) : ((bool?)null)) == true; } internal static bool HasAccess(Container container, Player player) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (CheckAccess != null && CheckAccess(container, player.GetPlayerID())) { if (container.m_checkGuardStone) { return PrivateArea.CheckAccess(((Component)container).transform.position, 0f, false, false); } return true; } return false; } internal ChestRuleResult TrySetCleanupOptOut(Container container, bool optOut) { RuleEligibility ruleEligibility = Eligibility(container); if (ruleEligibility != RuleEligibility.Eligible) { return new ChestRuleResult((TransferRefusal)4, Describe(ruleEligibility)); } Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer) || ((Character)localPlayer).IsDead()) { return new ChestRuleResult((TransferRefusal)11, "No local player."); } ZNetView val = View(container); if (!val.IsOwner()) { return new ChestRuleResult((TransferRefusal)3, "Another player is using this chest. Change it after they close it."); } if (!HasAccess(container, localPlayer)) { return new ChestRuleResult((TransferRefusal)1, "You do not have access to this chest."); } val.GetZDO().Set("com.bastionntb.yavsos.cleanup-optout", optOut); if (CleanupOptOut(container) != optOut) { return new ChestRuleResult((TransferRefusal)14, "The setting could not be confirmed. Reopen the chest to check it."); } return new ChestRuleResult((TransferRefusal)0, optOut ? "Cleanup leaves this container alone." : "Cleanup may take misplaced items out of this container."); } internal ChestRuleResult TryEdit(Container container, string expectedPayload, ChestRuleEdit? edit) { //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: 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_00f9: Invalid comparison between Unknown and I4 //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) if (expectedPayload == null) { throw new ArgumentNullException("expectedPayload"); } RuleEligibility ruleEligibility = Eligibility(container); if (ruleEligibility != RuleEligibility.Eligible) { return new ChestRuleResult((TransferRefusal)4, Describe(ruleEligibility)); } if (!policyReady()) { return new ChestRuleResult((TransferRefusal)4, "YAVSOS is still loading its settings. Try again shortly."); } Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer) || ((Character)localPlayer).IsDead()) { return new ChestRuleResult((TransferRefusal)11, "No local player."); } ZNetView val = View(container); ZDO zDO = val.GetZDO(); if (!val.IsOwner()) { return new ChestRuleResult((TransferRefusal)3, "Another player is using this chest. Edit its rule after they close it."); } if (!CheckAccess(container, localPlayer.GetPlayerID()) || (container.m_checkGuardStone && !PrivateArea.CheckAccess(((Component)container).transform.position, 0f, true, false))) { return new ChestRuleResult((TransferRefusal)1, "You do not have access to this chest."); } string text = zDO.GetString("com.bastionntb.yavsos.container-policy", ""); ClassificationTaxonomy val2 = taxonomy(); string text2 = default(string); TransferRefusal val3 = ContainerPolicyEnvelope.TryEdit(text, expectedPayload, val2, edit, ref text2); if ((int)val3 == 6) { return new ChestRuleResult(val3, "The rule changed since you opened it. Reopen it and try again.", ContainerPolicyEnvelope.Read(text, val2)); } if ((int)val3 != 0) { return new ChestRuleResult(val3, "This chest has a rule from a different YAVSOS version or category set; it was left unchanged.", ContainerPolicyEnvelope.Read(text, val2)); } zDO.Set("com.bastionntb.yavsos.container-policy", text2); string text3 = zDO.GetString("com.bastionntb.yavsos.container-policy", ""); if (!val.IsOwner() || !StringComparer.Ordinal.Equals(text3, text2)) { return new ChestRuleResult((TransferRefusal)14, "The rule could not be confirmed. Reopen the chest to check it.", ContainerPolicyEnvelope.Read(text3, val2)); } return new ChestRuleResult((TransferRefusal)0, (text2.Length == 0) ? "Rule cleared." : "Rule saved.", ContainerPolicyEnvelope.Read(text3, val2)); } } internal sealed class GameInventorySnapshotAdapter : IDisposable { private sealed class InventoryBinding { internal readonly Guid Generation = Guid.NewGuid(); internal readonly Dictionary<ItemData, Guid> Handles = new Dictionary<ItemData, Guid>(); internal ulong Epoch; internal string LastPayload = ""; } internal const string NativeAdapter = "valheim-item-110-exact-1"; private readonly Dictionary<Inventory, InventoryBinding> bindings = new Dictionary<Inventory, InventoryBinding>(); private readonly List<ItemData> mergeClasses = new List<ItemData>(); private readonly int threadId = Environment.CurrentManagedThreadId; private bool disposed; private static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); private const float DurabilitySaveStep = 0.011f; internal GameInventoryCapture Capture(Inventory inventory, string inventoryId, long owner, ushort ownerRevision, Func<ItemData, string?> registration, Func<ItemData, bool> protectedItem, IEnumerable<int> protectedCells, Func<bool> capabilityCurrent, bool keepUncapturable = false) { //IL_0429: Unknown result type (might be due to invalid IL or missing references) //IL_0430: Expected O, but got Unknown //IL_04b0: Unknown result type (might be due to invalid IL or missing references) //IL_04c0: Expected O, but got Unknown //IL_04bb: Unknown result type (might be due to invalid IL or missing references) //IL_04db: Expected O, but got Unknown //IL_03a1: Unknown result type (might be due to invalid IL or missing references) //IL_03ab: Expected O, but got Unknown CheckThread(); if (!capabilityCurrent() || !NativeInventoryProbe.Supported || inventory == null || ((object)inventory).GetType() != typeof(Inventory)) { throw new NotSupportedException("The native inventory capability is unavailable."); } int width = inventory.GetWidth(); int height = inventory.GetHeight(); if (width < 1 || height < 1 || width > 256 || height > 256) { throw new NotSupportedException("Inventory layout cannot round-trip through the native format."); } List<ItemData> allItems = inventory.GetAllItems(); if (allItems.Count > 65535 || allItems.Count > width * height) { throw new InvalidDataException("Inventory occupancy is unsupported."); } HashSet<int> hashSet = new HashSet<int>(); foreach (int protectedCell in protectedCells) { if (protectedCell < 0 || protectedCell >= width * height || (hashSet.Count >= width * height && !hashSet.Contains(protectedCell))) { throw new InvalidDataException("Protected cell is outside the native grid."); } hashSet.Add(protectedCell); } if (!bindings.TryGetValue(inventory, out InventoryBinding value)) { if (bindings.Count >= 257) { throw new InvalidOperationException("Run inventory binding limit reached."); } value = new InventoryBinding(); bindings.Add(inventory, value); } HashSet<ItemData> seen = new HashSet<ItemData>(); List<TransferStack> list = new List<TransferStack>(allItems.Count); List<string> list2 = new List<string>(); long num = 6L; bool flag = default(bool); foreach (ItemData item in allItems) { if (item == null || !seen.Add(item)) { throw new InvalidDataException("Duplicate live item reference."); } if (!value.Handles.TryGetValue(item, out var value2)) { value.Handles.Add(item, value2 = Guid.NewGuid()); } TransferItemIdentity val; try { num += PreflightBytes(item); if (num > 1048576) { throw new InvalidDataException("Native inventory preimage exceeds its bound."); } val = Preflighted(item, registration(item)); VerifyNativeRoundtrip(item, val); } catch (Exception ex) when (((Func<bool>)delegate { // Could not convert BlockContainer to single expression flag = keepUncapturable; if (flag) { flag = ((ex is NotSupportedException || ex is InvalidDataException || ex is ArgumentException) ? true : false); } return flag && num <= 1048576; }).Invoke()) { Guid handle = value2; int isProtected; if (!item.m_equipped) { SharedData shared = item.m_shared; if (shared == null || !shared.m_questItem) { isProtected = (protectedItem(item) ? 1 : 0); goto IL_0292; } } isProtected = 1; goto IL_0292; IL_0292: list.Add(Uncapturable(item, handle, width, height, (byte)isProtected != 0)); list2.Add((Object.op_Implicit((Object)(object)item.m_dropPrefab) ? ((Object)item.m_dropPrefab).name : (item.m_shared?.m_name ?? "unknown item")) + " at (" + item.m_gridPos.x + "," + item.m_gridPos.y + ") (" + ex.Message.TrimEnd('.') + ")"); continue; } list.Add(new TransferStack(value2, item.m_gridPos.x, item.m_gridPos.y, item.m_stack, item.m_shared.m_maxStackSize, val, item.m_equipped || item.m_shared.m_questItem || protectedItem(item), true, MergeClass(item))); } ItemData[] array = value.Handles.Keys.Where((ItemData item) => !seen.Contains(item)).ToArray(); foreach (ItemData key in array) { value.Handles.Remove(key); } ZPackage val2 = new ZPackage(); inventory.Save(val2); byte[] array2 = val2.GetArray(); if (array2.Length > 1048576) { throw new InvalidDataException("Native inventory preimage exceeds its bound."); } string text = Hash(array2); if (value.LastPayload != text) { if (value.Epoch == ulong.MaxValue) { throw new InvalidOperationException("Inventory epoch exhausted."); } value.Epoch++; value.LastPayload = text; } TransferInventorySnapshot val3 = new TransferInventorySnapshot(new TransferInventoryStamp(inventoryId, value.Generation, value.Epoch, 0uL, owner, ownerRevision, text), width, height, (IEnumerable<TransferStack>)list, (IEnumerable<int>)hashSet); if (!capabilityCurrent()) { throw new NotSupportedException("Inventory capability changed during capture."); } return new GameInventoryCapture(val3, list2); } private static TransferStack Uncapturable(ItemData item, Guid handle, int width, int height, bool isProtected) { //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Expected O, but got Unknown //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Expected O, but got Unknown int x = item.m_gridPos.x; int y = item.m_gridPos.y; if (x < 0 || y < 0 || x >= width || y >= height) { throw new NotSupportedException("An unreadable item is outside the inventory grid."); } int num = Math.Max(1, Math.Min(item.m_stack, 65535)); int num2 = Math.Max(num, Math.Min(item.m_shared?.m_maxStackSize ?? 1, 65535)); string text = (Object.op_Implicit((Object)(object)item.m_dropPrefab) ? ((Object)item.m_dropPrefab).name : "unknown"); int length = text.Length; TransferItemIdentity val = new TransferItemIdentity((length > 0 && length <= 256) ? text : "unknown", "uncapturable:" + handle.ToString("N"), 1, 0, 0, 0f, 0L, "", false, false, (IEnumerable<KeyValuePair<string, string>>)Array.Empty<KeyValuePair<string, string>>(), "valheim-item-110-exact-1", Array.Empty<byte>()); return new TransferStack(handle, x, y, num, num2, val, isProtected, false, (string)null); } private string? MergeClass(ItemData item) { if (item.m_shared.m_maxStackSize <= 1) { return null; } for (int i = 0; i < mergeClasses.Count; i++) { if (mergeClasses[i].IsSameType(item) && item.IsSameType(mergeClasses[i])) { return "c" + i; } } if (mergeClasses.Count >= 4096) { return null; } mergeClasses.Add(item.Clone()); return "c" + (mergeClasses.Count - 1); } internal bool TryResolve(Inventory inventory, Guid instanceGeneration, Guid handle, out ItemData? item) { CheckThread(); item = null; if (!bindings.TryGetValue(inventory, out InventoryBinding value) || value.Generation != instanceGeneration) { return false; } foreach (KeyValuePair<ItemData, Guid> handle2 in value.Handles) { if (handle2.Value == handle && inventory.ContainsItem(handle2.Key)) { item = handle2.Key; return true; } } return false; } internal bool TryItem(Inventory inventory, Guid handle, out ItemData? item) { CheckThread(); item = null; if (bindings.TryGetValue(inventory, out InventoryBinding value)) { return TryResolve(inventory, value.Generation, handle, out item); } return false; } internal bool TryHandle(Inventory inventory, ItemData item, out Guid handle) { CheckThread(); handle = Guid.Empty; if (item != null && bindings.TryGetValue(inventory, out InventoryBinding value)) { return value.Handles.TryGetValue(item, out handle); } return false; } internal static TransferItemIdentity Identity(ItemData item, string? registration) { PreflightBytes(item); return Preflighted(item, registration); } private static TransferItemIdentity Preflighted(ItemData item, string? registration) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown if (string.IsNullOrEmpty(registration) || !Object.op_Implicit((Object)(object)item.m_dropPrefab)) { throw new NotSupportedException("Item registration is not currently verified."); } return new TransferItemIdentity(((Object)item.m_dropPrefab).name, registration, item.m_quality, item.m_variant, item.m_worldLevel, item.m_durability, item.m_crafterID, item.m_crafterName, item.m_pickedUp, item.m_cheated, (IEnumerable<KeyValuePair<string, string>>)item.m_customData, "valheim-item-110-exact-1", Array.Empty<byte>()); } private static long PreflightBytes(ItemData item) { if (item == null || ((object)item).GetType() != typeof(ItemData) || item.m_shared == null || item.m_stack < 1 || item.m_stack > 65535 || item.m_stack > item.m_shared.m_maxStackSize || item.m_shared.m_maxStackSize < 1 || item.m_shared.m_maxStackSize > 65535 || item.m_quality < 1 || item.m_quality > 65535 || item.m_variant < 0 || item.m_worldLevel < 0 || item.m_worldLevel > 255 || item.m_gridPos.x < 0 || item.m_gridPos.x > 255 || item.m_gridPos.y < 0 || item.m_gridPos.y > 255 || float.IsNaN(item.m_durability) || float.IsInfinity(item.m_durability) || item.m_durability < 0f || (double)item.m_durability * 100.0 > 2147483647.0 || item.m_customData == null || item.m_customData.Count > 2048) { throw new NotSupportedException("Item state cannot be represented by the native adapter."); } long num = 128 + TextBytes(item.m_crafterName, 16384); foreach (KeyValuePair<string, string> customDatum in item.m_customData) { num += 10 + TextBytes(customDatum.Key, 16384) + TextBytes(customDatum.Value, 65536); } if (num > 1048576) { throw new InvalidDataException("Item metadata exceeds its bound."); } return num; } private static int TextBytes(string value, int maximum) { if (value == null || value.Length > maximum) { throw new InvalidDataException("Invalid native item text."); } int byteCount = StrictUtf8.GetByteCount(value); if (byteCount > maximum) { throw new InvalidDataException("Native item text exceeds its bound."); } return byteCount; } internal static void VerifyItem(ItemData item, string registration) { VerifyNativeRoundtrip(item, Identity(item, registration)); } private static void VerifyNativeRoundtrip(ItemData item, TransferItemIdentity identity) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_002a: 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_0037: Expected O, but got Unknown //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) ZPackage val = new ZPackage(); item.Save(val); byte[] array = val.GetArray(); if (array.Length > 1048576) { throw new InvalidDataException("Item payload exceeds its bound."); } ZPackage val2 = new ZPackage(array); var (num, val3) = ItemData.Load(val2, (Item)110); if (val2.GetPos() != array.Length || num != StringExtensionMethods.GetStableHashCode(((Object)item.m_dropPrefab).name)) { throw new InvalidDataException("Native item identity did not round-trip."); } val3.m_dropPrefab = item.m_dropPrefab; val3.m_shared = item.m_shared; if (Math.Abs(val3.m_durability - item.m_durability) > 0.011f) { throw new NotSupportedException("Native save changes item durability; this exact-state adapter refuses it."); } val3.m_durability = item.m_durability; if (val3.m_stack != item.m_stack || val3.m_gridPos != item.m_gridPos || val3.m_equipped != item.m_equipped || !identity.Equals(Preflighted(val3, identity.Registration))) { throw new NotSupportedException("Native save normalizes item state; this exact-state adapter refuses it."); } } private static string Hash(byte[] bytes) { using SHA256 sHA = SHA256.Create(); return BitConverter.ToString(sHA.ComputeHash(bytes)).Replace("-", "").ToLowerInvariant(); } private void CheckThread() { if (disposed) { throw new ObjectDisposedException("GameInventorySnapshotAdapter"); } if (Environment.CurrentManagedThreadId != threadId) { throw new InvalidOperationException("Game-thread capture required."); } } public void Dispose() { CheckThread(); bindings.Clear(); mergeClasses.Clear(); disposed = true; } } internal sealed class GameInventoryCapture { internal TransferInventorySnapshot Snapshot { get; } internal IReadOnlyList<string> Uncapturable { get; } internal GameInventoryCapture(TransferInventorySnapshot snapshot, IReadOnlyList<string>? uncapturable = null) { Snapshot = snapshot; Uncapturable = uncapturable ?? Array.Empty<string>(); } } internal static class NativeInventoryProbe { internal const string Registration = "yavsos-native-probe"; private const string ProbeKey = "com.bastionntb.yavsos.native-probe"; private static bool ran; internal static bool Supported { get; private set; } internal static bool Checked => ran; internal static string Status { get; private set; } = "inventory reader not checked yet"; internal static void BeginSession() { ran = false; Supported = false; Status = "inventory reader not checked yet"; } internal static bool Update(Action<string> info, Action<string> warning) { if (ran || !Object.op_Implicit((Object)(object)ObjectDB.instance) || ObjectDB.instance.m_items == null || ObjectDB.instance.m_items.Count == 0 || !Object.op_Implicit((Object)(object)ZNetScene.instance)) { return false; } ran = true; try { string text = Check(); Supported = true; Status = "inventory reader verified: " + text; info(Status); } catch (Exception ex) when ((!(ex is OutOfMemoryException) && !(ex is StackOverflowException)) || 1 == 0) { Supported = false; Status = "inventory reader unavailable (" + ex.GetType().Name + ": " + ex.Message + "); cleanup is disabled, everything else works"; warning(Status); } return true; } private static string Check() { //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Expected O, but got Unknown //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Expected O, but got Unknown //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Expected O, but got Unknown //IL_0135: Unknown result type (might be due to invalid IL or missing references) List<GameObject> source = ObjectDB.instance.m_items.Where((GameObject x) => Object.op_Implicit((Object)(object)x) && Object.op_Implicit((Object)(object)x.GetComponent<ItemDrop>())).OrderBy<GameObject, string>((GameObject x) => ((Object)x).name, StringComparer.Ordinal).ToList(); GameObject val = ((IEnumerable<GameObject>)source).FirstOrDefault((Func<GameObject, bool>)((GameObject x) => Shared(x).m_maxStackSize > 2)) ?? throw new InvalidOperationException("no stackable sample item"); GameObject val2 = ((IEnumerable<GameObject>)source).FirstOrDefault((Func<GameObject, bool>)((GameObject x) => Shared(x).m_maxStackSize == 1 && Shared(x).m_useDurability && Shared(x).m_maxQuality > 1)) ?? ((IEnumerable<GameObject>)source).FirstOrDefault((Func<GameObject, bool>)((GameObject x) => Shared(x).m_maxStackSize == 1)) ?? throw new InvalidOperationException("no single-stack sample item"); Inventory val3 = new Inventory("YAVSOS probe", (Sprite)null, 4, 2); Place(val3, Sample(val, Math.Min(Shared(val).m_maxStackSize - 1, 7), rich: false), 0, 0); Place(val3, Sample(val2, 1, rich: true), 2, 1); ZPackage val4 = new ZPackage(); val3.Save(val4); byte[] array = val4.GetArray(); NativeTransferInventoryCodec.Validate(array, 4, 2, (Func<int, NativeTransferPrefabLimits>)Limits); byte[] resaved; Inventory val5 = RoundTrip(array, 4, 2, out resaved); List<ItemData> allItems = val3.GetAllItems(); SortedSet<string> sortedSet = new SortedSet<string>(StringComparer.Ordinal); foreach (ItemData item in allItems) { GameInventorySnapshotAdapter.VerifyItem(item, "yavsos-native-probe"); ItemData val6 = Copy(val5, item); foreach (string item2 in val6.m_customData.Keys.Where((string x) => !item.m_customData.ContainsKey(x))) { sortedSet.Add(item2); } ItemData obj = val6.Clone(); obj.m_customData = val6.m_customData.Where((KeyValuePair<string, string> x) => item.m_customData.ContainsKey(x.Key)).ToDictionary((KeyValuePair<string, string> x) => x.Key, (KeyValuePair<string, string> x) => x.Value); if (!GameInventorySnapshotAdapter.Identity(obj, "yavsos-native-probe").Equals(GameInventorySnapshotAdapter.Identity(item, "yavsos-native-probe"))) { throw new InvalidOperationException(((Object)item.m_dropPrefab).name + " did not round-trip exactly"); } } if (sortedSet.Count == 0 && !resaved.SequenceEqual(array)) { throw new InvalidOperationException("re-saved bytes differ"); } NativeTransferInventoryCodec.Validate(resaved, 4, 2, (Func<int, NativeTransferPrefabLimits>)Limits); byte[] resaved2; Inventory loaded = RoundTrip(resaved, 4, 2, out resaved2); if (!resaved2.SequenceEqual(resaved)) { throw new InvalidOperationException("re-saved bytes keep changing"); } foreach (ItemData allItem in val5.GetAllItems()) { if (!GameInventorySnapshotAdapter.Identity(Copy(loaded, allItem), "yavsos-native-probe").Equals(GameInventorySnapshotAdapter.Identity(allItem, "yavsos-native-probe"))) { throw new InvalidOperationException(((Object)allItem.m_dropPrefab).name + " keeps changing on load"); } } string text = ((sortedSet.Count == 0) ? "" : ("; another mod adds on load: " + string.Join(", ", sortedSet))); return $"{((Object)val).name} x{allItems[0].m_stack} and {((Object)val2).name} q{allItems[1].m_quality} round-trip exactly ({array.Length} bytes){text}"; } private static Inventory RoundTrip(byte[] bytes, int width, int height, out byte[] resaved) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown Inventory val = new Inventory("YAVSOS probe copy", (Sprite)null, width, height); val.Load(new ZPackage(bytes)); ZPackage val2 = new ZPackage(); val.Save(val2); resaved = val2.GetArray(); return val; } private static ItemData Copy(Inventory loaded, ItemData item) { ItemData val = loaded.GetItemAt(item.m_gridPos.x, item.m_gridPos.y) ?? throw new InvalidOperationException("item cell changed"); if (val.m_stack != item.m_stack || val.m_equipped != item.m_equipped || !Object.op_Implicit((Object)(object)val.m_dropPrefab) || loaded.GetAllItems().Count != 2) { throw new InvalidOperationException(((Object)item.m_dropPrefab).name + " did not round-trip exactly"); } return val; } private static SharedData Shared(GameObject prefab) { return prefab.GetComponent<ItemDrop>().m_itemData.m_shared; } private static ItemData Sample(GameObject prefab, int stack, bool rich) { ItemData val = prefab.GetComponent<ItemDrop>().m_itemData.Clone(); val.m_dropPrefab = prefab; val.m_stack = stack; val.m_equipped = false; val.m_quality = ((!rich) ? 1 : Math.Min(2, val.m_shared.m_maxQuality)); val.m_variant = ((rich && val.m_shared.m_variants > 1) ? 1 : 0); val.m_durability = (val.m_shared.m_useDurability ? Mathf.Floor(val.GetMaxDurability() / 2f) : val.m_durability); val.m_worldLevel = (rich ? 1 : 0); val.m_pickedUp = rich; val.m_crafterID = (rich ? 4242424242u : 0); val.m_crafterName = (rich ? "YAVSOS probe ✓" : ""); val.m_customData = (rich ? new Dictionary<string, string> { ["com.bastionntb.yavsos.native-probe"] = "{\"text\":\"ü\\n\",\"n\":3}", ["com.bastionntb.yavsos.native-probe.empty"] = "" } : new Dictionary<string, string>()); return val; } private static void Place(Inventory inventory, ItemData item, int x, int y) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) if (!inventory.AddItem(item, new Vector2i(x, y)) || inventory.GetItemAt(x, y) != item) { throw new InvalidOperationException("could not place " + ((Object)item.m_dropPrefab).name); } } private static NativeTransferPrefabLimits? Limits(int hash) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(hash); ItemData val = ((!Object.op_Implicit((Object)(object)itemPrefab)) ? null : itemPrefab.GetComponent<ItemDrop>()?.m_itemData); if (val?.m_shared != null) { return new NativeTransferPrefabLimits(val.m_shared.m_maxStackSize, val.m_shared.m_maxQuality, val.m_shared.m_variants); } return null; } } } namespace YAVSOS.Sources { internal static class LoadedProducerReaders { private const int MaxNestedEntries = 256; internal static IEnumerable<LoadedSourceFact?> Read(GameObject prefab, string id, bool active, Action check) { check(); if (!Object.op_Implicit((Object)(object)prefab)) { throw new InvalidDataException("Null/destroyed loaded producer definition."); } Trader component = prefab.GetComponent<Trader>(); Incinerator incinerator = prefab.GetComponent<Incinerator>(); WispSpawner lure = prefab.GetComponent<WispSpawner>(); SapCollector collector = prefab.GetComponent<SapCollector>(); Procreation breeding = prefab.GetComponent<Procreation>(); if (Object.op_Implicit((Object)(object)component)) { foreach (LoadedSourceFact item in Purchases(component, id, active, check)) { yield return item; } } if (Object.op_Implicit((Object)(object)incinerator)) { foreach (LoadedSourceFact item2 in Exchanges(incinerator, id, active, check)) { yield return item2; } } if (Object.op_Implicit((Object)(object)lure)) { foreach (LoadedSourceFact item3 in Wisps(lure, id, active, check)) { yield return item3; } } if (Object.op_Implicit((Object)(object)collector)) { foreach (LoadedSourceFact item4 in Sap(collector, id, active, check)) { yield return item4; } } if (Object.op_Implicit((Object)(object)breeding)) { foreach (LoadedSourceFact item5 in Breeding(breeding, id, active, check)) { yield return item5; } } check(); } private static IEnumerable<LoadedSourceFact?> Purchases(Trader trader, string id, bool active, Action check) { Alive((Component)(object)trader, check); List<TradeItem> entries = trader.m_items; if (entries == null) { yield return Coverage(id + ":trade", "gap: missing trader stock list; store faults"); yield break; } if (entries.Count > 256) { throw new MalformedDefinitionException("Trader stock bound exceeded."); } int count = entries.Count; for (int i = 0; i < count; i++) { CheckStock(); TradeItem val = entries[i]; string id2 = id + ":trade:" + i; if (val == null) { throw new MalformedDefinitionException("Null trader stock entry."); } string text = "price=" + val.m_price + "; currency=StoreGui.m_coinPrefab (client store UI; not read, no currency biome); worldKey=" + Clean(val.m_requiredGlobalKey) + " (recorded, ignored for origin); playerBuyKey=" + Clean(val.m_buyKey) + "; playerIncrementKey=" + Clean(val.m_incrementKey); if (!Object.op_Implicit((Object)(object)val.m_prefab)) { yield return Coverage(id2, (string.IsNullOrEmpty(val.m_buyKey) ? "gap: missing purchase item; " : "player-key purchase without inventory output; ") + text); continue; } SharedData val2 = val.m_prefab.m_itemData?.m_shared; int num = ((val2 != null) ? LoadedProducerSemantics.PurchaseAmount(val.m_stack, val2.m_maxStackSize, val.m_price) : 0); ObjectDB instance = ObjectDB.instance; GameObject val3 = (Object.op_Implicit((Object)(object)instance) ? instance.GetItemPrefab(((Object)val.m_prefab).name) : null); string name = ((Object.op_Implicit((Object)(object)val3) && Object.op_Implicit((Object)(object)val3.GetComponent<ItemDrop>())) ? ((Object)val3).name : ""); yield return Acquisition(id2, name, id, "purchase", text + "; stack=" + val.m_stack + " capped by maxStack=" + (val2?.m_maxStackSize ?? 0), num, active && ((Behaviour)trader).enabled && num > 0); } CheckStock(); void CheckStock() { check(); if (!Object.op_Implicit((Object)(object)trader) || trader.m_items != entries || entries.Count != count) { throw new InvalidDataException("Trader stock changed during capture."); } } } private static IEnumerable<LoadedSourceFact?> Exchanges(Incinerator machine, string id, bool active, Action check) { Alive((Component)(object)machine, check); string root = id + ":incinerate"; List<IncineratorConversion> conversions = machine.m_conversions; bool flag = Object.op_Implicit((Object)(object)machine.m_incinerateSwitch) && Object.op_Implicit((Object)(object)machine.m_container) && Object.op_Implicit((Object)(object)machine.m_lightingAOEs) && Object.op_Implicit((Object)(object)machine.m_leverAnim) && Object.op_Implicit((Object)(object)((Component)machine).GetComponent<ZNetView>()); bool usable = active && ((Behaviour)machine).enabled && flag && conversions != null; if (!flag) { yield return Coverage(root + ":wiring", "gap: missing switch/container/effect/lever/view reference; incineration faults"); } if (conversions == null) { yield return Coverage(root, "gap: missing conversion list; Awake sort faults"); } else if (conversions.Count > 256) { throw new MalformedDefinitionException("Incinerator conversion bound exceeded."); } int count = conversions?.Count ?? 0; (IncineratorConversion? Entry, List<Requirement>? Inputs, int Count)[] tracked = new(IncineratorConversion, List<Requirement>, int)[count]; IncineratorConversionShape?[] shapes = (IncineratorConversionShape?[])(object)new IncineratorConversionShape[count]; bool safe = usable; long total = 0L; for (int c = 0; c < count; c++) { CheckMachine(c); IncineratorConversion val = conversions[c]; if (val == null) { tracked[c] = (Entry: null, Inputs: null, Count: 0); safe = false; yield return Coverage(root + ":" + c, "gap: null conversion; Awake sort faults"); continue; } List<Requirement> requirements = val.m_requirements; tracked[c] = (Entry: val, Inputs: requirements, Count: requirements?.Count ?? 0); total += tracked[c].Count; if (requirements == null || total > 256) { safe = false; yield return null; continue; } bool flag2 = Object.op_Implicit((Object)(object)val.m_result); List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>(requirements.Count); foreach (Requirement item in requirements) { SharedData val2 = ((item == null || !Object.op_Implicit((Object)(object)item.m_resItem)) ? null : item.m_resItem.m_itemData?.m_shared); if (val2 == null || string.IsNullOrEmpty(val2.m_name) || item.m_amount <= 0) { flag2 = false; } else { list.Add(new KeyValuePair<string, int>(val2.m_name, item.m_amount)); } } shapes[c] = ((!flag2) ? ((IncineratorConversionShape)null) : new IncineratorConversionShape(val.m_priority, val.m_requireOnlyOneIngredient, (IEnumerable<KeyValuePair<string, int>>)list)); safe &= flag2 && shapes[c].Safe; yield return null; } if (total > 256) { yield return Coverage(root + ":bound", "gap: more than " + 256 + " total requirements; reachability unsupported"); } else if (usable && !safe) { yield return Coverage(root + ":unsafe", "gap: a conversion can fault or discard items (null/non-positive/empty AND); every conversion unsupported"); } IncineratorConversionShape[] ready = (safe ? Array.ConvertAll(shapes, (IncineratorConversionShape x) => x) : null); for (int c = 0; c < count; c++) { CheckMachine(count); IncineratorConversion entry; List<Requirement> inputs; int n; (entry, inputs, n) = tracked[c]; if (entry == null) { continue; } string route = root + ":" + c; string output = Name(Object.op_Implicit((Object)(object)entry.m_result) ? ((Component)entry.m_result).gameObject : null); if (output.Length == 0) { yield return Coverage(route, "gap: missing/unresolved incinerator result"); continue; } bool basis = ready != null && entry.m_resultAmount > 0; string detail = "priority=" + entry.m_priority + " (descending; equal order unspecified); mode=" + (entry.m_requireOnlyOneIngredient ? "one-of" : "all") + "; resultAmount=" + entry.m_resultAmount + "; inputs match shared item names; aggregate default result separate"; bool any = false; if (n > 256) { yield return Coverage(route + ":inputs", "gap: single conversion exceeds requirement bound; no input scan"); yield return Acquisition(route + ":acquire", output, id, "exchange", detail, entry.m_resultAmount, eligible: false); continue; } if (inputs == null || n == 0) { yield return Coverage(route + ":inputs", "gap: no requirement inputs"); } else if (!entry.m_requireOnlyOneIngredient) { bool reachable = basis && LoadedProducerSemantics.IncineratorAlternativeReachable((IReadOnlyList<IncineratorConversionShape>)ready, c, -1); for (int r = 0; r < n; r++) { CheckMachine(count); yield return Exchange(route + ":" + r, output, inputs[r], route, detail, reachable); } if (basis && !reachable) { yield return Coverage(route + ":shadow", "gap: an equal/higher-priority conversion may consume the minimal inputs"); } any = reachable; } else { for (int r = 0; r < n; r++) { CheckMachine(count); string alternative = route + ":alt:" + r; bool reachable = basis && LoadedProducerSemantics.IncineratorAlternativeReachable((IReadOnlyList<IncineratorConversionShape>)ready, c, r); yield return Exchange(alternative, output, inputs[r], alternative, detail, reachable); if (basis && !reachable) { yield return Coverage(alternative + ":shadow", "gap: an equal/higher-priority conversion may consume the minimal input"); } any = any || reachable; } } CheckMachine(count); yield return Acquisition(route + ":acquire", output, id, "exchange", detail, entry.m_resultAmount, any); yield return null; } CheckMachine(count); if (Object.op_Implicit((Object)(object)machine.m_defaultResult)) { yield return Acquisition(root + ":default", Name(((Component)machine.m_defaultResult).gameObject), id, "exchange", "aggregate residue: one result per " + machine.m_defaultCost + " remaining units after conversions; no input recipe", 1, usable && safe && machine.m_defaultCost > 0); yield return Coverage(root + ":default-domain", "gap: aggregate residue input domain is not a material route"); } void CheckMachine(int read) { check(); if (!Object.op_Implicit((Object)(object)machine) || machine.m_conversions != conversions || (conversions?.Count ?? 0) != count) { throw new InvalidDataException("Incinerator conversions changed during capture."); } for (int i = 0; i < read; i++) { (IncineratorConversion, List<Requirement>, int) tuple2 = tracked[i]; if (conversions[i] != tuple2.Item1 || (tuple2.Item1 != null && (tuple2.Item1.m_requirements != tuple2.Item2 || (tuple2.Item2?.Count ?? 0) != tuple2.Item3))) { throw new InvalidDataException("Incinerator requirements changed during capture."); } } } } private static LoadedSourceFact Exchange(string id, string output, Requirement? input, string route, string detail, bool eligible) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown string text = ((input != null && Object.op_Implicit((Object)(object)input.m_resItem)) ? Item(((Component)input.m_resItem).gameObject) : ""); return new LoadedSourceFact((LoadedSourceFactKind)0, id, output, (text.Length == 0) ? Missing(id) : text, route, "exchange", detail, input?.m_amount ?? 1, eligible); } private static IEnumerable<LoadedSourceFact?> Wisps(WispSpawner lure, string id, bool active, Action check) { Alive((Component)(object)lure, check); string row = id + ":wisp"; GameObject wispPrefab = lure.m_wispPrefab; string text = "night=" + lure.m_onlySpawnAtNight + "; maxCover=" + F(lure.m_maxCover) + "; chance=" + F(lure.m_spawnChance) + "; maxSpawned=" + lure.m_maxSpawned + "; interval=" + F(lure.m_spawnInterval) + "; lured wisp must be picked; no biome gate in definition"; GameObject val = null; int num = 0; bool flag = false; if (Object.op_Implicit((Object)(object)wispPrefab)) { Pickable component = wispPrefab.GetComponent<Pickable>(); ItemDrop component2 = wispPrefab.GetComponent<ItemDrop>(); if (Object.op_Implicit((Object)(object)component) && Object.op_Implicit((Object)(object)component.m_itemPrefab)) { val = component.m_itemPrefab; num = component.m_amount; flag = !component.m_defaultPicked || (((Behaviour)component).enabled && component.m_respawnTimeMinutes > 0f && component.m_spawnCheck == null); } else if (Object.op_Implicit((Object)(object)component2) && !Object.op_Implicit((Object)(object)wispPrefab.GetComponent<Character>())) { val = wispPrefab; num = component2.m_itemData?.m_stack ?? 0; flag = true; } } if (!Object.op_Implicit((Object)(object)val)) { yield return Coverage(row, "gap: lure output is not a root Pickable/ItemDrop; " + text); yield break; } yield return Acquisition(row, Name(val), id, "production", text, num, active && ((Behaviour)lure).enabled && flag && num > 0 && FinitePositive(lure.m_spawnChance) && lure.m_maxSpawned > 0 && Object.op_Implicit((Object)(object)lure.m_spawnPoint) && Object.op_Implicit((Object)(object)lure.m_coverPoint)); yield return Coverage(row + ":origin", "gap: no environment source; lure site, build cost and night are conditions, not natural origin"); } private static IEnumerable<LoadedSourceFact?> Sap(SapCollector collector, string id, bool active, Action check) { Alive((Component)(object)collector, check); string row = id + ":sap"; string text = "overlapping ResourceRoot with level>1 (any root; mustConnectTo=" + Clean(Object.op_Implicit((Object)(object)collector.m_mustConnectTo) ? ((Object)collector.m_mustConnectTo).name : null) + " is a non-null gate); secPerUnit=" + F(collector.m_secPerUnit) + "; maxLevel=" + collector.m_maxLevel + "; one scaled item per stored level"; if (!Object.op_Implicit((Object)(object)collector.m_spawnItem)) { yield return Coverage(row, "gap: missing sap output; " + text); yield break; } yield return Acquisition(row, Name(((Component)collector.m_spawnItem).gameObject), id, "production", text, 1, active && ((Behaviour)collector).enabled && Object.op_Implicit((Object)(object)collector.m_mustConnectTo) && Object.op_Implicit((Object)(object)collector.m_spawnPoint) && collector.m_maxLevel > 0 && FinitePositive(collector.m_secPerUnit)); yield return Coverage(row + ":origin", "natural ResourceRoot origin must be established independently by rooted world traversal; any overlapping root, not mustConnectTo identity; collector site/cost is not origin"); } private static IEnumerable<LoadedSourceFact?> Breeding(Procreation breeding, string id, bool active, Action check) { Alive((Component)(object)breeding, check); GameObject gameObject = ((Component)breeding).gameObject; string text = id + ":breed"; Tameable component = gameObject.GetComponent<Tameable>(); Character component2 = gameObject.GetComponent<Character>(); MonsterAI component3 = gameObject.GetComponent<MonsterAI>(); int num = ((Object.op_Implicit((Object)(object)component3) && component3.m_consumeItems != null) ? component3.m_consumeItems.Count : 0); bool flag = !Object.op_Implicit((Object)(object)component2) || num > 0; bool flag2 = Object.op_Implicit((Object)(object)component) && (component.m_startsTamed || (Object.op_Implicit((Object)(object)component3) && Object.op_Implicit((Object)(object)component2) && num > 0)); ZNetScene instance = ZNetScene.instance; GameObject val = ((Object.op_Implicit((Object)(object)breeding.m_offspring) && Object.op_Implicit((Object)(object)instance)) ? instance.GetPrefab(Utils.GetPrefabName(((Object)breeding.m_offspring).name)) : null); GameObject seperatePartner = breeding.m_seperatePartner; GameObject noPartnerOffspring = breeding.m_noPartnerOffspring; bool flag3 = Object.op_Implicit((Object)(object)seperatePartner); GameObject val2 = ((Object.op_Implicit((Object)(object)seperatePartner) && Object.op_Implicit((Object)(object)instance)) ? instance.GetPrefab(Utils.GetPrefabName(((Object)seperatePartner).name)) : null); bool flag4 = !flag3 || (Object.op_Implicit((Object)(object)val2) && ((Object)val2).name == ((Object)seperatePartner).name && CanBeReadyPartner(val2)); bool flag5 = Object.op_Implicit((Object)(object)val) && ((Object)val).name == ((Object)gameObject).name; bool flag6 = Object.op_Implicit((Object)(object)val) && flag3 && ((Object)val).name == ((Object)seperatePartner).name; bool flag7 = active && ((Behaviour)breeding).enabled && flag2 && flag && Object.op_Implicit((Object)(object)val) && LoadedProducerSemantics.ProcreationInitiates(breeding.m_pregnancyChance) && LoadedProducerSemantics.ProcreationCompletes(breeding.m_pregnancyDuration); string text2 = "parent=" + Clean(((Object)gameObject).name) + "; tamed (startsTamed=" + (Object.op_Implicit((Object)(object)component) && component.m_startsTamed) + "); fed by " + num + " MonsterAI foods; not alerted; initiation needs Random.value>" + F(breeding.m_pregnancyChance) + "; population<" + breeding.m_maxCreatures + " within " + F(breeding.m_totalCheckRange) + "; partner=" + (flag3 ? (Clean(((Object)seperatePartner).name) + " x1") : "same prefab x2 including parent") + " ready within " + F(breeding.m_partnerCheckRange) + "; lovePoints=" + breeding.m_requiredLovePoints + "; duration=" + F(breeding.m_pregnancyDuration) + "s; no biome/environment gate; origin anchor is the parent's natural source"; List<LoadedSourceFact> list = new List<LoadedSourceFact>(4); if (!Object.op_Implicit((Object)(object)breeding.m_offspring)) { list.Add(Coverage(text + ":registration", "gap: missing offspring; Procreate faults")); } else { if (!Object.op_Implicit((Object)(object)val)) { list.Add(Coverage(text + ":registration", "gap: offspring is not a registered prefab; Procreate faults")); } list.Add(Offspring(text, Object.op_Implicit((Object)(object)val) ? val : breeding.m_offspring, id, text2 + (Object.op_Implicit((Object)(object)noPartnerOffspring) ? "; partner present at birth" : "; partner present at initiation"), flag7 && flag4 && LoadedProducerSemantics.ProcreationCapacity(breeding.m_maxCreatures, !Object.op_Implicit((Object)(object)noPartnerOffspring), flag3, flag5, flag6))); } if (Object.op_Implicit((Object)(object)noPartnerOffspring)) { list.Add(Offspring(text + ":alone", noPartnerOffspring, id, text2 + "; no partner present at birth", flag7 && LoadedProducerSemantics.ProcreationCapacity(breeding.m_maxCreatures, false, flag3, flag5, flag6))); } list.Add(Coverage(text + ":origin", "gap: breeding needs an external parent anchor; parent natural-source join unsupported; no biome")); foreach (LoadedSourceFact item in list) { yield return item; } } private static bool CanBeReadyPartner(GameObject partner) { Procreation component = partner.GetComponent<Procreation>(); Tameable component2 = partner.GetComponent<Tameable>(); Character component3 = partner.GetComponent<Character>(); MonsterAI component4 = partner.GetComponent<MonsterAI>(); bool flag = Object.op_Implicit((Object)(object)component4) && component4.m_consumeItems != null && component4.m_consumeItems.Count > 0; if (Object.op_Implicit((Object)(object)component) && ((Behaviour)component).enabled && Object.op_Implicit((Object)(object)component2) && (!Object.op_Implicit((Object)(object)component3) || flag)) { if (!component2.m_startsTamed) { return Object.op_Implicit((Object)(object)component4) && Object.op_Implicit((Object)(object)component3) && flag; } return true; } return false; } private static LoadedSourceFact Offspring(string id, GameObject output, string producer, string conditions, bool eligible) { if (!Object.op_Implicit((Object)(object)output.GetComponent<Character>())) { return Acquisition(id, Name(output), producer, "breeding", conditions, 1, eligible); } return Coverage(id, "creature offspring " + Clean(((Object)output).name) + " is not an inventory item; growth/drops unsupported here; " + conditions); } private static LoadedSourceFact Acquisition(string id, string name, string producer, string method, string detail, int amount, bool eligible) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown if (name.Length != 0) { return new LoadedSourceFact((LoadedSourceFactKind)4, id, name, "", producer, method, detail, amount, eligible && amount > 0); } return Coverage(id, "gap: unresolved " + method + " inventory output; " + detail); } private static string Name(GameObject? output) { if (!Object.op_Implicit((Object)(object)output)) { return ""; } return Item(output); } private static string Item(GameObject prefab) { ItemDrop component = prefab.GetComponent<ItemDrop>(); if (!Object.op_Implicit((Object)(object)component)) { return ""; } GameObject val = component.m_itemData?.m_dropPrefab; ObjectDB instance = ObjectDB.instance; if (!Object.op_Implicit((Object)(object)instance)) { return ""; } GameObject val2 = (Object.op_Implicit((Object)(object)val) ? instance.GetItemPrefab(((Object)val).name) : null); if (!Object.op_Implicit((Object)(object)val2)) { val2 = instance.GetItemPrefab(((Object)prefab).name); } if (!Object.op_Implicit((Object)(object)val2)) { string text = LoadedSourceSemantics.CloneBaseName(((Object)prefab).name); if (text != null) { val2 = instance.GetItemPrefab(text); } } if (!Object.op_Implicit((Object)(object)val2) || !Object.op_Implicit((Object)(object)val2.GetComponent<ItemDrop>())) { return ""; } return ((Object)val2).name; } private static string Clean(string? value) { if (string.IsNullOrEmpty(value)) { return "none"; } StringBuilder stringBuilder = new StringBuilder(Math.Min(value.Length, 128)); foreach (char c in value) { if (stringBuilder.Length == 128) { break; } stringBuilder.Append((char.IsControl(c) || char.IsSurrogate(c)) ? '?' : c); } return stringBuilder.ToString(); } private static void Alive(Component component, Action check) { check(); if (!Object.op_Implicit((Object)(object)component)) { throw new InvalidDataException("Loaded producer destroyed during capture."); } } private static LoadedSourceFact Coverage(string id, string detail) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown return new LoadedSourceFact((LoadedSourceFactKind)1, id, "", "", "", "", detail, 1, true); } private static string Missing(string id) { return "yavsos:missing-source:" + id; } private static string F(float value) { return value.ToString("R", CultureInfo.InvariantCulture); } private static bool FinitePositive(float value) { if (value > 0f && !float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } internal sealed class LoadedSourceHost : IDisposable { private const int MaxRoots = 32768; private const int MaxNestedEntries = 256; private readonly LoadedSourceCache cache; private ObjectDB? ordinaryDb; private readonly OptionalDefinitionWork optionalWork = new OptionalDefinitionWork(); private readonly ManualLogSource log; private readonly Func<IEnumerable<LoadedSourceFact?>?> open; private ZNetScene? scene; private List<GameObject>? prefabs; private StoreGui? store; private ItemDrop? currency; private int prefabCount; private CatalogSnapshot? catalog; private OptionalIntegrations? optional; private bool active; private LoadedSourceState reported; private int reportedRetries; private string reportedOff = ""; private readonly HashSet<string> malformedReported = new HashSet<string>(StringComparer.Ordinal); internal ClassificationSourceSnapshot? Current { get { LoadedSourceReceipt current = cache.Current; if (current == null) { return null; } return current.Snapshot; } } internal LoadedSourceReceipt? Receipt => cache.Current; internal long Revision => cache.Revision; internal bool EpicLootRarityOffered { get { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Invalid comparison between Unknown and I4 if (optional != null) { return (int)optional.EpicLoot.State == 1; } return false; } } internal OptionalCapabilityState EpicLootState { get { //IL_0011: Unknown result type (might be due to invalid IL or missing references) OptionalIntegrations? optionalIntegrations = optional; if (optionalIntegrations == null) { return (OptionalCapabilityState)0; } return optionalIntegrations.EpicLoot.State; } } internal string? GaveUp { get { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 if (!active || (int)cache.State != 5) { return null; } return cache.Diagnostic; } } internal LoadedSourceHost(ManualLogSource log) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown this.log = log; LoadedSourceLimits limits = new LoadedSourceLimits(65536, 8388608, 32, 1.0, 30.0); cache = new LoadedSourceCache(limits, (Func<CatalogSnapshot, IReadOnlyList<LoadedSourceFact>, CancellationToken, Task<LoadedSourceReceipt>>)((CatalogSnapshot loaded, IReadOnlyList<LoadedSourceFact> facts, CancellationToken token) => Task.Run(() => LoadedSourceReceipt.Build(loaded, (IEnumerable<LoadedSourceFact>)facts, limits, token), token)), (Func<double>)null, 3, 3.0); open = OpenOrdinaryCapture; } internal void BeginSession(bool isActive) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) active = isActive; ordinaryDb = null; catalog = null; scene = null; prefabs = null; optional = null; store = null; currency = null; cache.BeginSession(isActive); reported = cache.State; } internal void Invalidate() { cache.Invalidate(); } internal IReadOnlyList<FacetEvidence> FilterEquipmentRarity(ClassificationResult item, ItemData data) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (!item.InstanceRarity) { return Array.Empty<FacetEvidence>(); } if (optional != null && (int)optional.EpicLoot.State == 0) { return Array.Empty<FacetEvidence>(); } if (cache.Current != null && optional != null) { return optional.FilterEquipmentRarity(item, data); } return (IReadOnlyList<FacetEvidence>)(object)new FacetEvidence[1] { ClassificationFilters.UnreadableRow("rarity") }; } internal void Update(CatalogSnapshot? loaded) { //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Invalid comparison between Unknown and I4 //IL_0273: Unknown result type (might be due to invalid IL or missing references) //IL_0279: Invalid comparison between Unknown and I4 optionalWork.ReapRetired(); catalog = loaded; cache.SetCatalog(loaded); if (active) { ObjectDB instance = ObjectDB.instance; ZNetScene instance2 = ZNetScene.instance; List<GameObject> list = (Object.op_Implicit((Object)(object)instance2) ? instance2.m_prefabs : null); StoreGui instance3 = StoreGui.instance; ItemDrop val = (Object.op_Implicit((Object)(object)instance3) ? instance3.m_coinPrefab : null); if (ordinaryDb != instance || scene != instance2 || prefabs != list || prefabCount != (list?.Count ?? 0) || store != instance3 || currency != val) { ordinaryDb = instance; scene = instance2; prefabs = list; prefabCount = list?.Count ?? 0; store = instance3; currency = val; cache.Invalidate(); } } cache.Tick(open); if (cache.Retries > reportedRetries) { log.LogWarning((object)("Fresh loaded sources rejected: " + cache.Diagnostic)); } reportedRetries = cache.Retries; if (reported == cache.State) { return; } reported = cache.State; if ((int)reported == 4) { LoadedSourceReceipt current = cache.Current; log.LogInfo((object)($"Fresh loaded sources: {current.Facts.Count} detached facts, " + $"{current.Snapshot.Routes.Count} routes, {current.ScalarBytes} UTF-8 scalar bytes, maximum capture step {cache.MaximumStepMilliseconds:F3} ms, " + "SHA-256 " + current.Snapshot.Fingerprint + ".")); { foreach (string item in current.Coverage) { if (item.EndsWith("; skipped", StringComparison.Ordinal) && malformedReported.Add(item)) { log.LogWarning((object)("Loaded sources: " + item + ". Only this evidence is missing; report it to the mod that adds the item.")); } } return; } } if ((int)reported == 5) { log.LogWarning((object)("Fresh loaded sources rejected: " + cache.Diagnostic)); } } private IEnumerable<LoadedSourceFact?>? OpenOrdinaryCapture() { if (catalog == null || !Object.op_Implicit((Object)(object)ordinaryDb) || !Object.op_Implicit((Object)(object)scene) || prefabs == null) { return null; } return CaptureOrdinary(catalog, ordinaryDb, scene, prefabs); } private IEnumerable<LoadedSourceFact?> CaptureOrdinary(CatalogSnapshot loaded, ObjectDB db, ZNetScene net, List<GameObject> definitions) { int count = definitions.Count; StoreGui expectedStore = store; ItemDrop expectedCurrency = currency; if (count > 32768) { throw new InvalidDataException("Ordinary definition root limit exceeded."); } Check(); yield return Coverage("ordinary:capability", "game=" + Version.GetVersionString(false) + "; ordinary-loaded-consumers"); GameObject val = (Object.op_Implicit((Object)(object)expectedCurrency) ? ((Component)expectedCurrency).gameObject : null); GameObject val2 = (Object.op_Implicit((Object)(object)val) ? db.GetItemPrefab(((Object)val).name) : null); if (Object.op_Implicit((Object)(object)expectedStore) && Object.op_Implicit((Object)(object)val2) && (Object)(object)val2 == (Object)(object)val && expectedCurrency.m_itemData?.m_shared != null) { yield return new LoadedSourceFact((LoadedSourceFactKind)2, "payment:store", ((Object)val2).name, "", "payment:store", "payment-currency", "", 1, true); } else { yield return Coverage("payment:store", "gap: current registered payment definition unavailable"); } for (int i = 0; i < count; i++) { Check(); GameObject val3 = definitions[i]; if (!Object.op_Implicit((Object)(object)val3)) { throw new InvalidDataException("Null/destroyed loaded prefab registration."); } string id = "prefab:" + i + ":" + ((Object)val3).name; foreach (LoadedSourceFact item in Definition(val3, id, Check)) { yield return item; } yield return null; } optional = OptionalIntegrations.Probe(); Report(optional); foreach (LoadedSourceFact item2 in optional.Capture(loaded, db, definitions, Check, optionalWork)) { yield return item2; } Check(); void Check() { if (!active || catalog != loaded || !Object.op_Implicit((Object)(object)db) || !Object.op_Implicit((Object)(object)net) || ObjectDB.instance != db || ZNetScene.instance != net || net.m_prefabs != definitions || definitions.Count != count || StoreGui.instance != expectedStore || (Object.op_Implicit((Object)(object)expectedStore) ? expectedStore.m_coinPrefab : null) != expectedCurrency) { throw new InvalidDataException("Ordinary category registrations changed during capture."); } } } private void Report(OptionalIntegrations found) { List<OptionalCapability> list = ((IEnumerable<OptionalCapability>)(object)new OptionalCapability[3] { found.Jotunn, found.EpicLoot, found.EpicLootSemantics }).Where((OptionalCapability x) => (int)x.State == 2).ToList(); string text = string.Join("|", list.Select((OptionalCapability x) => x.Witness)); if (text == reportedOff) { return; } reportedOff = text; foreach (OptionalCapability item in list) { log.LogWarning((object)(item.Adapter + " turned off for version " + item.ObservedVersion + ": its code differs from what YAVSOS checked (" + item.Diagnostic + "). The rest of YAVSOS keeps working.")); } } private IReadOnlyList<LoadedSourceFact?> Definition(GameObject prefab, string id, Action check) { List<LoadedSourceFact> list = new List<LoadedSourceFact>(); try { list.AddRange(ItemUses(prefab, id, check)); list.AddRange(Machine(prefab, id, check)); Piece component = prefab.GetComponent<Piece>(); list.AddRange(LoadedProducerReaders.Read(prefab, id, !Object.op_Implicit((Object)(object)component) || component.m_enabled, check)); } catch (MalformedDefinitionException ex) { list.Clear(); list.Add(Coverage(id + ":malformed", "gap: " + ex.Message + " Its recipes, uses and products give no evidence.")); if (malformedReported.Add(((Object)prefab).name)) { log.LogWarning((object)("Loaded sources: " + ((Object)prefab).name + " has a malformed definition (" + ex.Message + "); only its own evidence is skipped. Report it to the mod that adds it.")); } } return list; } private static IEnumerable<LoadedSourceFact?> ItemUses(GameObject prefab, string id, Action check) { check(); Piece piece = prefab.GetComponent<Piece>(); bool active = !Object.op_Implicit((Object)(object)piece) || piece.m_enabled; Plant plant = prefab.GetComponent<Plant>(); Feast feast = prefab.GetComponent<Feast>(); ItemDrop val = ((!Object.op_Implicit((Object)(object)feast)) ? null : (Object.op_Implicit((Object)(object)feast.m_foodItem) ? feast.m_foodItem : prefab.GetComponent<ItemDrop>())); SharedData val2 = ((!Object.op_Implicit((Object)(object)val)) ? null : val.m_itemData?.m_shared); bool feastAvailable = active && Object.op_Implicit((Object)(object)feast) && ((Behaviour)feast).enabled && feast.m_eatStacks > 0 && val2 != null && FinitePositive(val2.m_foodBurnTime) && FinitePositive(val2.m_food); if (Object.op_Implicit((Object)(object)piece)) { Requirement[] resources = piece.m_resources; if (resources == null || resources.Length > 256) { throw new MalformedDefinitionException("Piece resource bound exceeded."); } for (int i = 0; i < resources.Length; i++) { check(); if (!Object.op_Implicit((Object)(object)piece) || piece.m_resources != resources) { throw new InvalidDataException("Piece resources changed during capture."); } Requirement resource = resources[i]; if (resource == null) { throw new MalformedDefinitionException("Null piece resource."); } bool eligible = active && !resource.m_upgraderResource; yield return Use(id + ":build:" + i, resource.m_resItem, id, "building", eligible, resource.m_amount); if (Object.op_Implicit((Object)(object)plant)) { yield return Use(id + ":plant-use:" + i, resource.m_resItem, id, "planting", eligible && ((Behaviour)plant).enabled, resource.m_amount); } if (Object.op_Implicit((Object)(object)feast)) { yield return Use(id + ":feast-use:" + i, resource.m_resItem, id, "feast-ingredient", eligible && feastAvailable, resource.m_amount); } } check(); if (!Object.op_Implicit((Object)(object)piece) || piece.m_resources != resources) { throw new InvalidDataException("Piece resources changed during capture."); } } Smelter component = prefab.GetComponent<Smelter>(); if (Object.op_Implicit((Object)(object)component) && Object.op_Implicit((Object)(object)component.m_fuelItem)) { yield return Use(id + ":smelter-fuel", component.m_fuelItem, id, "fuel", active && ((Behaviour)component).enabled && component.m_maxFuel > 0 && component.m_fuelPerProduct > 0); } CookingStation component2 = prefab.GetComponent<CookingStation>(); if (Object.op_Implicit((Object)(object)component2) && Object.op_Implicit((Object)(object)component2.m_fuelItem)) { yield return Use(id + ":cooking-fuel", component2.m_fuelItem, id, "fuel", active && ((Behaviour)component2).enabled && component2.m_useFuel && component2.m_maxFuel > 0); } Fireplace fire = prefab.GetComponent<Fireplace>(); if (Object.op_Implicit((Object)(object)fire) && Object.op_Implicit((Object)(object)fire.m_fuelItem)) { yield return Use(id + ":fire-fuel", fire.m_fuelItem, id, "fuel", active && ((Behaviour)fire).enabled && !fire.m_infiniteFuel && fire.m_canRefill && fire.m_maxFuel > 0f); } if (Object.op_Implicit((Object)(object)fire) && fire.m_fireworkItemList != null) { FireworkItem[] entries = fire.m_fireworkItemList; if (entries.Length > 256) { throw new MalformedDefinitionException("Fireworks consumer bound exceeded."); } SharedData fuel = ((!Object.op_Implicit((Object)(object)fire.m_fuelItem)) ? null : fire.m_fuelItem.m_itemData?.m_shared); HashSet<string> names = new HashSet<string>(StringComparer.Ordinal); for (int i = 0; i < entries.Length; i++) { check(); if (!Object.op_Implicit((Object)(object)fire) || fire.m_fireworkItemList != entries) { throw new InvalidDataException("Fireworks consumers changed during capture."); } FireworkItem val3 = entries[i]; SharedData val4 = ((!Object.op_Implicit((Object)(object)val3.m_fireworkItem)) ? null : val3.m_fireworkItem.m_itemData?.m_shared); if (val4 == null || string.IsNullOrEmpty(val4.m_name)) { yield return Coverage(id + ":fireworks:gap", "gap: malformed entry prevents later fireworks consumers"); break; } bool flag = names.Add(val4.m_name) && (fire.m_infiniteFuel || fuel?.m_name != val4.m_name); yield return Use(id + ":fireworks:" + i, val3.m_fireworkItem, id, "fireworks", active && ((Behaviour)fire).enabled && fire.m_canRefill && fuel != null && flag, val3.m_fireworkItemCount); } check(); if (!Object.op_Implicit((Object)(object)fire) || fire.m_fireworkItemList != entries) { throw new InvalidDataException("Fireworks consumers changed during capture."); } } Door component3 = prefab.GetComponent<Door>(); if (Object.op_Implicit((Object)(object)component3) && Object.op_Implicit((Object)(object)component3.m_keyItem)) { yield return Use(id + ":key", component3.m_keyItem, id, "door-key", active && ((Behaviour)component3).enabled); } Tameable tameable = prefab.GetComponent<Tameable>(); if (Object.op_Implicit((Object)(object)tameable) && Object.op_Implicit((Object)(object)tameable.m_saddleItem)) { yield return Use(id + ":saddle", tameable.m_saddleItem, id, "saddle", active && ((Behaviour)tameable).enabled); } MonsterAI ai = prefab.GetComponent<MonsterAI>(); if (Object.op_Implicit((Object)(object)ai) && ai.m_consumeItems != null) { Character component4 = prefab.GetComponent<Character>(); string tame = ((Object.op_Implicit((Object)(object)tameable) && !tameable.m_startsTamed) ? ((Object.op_Implicit((Object)(object)component4) && !string.IsNullOrEmpty(component4.m_name)) ? component4.m_name : id) : ""); List<ItemDrop> foods = ai.m_consumeItems; int i = foods.Count; if (i > 256) { throw new MalformedDefinitionException("Creature food bound exceeded."); } for (int j = 0; j < i; j++) { check(); if (!Object.op_Implicit((Object)(object)ai) || ai.m_consumeItems != foods || foods.Count != i) { throw new InvalidDataException("Creature foods changed during capture."); } yield return Use(id + ":feed:" + j, foods[j], id, "creature-food", active && ((Behaviour)ai).enabled); if (tame.Length != 0 && Object.op_Implicit((Object)(object)foods[j])) { string text = Item(((Component)foods[j]).gameObject); if (text != null && text.Length > 0) { yield return new LoadedSourceFact((LoadedSourceFactKind)3, id + ":tame-food:" + j, text, "", id, "eaten-by", tame, 1, active && ((Behaviour)ai).enabled && ((Behaviour)tameable).enabled); } } } check(); if (!Object.op_Implicit((Object)(object)ai) || ai.m_consumeItems != foods || foods.Count != i) { throw new InvalidDataException("Creature foods changed during capture."); } } Trader trader = prefab.GetComponent<Trader>(); if (Object.op_Implicit((Object)(object)trader)) { List<TraderUseItem> entries2 = trader.m_useItems; if (entries2 == null || entries2.Count > 256) { throw new MalformedDefinitionException("Trader handover bound exceeded."); } int i = entries2.Count; for (int j = 0; j < i; j++) { check(); if (!Object.op_Implicit((Object)(object)trader) || trader.m_useItems != entries2 || entries2.Count != i) { throw new InvalidDataException("Trader handovers changed during capture."); } TraderUseItem val5 = entries2[j]; if (val5 == null) { throw new MalformedDefinitionException("Null trader handover."); } yield return Use(id + ":handover:" + j, val5.m_prefab, id, "trader-handover", active && ((Behaviour)trader).enabled); } check(); if (!Object.op_Implicit((Object)(object)trader) || trader.m_useItems != entries2 || entries2.Count != i) { throw new InvalidDataException("Trader handovers changed during capture."); } } OfferingBowl component5 = prefab.GetComponent<OfferingBowl>();