using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Runic.Foundation.Core; using RunicInventory.Api; using RunicInventory.Core; using RunicInventory.Integration; using UnityEngine; using UnityEngine.EventSystems; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Runic Inventory")] [assembly: AssemblyDescription("Labeled native-row equipment and quick slots with automatic empty-role equip, lossless armor swaps, locks, sort, and pickup controls for Valheim.")] [assembly: AssemblyCompany("Chazman")] [assembly: AssemblyProduct("Runic Inventory")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: InternalsVisibleTo("RunicInventory.Tests")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [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.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Runic.Foundation.Core { public static class RunicIdentifier { public static bool IsValid(string value) { if (string.IsNullOrEmpty(value) || value.Length > 128) { return false; } bool flag = false; bool flag2 = false; foreach (char c in value) { if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) { flag = true; flag2 = false; continue; } switch (c) { case '-': if (!flag || flag2) { return false; } flag2 = true; break; case '.': if (!flag || flag2) { return false; } flag = false; flag2 = false; break; default: return false; } } if (flag) { return !flag2; } return false; } public static string Require(string value, string parameterName) { if (!IsValid(value)) { throw new ArgumentException("Runic identifiers must be lowercase dot-separated tokens containing only ASCII letters, digits, and internal hyphens.", parameterName); } return value; } } public sealed class InputChord : IEquatable { private readonly IReadOnlyList _modifiers; public string DeviceId { get; } public string PrimaryControl { get; } public IReadOnlyList Modifiers => _modifiers; public string CanonicalId { get; } public InputChord(string deviceId, string primaryControl, IEnumerable modifiers = null) { DeviceId = RunicIdentifier.Require(deviceId, "deviceId"); PrimaryControl = RequireControl(primaryControl, "primaryControl"); string text = CanonicalizeControl(PrimaryControl); SortedDictionary sortedDictionary = new SortedDictionary(StringComparer.Ordinal); if (modifiers != null) { foreach (string modifier in modifiers) { string text2 = RequireControl(modifier, "modifiers"); string text3 = CanonicalizeControl(text2); if (string.Equals(text3, text, StringComparison.Ordinal)) { throw new ArgumentException("The primary control cannot also be a modifier.", "modifiers"); } if (sortedDictionary.ContainsKey(text3)) { throw new ArgumentException("Duplicate input modifier: " + text2, "modifiers"); } sortedDictionary.Add(text3, text2); } } string[] array = new string[sortedDictionary.Count]; int num = 0; foreach (string value in sortedDictionary.Values) { array[num++] = value; } _modifiers = Array.AsReadOnly(array); StringBuilder stringBuilder = new StringBuilder(DeviceId).Append(':'); foreach (string key in sortedDictionary.Keys) { stringBuilder.Append(key).Append('+'); } stringBuilder.Append(text); CanonicalId = stringBuilder.ToString(); } public bool Equals(InputChord other) { if (other != null) { return string.Equals(CanonicalId, other.CanonicalId, StringComparison.Ordinal); } return false; } public override bool Equals(object obj) { return Equals(obj as InputChord); } public override int GetHashCode() { return StringComparer.Ordinal.GetHashCode(CanonicalId); } public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < _modifiers.Count; i++) { if (i > 0) { stringBuilder.Append(" + "); } stringBuilder.Append(_modifiers[i]); } if (_modifiers.Count > 0) { stringBuilder.Append(" + "); } return stringBuilder.Append(PrimaryControl).Append(" [").Append(DeviceId) .Append(']') .ToString(); } private static string RequireControl(string value, string parameterName) { if (string.IsNullOrWhiteSpace(value)) { throw new ArgumentException("An input control name is required.", parameterName); } string text = value.Trim(); if (text.Length > 64) { throw new ArgumentException("Input control names cannot exceed 64 characters.", parameterName); } string text2 = text; foreach (char c in text2) { if (char.IsControl(c) || c == ':' || c == '+' || c == '|') { throw new ArgumentException("The input control name contains a reserved character.", parameterName); } } if (CanonicalizeControl(text).Length == 0) { throw new ArgumentException("The input control name has no canonical characters.", parameterName); } return text; } private static string CanonicalizeControl(string value) { StringBuilder stringBuilder = new StringBuilder(value.Length); foreach (char c in value) { if (!char.IsWhiteSpace(c) && c != '_' && c != '-') { stringBuilder.Append(char.ToLowerInvariant(c)); } } return stringBuilder.ToString(); } } public sealed class KeybindingDescriptor { public string ModuleId { get; } public string BindingId { get; } public string QualifiedId => ModuleId + "/" + BindingId; public string DisplayName { get; } public InputChord Chord { get; } public string Context { get; } public KeybindingDescriptor(string moduleId, string bindingId, string displayName, InputChord chord, string context = "gameplay") { ModuleId = RunicIdentifier.Require(moduleId, "moduleId"); BindingId = RunicIdentifier.Require(bindingId, "bindingId"); Context = RunicIdentifier.Require(context, "context"); if (string.IsNullOrWhiteSpace(displayName)) { throw new ArgumentException("A keybinding display name is required.", "displayName"); } Chord = chord ?? throw new ArgumentNullException("chord"); DisplayName = displayName.Trim(); } } public sealed class KeybindingConflict { public InputChord Chord { get; } public IReadOnlyList Bindings { get; } internal KeybindingConflict(InputChord chord, IReadOnlyList bindings) { Chord = chord; Bindings = bindings; } } public enum KeybindingChangeKind { Registered, Unregistered } public sealed class KeybindingsChangedEventArgs : EventArgs { public KeybindingChangeKind Kind { get; } public KeybindingDescriptor Binding { get; } public KeybindingConflict Conflict { get; } internal KeybindingsChangedEventArgs(KeybindingChangeKind kind, KeybindingDescriptor binding, KeybindingConflict conflict) { Kind = kind; Binding = binding; Conflict = conflict; } } public sealed class KeybindingConflictRegistry { private sealed class BindingEntry { internal KeybindingDescriptor Binding { get; } internal long Token { get; } internal BindingEntry(KeybindingDescriptor binding, long token) { Binding = binding; Token = token; } } private readonly object _sync = new object(); private readonly Dictionary _byQualifiedId = new Dictionary(StringComparer.Ordinal); private readonly Dictionary> _byChord = new Dictionary>(StringComparer.Ordinal); private long _nextToken; public event EventHandler Changed; public KeybindingRegistration Register(KeybindingDescriptor binding) { if (binding == null) { throw new ArgumentNullException("binding"); } long token; KeybindingConflict conflict; lock (_sync) { if (_byQualifiedId.ContainsKey(binding.QualifiedId)) { throw new InvalidOperationException("A keybinding is already registered as '" + binding.QualifiedId + "'."); } if (_nextToken == long.MaxValue) { throw new InvalidOperationException("The keybinding registration token space is exhausted."); } token = ++_nextToken; BindingEntry bindingEntry = new BindingEntry(binding, token); _byQualifiedId.Add(binding.QualifiedId, bindingEntry); if (!_byChord.TryGetValue(binding.Chord.CanonicalId, out var value)) { value = new List(); _byChord.Add(binding.Chord.CanonicalId, value); } value.Add(bindingEntry); conflict = CreateConflictLocked(binding.Chord.CanonicalId); } RaiseChanged(new KeybindingsChangedEventArgs(KeybindingChangeKind.Registered, binding, conflict)); return new KeybindingRegistration(this, binding, token); } public bool Unregister(string moduleId, string bindingId) { RunicIdentifier.Require(moduleId, "moduleId"); RunicIdentifier.Require(bindingId, "bindingId"); return Unregister(moduleId + "/" + bindingId, (long?)null); } public bool TryGetBinding(string moduleId, string bindingId, out KeybindingDescriptor binding) { binding = null; if (!RunicIdentifier.IsValid(moduleId) || !RunicIdentifier.IsValid(bindingId)) { return false; } lock (_sync) { if (!_byQualifiedId.TryGetValue(moduleId + "/" + bindingId, out var value)) { return false; } binding = value.Binding; return true; } } public IReadOnlyList GetBindings() { lock (_sync) { List list = new List(_byQualifiedId.Count); foreach (BindingEntry value in _byQualifiedId.Values) { list.Add(value.Binding); } list.Sort((KeybindingDescriptor left, KeybindingDescriptor right) => StringComparer.Ordinal.Compare(left.QualifiedId, right.QualifiedId)); return list.AsReadOnly(); } } public IReadOnlyList GetConflicts() { lock (_sync) { List list = new List(); foreach (string key in _byChord.Keys) { KeybindingConflict keybindingConflict = CreateConflictLocked(key); if (keybindingConflict != null) { list.Add(keybindingConflict); } } list.Sort((KeybindingConflict left, KeybindingConflict right) => StringComparer.Ordinal.Compare(left.Chord.CanonicalId, right.Chord.CanonicalId)); return list.AsReadOnly(); } } public IReadOnlyList GetConflictsFor(string moduleId) { RunicIdentifier.Require(moduleId, "moduleId"); IReadOnlyList conflicts = GetConflicts(); List list = new List(); foreach (KeybindingConflict item in conflicts) { foreach (KeybindingDescriptor binding in item.Bindings) { if (string.Equals(binding.ModuleId, moduleId, StringComparison.Ordinal)) { list.Add(item); break; } } } return list.AsReadOnly(); } internal bool IsActive(string qualifiedId, long token) { lock (_sync) { BindingEntry value; return _byQualifiedId.TryGetValue(qualifiedId, out value) && value.Token == token; } } internal bool Unregister(string qualifiedId, long? requiredToken) { KeybindingDescriptor binding; KeybindingConflict conflict; lock (_sync) { if (!_byQualifiedId.TryGetValue(qualifiedId, out var entry) || (requiredToken.HasValue && entry.Token != requiredToken.Value)) { return false; } binding = entry.Binding; _byQualifiedId.Remove(qualifiedId); List list = _byChord[binding.Chord.CanonicalId]; list.RemoveAll((BindingEntry candidate) => candidate.Token == entry.Token); if (list.Count == 0) { _byChord.Remove(binding.Chord.CanonicalId); } conflict = CreateConflictLocked(binding.Chord.CanonicalId); } RaiseChanged(new KeybindingsChangedEventArgs(KeybindingChangeKind.Unregistered, binding, conflict)); return true; } private KeybindingConflict CreateConflictLocked(string chordId) { if (!_byChord.TryGetValue(chordId, out var value) || value.Count < 2) { return null; } List list = new List(value.Count); foreach (BindingEntry item in value) { list.Add(item.Binding); } list.Sort((KeybindingDescriptor left, KeybindingDescriptor right) => StringComparer.Ordinal.Compare(left.QualifiedId, right.QualifiedId)); return new KeybindingConflict(list[0].Chord, list.AsReadOnly()); } private void RaiseChanged(KeybindingsChangedEventArgs arguments) { EventHandler eventHandler = this.Changed; if (eventHandler == null) { return; } Delegate[] invocationList = eventHandler.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { EventHandler eventHandler2 = (EventHandler)invocationList[i]; try { eventHandler2(this, arguments); } catch (Exception) { } } } } public sealed class KeybindingRegistration : IDisposable { private readonly KeybindingConflictRegistry _registry; private readonly long _token; public KeybindingDescriptor Binding { get; } public bool IsActive => _registry.IsActive(Binding.QualifiedId, _token); internal KeybindingRegistration(KeybindingConflictRegistry registry, KeybindingDescriptor binding, long token) { _registry = registry; Binding = binding; _token = token; } public void Dispose() { _registry.Unregister(Binding.QualifiedId, _token); } } } namespace RunicInventory { internal static class Diagnostics { private static ManualLogSource _log; internal static void Initialize(ManualLogSource log) { _log = log; } internal static void Info(string value) { ManualLogSource log = _log; if (log != null) { log.LogInfo((object)Bound(value)); } } internal static void Warn(string value) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)Bound(value)); } } internal static void Error(Exception exception, string context) { ManualLogSource log = _log; if (log != null) { log.LogError((object)(Bound(context) + " " + ((exception == null) ? "unknown" : (exception.GetType().Name + ": " + Bound(exception.Message))))); } } internal static void Trace(string value) { ConfigEntry verboseDiagnostics = InventoryConfig.VerboseDiagnostics; if (verboseDiagnostics != null && verboseDiagnostics.Value) { ManualLogSource log = _log; if (log != null) { log.LogInfo((object)Bound(value)); } } } private static string Bound(string value) { string text = value ?? string.Empty; if (text.Length > 512) { return text.Substring(0, 512); } return text; } } internal static class InventoryConfig { internal static ConfigEntry Enabled { get; private set; } internal static ConfigEntry ShowInventoryStatus { get; private set; } internal static ConfigEntry ShowRoleLabels { get; private set; } internal static ConfigEntry ShowPickupPreview { get; private set; } internal static ConfigEntry FilteredPickupItems { get; private set; } internal static ConfigEntry SortRows { get; private set; } internal static ConfigEntry Quick1 { get; private set; } internal static ConfigEntry Quick2 { get; private set; } internal static ConfigEntry Quick3 { get; private set; } internal static ConfigEntry Sort { get; private set; } internal static ConfigEntry ToggleLock { get; private set; } internal static ConfigEntry ControllerEnabled { get; private set; } internal static ConfigEntry ControllerModifier { get; private set; } internal static ConfigEntry ControllerQuick1 { get; private set; } internal static ConfigEntry ControllerQuick2 { get; private set; } internal static ConfigEntry ControllerQuick3 { get; private set; } internal static ConfigEntry ControllerSort { get; private set; } internal static ConfigEntry ControllerToggleLock { get; private set; } internal static ConfigEntry VerboseDiagnostics { get; private set; } internal static void Bind(ConfigFile config) { //IL_00c5: 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_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) Enabled = config.Bind("General", "Enabled", true, "Enable the validated native-row topology. Disabling never removes or serializes an item."); ShowInventoryStatus = config.Bind("UI", "ShowInventoryStatus", false, "Show the bounded topology/authority panel while the player inventory is open."); ShowRoleLabels = config.Bind("UI", "ShowRoleLabels", true, "Label and subtly outline the eight native bottom-row equipment and quick slots while inventory is open."); ShowPickupPreview = config.Bind("UI", "ShowPickupPreview", true, "Append a bounded local capacity/weight preview to nearby world-item hover text."); FilteredPickupItems = config.Bind("Pickup Filter", "Items", string.Empty, "Exact comma/semicolon/newline-separated prefab IDs or shared-name tokens to refuse before pickup mutation; maximum 128 safe entries. Quest items always bypass the filter."); SortRows = config.Bind("Sort", "Rows", "1,2", "Zero-based general rows eligible for regional sort. Row 0 and the bottom special row are always rejected. Empty means every proven general row."); Quick1 = config.Bind("Keyboard", "UseQuickSlot1", new KeyboardShortcut((KeyCode)49, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Manually use quick slot 1 for the owning local player, including a dedicated-server client."); Quick2 = config.Bind("Keyboard", "UseQuickSlot2", new KeyboardShortcut((KeyCode)50, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Manually use quick slot 2 for the owning local player, including a dedicated-server client."); Quick3 = config.Bind("Keyboard", "UseQuickSlot3", new KeyboardShortcut((KeyCode)51, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Manually use quick slot 3 for the owning local player, including a dedicated-server client."); Sort = config.Bind("Keyboard", "SortSelectedRows", new KeyboardShortcut((KeyCode)105, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Sort only configured safe general rows while the inventory is open."); ToggleLock = config.Bind("Keyboard", "ToggleFocusedSlotLock", new KeyboardShortcut((KeyCode)108, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Optional keyboard fallback for slot locking. The primary gesture is Left Alt + right-click on a player slot."); ControllerEnabled = config.Bind("Controller", "Enabled", true, "Enable raw, effective-path-validated controller chords."); ControllerModifier = config.Bind("Controller", "ModifierAction", "JoyAltKeys", "Existing Valheim Joy* action held as the modifier."); ControllerQuick1 = config.Bind("Controller", "UseQuickSlot1Action", "JoyMap", "Existing gamepad action pressed with ModifierAction to use quick slot 1. The exact untouched legacy 1.0.0 controller set is read as JoyMap without rewriting the file."); ControllerQuick2 = config.Bind("Controller", "UseQuickSlot2Action", "JoyButtonY", "Existing gamepad action pressed with ModifierAction to use quick slot 2."); ControllerQuick3 = config.Bind("Controller", "UseQuickSlot3Action", "JoyRBumper", "Existing gamepad action pressed with ModifierAction to use quick slot 3."); ControllerSort = config.Bind("Controller", "SortSelectedRowsAction", "JoyButtonA", "Existing gamepad action pressed with ModifierAction to sort while inventory is open."); ControllerToggleLock = config.Bind("Controller", "ToggleFocusedSlotLockAction", "JoyButtonB", "Existing gamepad action pressed with ModifierAction to toggle the focused slot lock."); VerboseDiagnostics = config.Bind("Diagnostics", "Verbose", false, "Log bounded reason codes and control routes; never log inventory contents or player metadata."); } } [BepInPlugin("chazman.RunicInventory", "Runic Inventory", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "chazman.RunicInventory"; public const string Name = "Runic Inventory"; public const string Version = "1.0.0"; public const string ModuleId = "runic.inventory"; public const string ProtocolVersion = "1.0"; private readonly List _bindings = new List(); private readonly KeybindingConflictRegistry _keybindings = new KeybindingConflictRegistry(); private Harmony _harmony; private InventoryRuntime _runtime; private bool _configurationSubscribed; private bool _inputLayoutSubscribed; private bool _shuttingDown; internal static Plugin Instance { get; private set; } internal static bool RuntimeReady { get; private set; } internal InventoryRuntime Runtime => _runtime; private void Awake() { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown Instance = this; Diagnostics.Initialize(((BaseUnityPlugin)this).Logger); try { InventoryConfig.Bind(((BaseUnityPlugin)this).Config); ((BaseUnityPlugin)this).Config.SettingChanged += OnSettingChanged; _configurationSubscribed = true; ZInput.OnInputLayoutChanged += OnInputLayoutChanged; _inputLayoutSubscribed = true; if (!ValheimContracts.Initialize(out var problem)) { throw new MissingMethodException(problem); } _runtime = new InventoryRuntime(batch: false); _harmony = new Harmony("chazman.RunicInventory"); _harmony.PatchAll(typeof(Plugin).Assembly); RegisterBindings(); _runtime.Initialize(); InventoryIntegrationApi.Attach(_runtime); RuntimeReady = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)"Runic Inventory v1.0.0 ready. Native owner-local topology, locks, sort, quick use, equipment relocation, saves, and tombstones remain Valheim-owned."); if (Application.isBatchMode) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Batch transport detected: only an exact owning local Player can activate Inventory; a true dedicated server remains inert."); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Runic Inventory startup failed: " + ex)); ShutdownRuntime(); } } private void Update() { if (!RuntimeReady || _runtime == null) { return; } try { _runtime.Tick(); } catch (Exception exception) { Diagnostics.Error(exception, "Inventory runtime faulted and was disabled."); _runtime.FailClosed("runtime.exception"); } } private void OnGUI() { if (!RuntimeReady || _runtime == null) { return; } try { _runtime.Draw(); } catch (Exception exception) { Diagnostics.Error(exception, "Inventory status drawing failed closed."); } } private void OnSettingChanged(object sender, SettingChangedEventArgs arguments) { if (!RuntimeReady || _runtime == null) { return; } try { _runtime.OnConfigurationChanged(); } catch (Exception exception) { Diagnostics.Error(exception, "Inventory configuration refresh failed closed."); _runtime.FailClosed("config.refresh-failed"); } try { RefreshBindings(); } catch (Exception exception2) { Diagnostics.Error(exception2, "Inventory keybinding refresh failed."); } } private void OnInputLayoutChanged() { ControllerChordSession.Reset(); Diagnostics.Trace("Controller input layout changed; Inventory bindings will be re-resolved."); } private void OnDestroy() { ShutdownRuntime(); Diagnostics.Initialize(null); Instance = null; } private void RegisterBindings() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) ConfigEntry enabled = InventoryConfig.Enabled; if (enabled != null && enabled.Value) { RegisterKeyboard("quick-1", "Use quick slot 1", InventoryConfig.Quick1.Value, "gameplay"); RegisterKeyboard("quick-2", "Use quick slot 2", InventoryConfig.Quick2.Value, "gameplay"); RegisterKeyboard("quick-3", "Use quick slot 3", InventoryConfig.Quick3.Value, "gameplay"); RegisterKeyboard("sort", "Sort selected safe inventory rows", InventoryConfig.Sort.Value, "inventory"); RegisterKeyboard("lock", "Toggle focused inventory slot lock", InventoryConfig.ToggleLock.Value, "inventory"); ConfigEntry controllerEnabled = InventoryConfig.ControllerEnabled; if (controllerEnabled != null && controllerEnabled.Value) { string modifier = BoundControllerAction(InventoryConfig.ControllerModifier.Value); string quick = BoundControllerAction(InventoryConfig.ControllerQuick1.Value); string text = BoundControllerAction(InventoryConfig.ControllerQuick2.Value); string text2 = BoundControllerAction(InventoryConfig.ControllerQuick3.Value); string text3 = BoundControllerAction(InventoryConfig.ControllerSort.Value); string text4 = BoundControllerAction(InventoryConfig.ControllerToggleLock.Value); quick = ControllerBindingPolicy.EffectiveQuick1Action(modifier, quick, text, text2, text3, text4, out var _); RegisterController("controller-quick-1", "Use quick slot 1", quick, modifier, "gameplay"); RegisterController("controller-quick-2", "Use quick slot 2", text, modifier, "gameplay"); RegisterController("controller-quick-3", "Use quick slot 3", text2, modifier, "gameplay"); RegisterController("controller-sort", "Sort selected safe inventory rows", text3, modifier, "inventory"); RegisterController("controller-lock", "Toggle focused inventory slot lock", text4, modifier, "inventory"); } } } private void RefreshBindings() { DisposeBindings(); try { RegisterBindings(); } catch { DisposeBindings(); throw; } } private void DisposeBindings() { for (int num = _bindings.Count - 1; num >= 0; num--) { try { _bindings[num].Dispose(); } catch (Exception exception) { Diagnostics.Error(exception, "Inventory keybinding cleanup failed."); } } _bindings.Clear(); } private unsafe void RegisterKeyboard(string id, string display, KeyboardShortcut shortcut, string context) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_0080: Unknown result type (might be due to invalid IL or missing references) if ((int)((KeyboardShortcut)(ref shortcut)).MainKey == 0) { return; } List list = new List(); foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers) { if (list.Count >= 8) { throw new InvalidOperationException("A keyboard chord may contain at most eight modifiers."); } list.Add(((object)(*(KeyCode*)(&modifier))/*cast due to .constrained prefix*/).ToString()); } _bindings.Add(_keybindings.Register(new KeybindingDescriptor("runic.inventory", id, display, new InputChord("keyboard", ((object)((KeyboardShortcut)(ref shortcut)).MainKey/*cast due to .constrained prefix*/).ToString(), list), context))); } private void RegisterController(string id, string display, string action, string modifier, string context) { if (action.Length == 0 || modifier.Length == 0) { return; } try { _bindings.Add(_keybindings.Register(new KeybindingDescriptor("runic.inventory", id, display, new InputChord("controller", action, new string[1] { modifier }), context))); } catch (ArgumentException) { Diagnostics.Warn("Inventory skipped invalid controller route: " + id + "."); } } private static string BoundControllerAction(string value) { string text = value ?? string.Empty; if (text.Length == 0 || text.Length > 64) { return string.Empty; } string text2 = text.Trim(); if (!text2.StartsWith("Joy", StringComparison.Ordinal)) { return string.Empty; } for (int i = 0; i < text2.Length; i++) { if (!char.IsLetterOrDigit(text2[i]) && text2[i] != '_' && text2[i] != '-') { return string.Empty; } } return text2; } private void ShutdownRuntime() { if (_shuttingDown) { return; } _shuttingDown = true; RuntimeReady = false; InventoryRuntime runtime = _runtime; InventoryIntegrationApi.Detach(runtime); if (_configurationSubscribed) { ((BaseUnityPlugin)this).Config.SettingChanged -= OnSettingChanged; _configurationSubscribed = false; } if (_inputLayoutSubscribed) { ZInput.OnInputLayoutChanged -= OnInputLayoutChanged; _inputLayoutSubscribed = false; } ControllerChordSession.Reset(); DisposeBindings(); try { runtime?.Dispose(); } catch (Exception exception) { Diagnostics.Error(exception, "Inventory runtime cleanup was incomplete."); } _runtime = null; try { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch (Exception exception2) { Diagnostics.Error(exception2, "Inventory Harmony cleanup was incomplete."); } _harmony = null; } } } namespace RunicInventory.Integration { internal sealed class ControllerBindingState { internal const int DefinitionCount = 6; internal ButtonDef Modifier { get; } internal ButtonDef Quick1 { get; } internal ButtonDef Quick2 { get; } internal ButtonDef Quick3 { get; } internal ButtonDef Sort { get; } internal ButtonDef ToggleLock { get; } internal string ReasonCode { get; } internal bool LegacyQuick1Mapped { get; } internal int ValidRouteCount { get; } internal bool Ready { get { if (Modifier != null) { return ValidRouteCount > 0; } return false; } } internal bool AllValid { get { if (Modifier != null) { return ValidRouteCount == 5; } return false; } } internal ControllerBindingState(ButtonDef modifier, ButtonDef quick1, ButtonDef quick2, ButtonDef quick3, ButtonDef sort, ButtonDef toggleLock, string reasonCode, bool legacyQuick1Mapped) { Modifier = modifier; Quick1 = quick1; Quick2 = quick2; Quick3 = quick3; Sort = sort; ToggleLock = toggleLock; ReasonCode = reasonCode ?? "controller.invalid"; LegacyQuick1Mapped = legacyQuick1Mapped; ValidRouteCount = ((quick1 != null) ? 1 : 0) + ((quick2 != null) ? 1 : 0) + ((quick3 != null) ? 1 : 0) + ((sort != null) ? 1 : 0) + ((toggleLock != null) ? 1 : 0); } internal ButtonDef Definition(int index) { return (ButtonDef)(index switch { 0 => Modifier, 1 => Quick1, 2 => Quick2, 3 => Quick3, 4 => Sort, 5 => ToggleLock, _ => throw new ArgumentOutOfRangeException("index"), }); } } internal static class ControllerBindings { private static ZInput _instance; private static ControllerBindingState _cached; internal static ControllerBindingState Resolve() { if (_cached != null && _instance == ZInput.instance) { return _cached; } string text = Name(InventoryConfig.ControllerModifier?.Value); string quick = Name(InventoryConfig.ControllerQuick1?.Value); string text2 = Name(InventoryConfig.ControllerQuick2?.Value); string text3 = Name(InventoryConfig.ControllerQuick3?.Value); string text4 = Name(InventoryConfig.ControllerSort?.Value); string text5 = Name(InventoryConfig.ControllerToggleLock?.Value); quick = ControllerBindingPolicy.EffectiveQuick1Action(text, quick, text2, text3, text4, text5, out var legacyMapped); _instance = ZInput.instance; if (_instance == null) { return _cached = new ControllerBindingState(null, null, null, null, null, null, "controller.zinput-unavailable", legacyMapped); } string path; ButtonDef val = ResolveAction(text, out path); string[] array = new string[5] { quick, text2, text3, text4, text5 }; string[] array2 = new string[5]; ButtonDef[] array3 = (ButtonDef[])(object)new ButtonDef[5]; for (int i = 0; i < 5; i++) { array3[i] = ResolveAction(array[i], out array2[i]); } int num = ControllerBindingPolicy.ValidRouteMask(text, path, array, array2); for (int j = 0; j < 5; j++) { if (!ControllerBindingPolicy.RouteIsValid(num, j)) { array3[j] = null; } } string reasonCode = ((val == null) ? "controller.modifier-invalid" : (num switch { 0 => "controller.no-valid-routes", 31 => "ok", _ => "controller.one-or-more-routes-invalid", })); return _cached = new ControllerBindingState(val, array3[0], array3[1], array3[2], array3[3], array3[4], reasonCode, legacyMapped); } internal static void Invalidate() { _instance = null; _cached = null; } internal static bool ShouldReserveAction(string action) { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Invalid comparison between Unknown and I4 ConfigEntry controllerEnabled = InventoryConfig.ControllerEnabled; if (controllerEnabled == null || !controllerEnabled.Value || string.IsNullOrEmpty(action) || ZInput.instance == null) { return false; } ControllerBindingState controllerBindingState = Resolve(); if (controllerBindingState.Ready && controllerBindingState.Modifier.Held) { ButtonDef quick = controllerBindingState.Quick1; if (quick == null || !quick.Pressed) { ButtonDef quick2 = controllerBindingState.Quick2; if (quick2 == null || !quick2.Pressed) { ButtonDef quick3 = controllerBindingState.Quick3; if (quick3 == null || !quick3.Pressed) { ButtonDef sort = controllerBindingState.Sort; if (sort == null || !sort.Pressed) { ButtonDef toggleLock = controllerBindingState.ToggleLock; if (toggleLock == null || !toggleLock.Pressed) { goto IL_00a3; } } } } } ButtonDef buttonDef; try { buttonDef = ZInput.instance.GetButtonDef(action); } catch (Exception) { return false; } if (buttonDef == null || (int)buttonDef.Source != 180) { return false; } string actionPath; try { actionPath = buttonDef.GetActionPath(true); } catch (Exception) { return false; } if (!SamePath(actionPath, controllerBindingState.Modifier) && !SamePath(actionPath, controllerBindingState.Quick1) && !SamePath(actionPath, controllerBindingState.Quick2) && !SamePath(actionPath, controllerBindingState.Quick3) && !SamePath(actionPath, controllerBindingState.Sort)) { return SamePath(actionPath, controllerBindingState.ToggleLock); } return true; } goto IL_00a3; IL_00a3: return false; } private static ButtonDef ResolveAction(string action, out string path) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Invalid comparison between Unknown and I4 path = string.Empty; if (action.Length == 0 || action.Length > 64 || !action.StartsWith("Joy", StringComparison.Ordinal)) { return null; } ButtonDef buttonDef; try { buttonDef = ZInput.instance.GetButtonDef(action); } catch (Exception) { return null; } if (buttonDef == null || (int)buttonDef.Source != 180) { return null; } try { path = buttonDef.GetActionPath(true)?.Trim() ?? string.Empty; } catch (Exception) { return null; } if (path.Length != 0) { return buttonDef; } return null; } private static string Name(string value) { string text = value ?? string.Empty; if (text.Length == 0 || text.Length > 64) { return string.Empty; } string text2 = text.Trim(); for (int i = 0; i < text2.Length; i++) { if (!char.IsLetterOrDigit(text2[i]) && text2[i] != '_' && text2[i] != '-') { return string.Empty; } } return text2; } private static bool SamePath(string path, ButtonDef definition) { if (string.IsNullOrEmpty(path) || definition == null) { return false; } try { return string.Equals(path, definition.GetActionPath(true), StringComparison.Ordinal); } catch (Exception) { return false; } } } internal static class KeyboardInput { private static readonly KeyCode[] ModifierKeys; internal static bool ShortcutDown(KeyboardShortcut shortcut) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) if ((int)((KeyboardShortcut)(ref shortcut)).MainKey == 0 || !ZInput.GetKeyDown(((KeyboardShortcut)(ref shortcut)).MainKey, false)) { return false; } int num = 0; int num2 = 0; foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers) { if (++num2 > 8) { return false; } if ((int)modifier == 0 || !ZInput.GetKey(modifier, false)) { return false; } int num3 = ModifierIndex(modifier); if (num3 >= 0) { num |= 1 << num3; } } for (int i = 0; i < ModifierKeys.Length; i++) { if ((num & (1 << i)) == 0 && ZInput.GetKey(ModifierKeys[i], false)) { return false; } } return true; } internal static bool ShouldReserveVanillaAction(string action) { //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_010b: 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_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) KeyCode val = (KeyCode)(action switch { "Hotbar1" => 49, "Hotbar2" => 50, "Hotbar3" => 51, "Hotbar4" => 52, "Hotbar5" => 53, "Hotbar6" => 54, "Hotbar7" => 55, "Hotbar8" => 56, _ => 0, }); if ((int)val == 0) { return false; } if (!IsChordFor(val, InventoryConfig.Quick1.Value) && !IsChordFor(val, InventoryConfig.Quick2.Value)) { return IsChordFor(val, InventoryConfig.Quick3.Value); } return true; } private static bool IsChordFor(KeyCode key, KeyboardShortcut shortcut) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) if (((KeyboardShortcut)(ref shortcut)).MainKey == key) { return ShortcutDown(shortcut); } return false; } private static int ModifierIndex(KeyCode value) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between I4 and Unknown for (int i = 0; i < ModifierKeys.Length; i++) { if ((int)ModifierKeys[i] == (int)value) { return i; } } return -1; } static KeyboardInput() { KeyCode[] array = new KeyCode[8]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); ModifierKeys = (KeyCode[])(object)array; } } internal static class InputReservation { internal static bool ShouldSuppress(string action) { InventoryRuntime inventoryRuntime = Plugin.Instance?.Runtime; if (!Plugin.RuntimeReady || inventoryRuntime == null || !inventoryRuntime.AcceptsInput) { return false; } if (!ControllerChordSession.ShouldSuppress(action) && !ControllerBindings.ShouldReserveAction(action)) { return KeyboardInput.ShouldReserveVanillaAction(action); } return true; } } internal static class ControllerInput { private static ControllerBindingState _loggedBindings; internal static void Tick(InventoryRuntime runtime, bool inventoryVisible, bool gameplayInput) { if (runtime == null) { return; } ConfigEntry controllerEnabled = InventoryConfig.ControllerEnabled; if (controllerEnabled == null || !controllerEnabled.Value || ZInput.instance == null) { return; } ControllerBindingState controllerBindingState = ControllerBindings.Resolve(); if (_loggedBindings != controllerBindingState) { _loggedBindings = controllerBindingState; if (controllerBindingState.AllValid) { Diagnostics.Info(controllerBindingState.LegacyQuick1Mapped ? "Inventory controller chords validated by six unique effective gamepad paths; the exact legacy default Quick 1 route is using JoyMap." : "Inventory controller chords validated by six unique effective gamepad paths."); } else if (controllerBindingState.Ready) { Diagnostics.Warn("Inventory controller chords partially available (" + controllerBindingState.ValidRouteCount + "/5 routes): " + controllerBindingState.ReasonCode + "."); } else { Diagnostics.Warn("Inventory controller chords disabled: " + controllerBindingState.ReasonCode + "."); } } bool active = ControllerChordSession.Active; if (!controllerBindingState.Ready || active || !controllerBindingState.Modifier.Held) { return; } if (inventoryVisible) { ButtonDef toggleLock = controllerBindingState.ToggleLock; if (toggleLock != null && toggleLock.Pressed) { ControllerChordSession.Begin(controllerBindingState); runtime.ToggleFocusedLock("controller"); return; } } if (inventoryVisible) { ButtonDef sort = controllerBindingState.Sort; if (sort != null && sort.Pressed) { ControllerChordSession.Begin(controllerBindingState); runtime.SortSelectedRows("controller"); return; } } if (!gameplayInput) { return; } ButtonDef quick = controllerBindingState.Quick1; if (quick != null && quick.Pressed) { ControllerChordSession.Begin(controllerBindingState); runtime.UseQuick(InventoryRoleKind.Quick1, "controller"); return; } ButtonDef quick2 = controllerBindingState.Quick2; if (quick2 != null && quick2.Pressed) { ControllerChordSession.Begin(controllerBindingState); runtime.UseQuick(InventoryRoleKind.Quick2, "controller"); return; } ButtonDef quick3 = controllerBindingState.Quick3; if (quick3 != null && quick3.Pressed) { ControllerChordSession.Begin(controllerBindingState); runtime.UseQuick(InventoryRoleKind.Quick3, "controller"); } } internal static void ResetLog() { _loggedBindings = null; } } internal static class ControllerChordSession { private static ControllerBindingState _bindings; private static readonly HashSet Paths = new HashSet(StringComparer.Ordinal); private static int _releasedFrame = -1; internal static bool Active { get { UpdateRelease(); return _bindings != null; } } internal static void Begin(ControllerBindingState bindings) { if (bindings == null || !bindings.Ready || _bindings != null) { return; } _bindings = bindings; Paths.Clear(); for (int i = 0; i < 6; i++) { ButtonDef obj = bindings.Definition(i); string text = ((obj != null) ? obj.GetActionPath(true) : null); if (!string.IsNullOrEmpty(text)) { Paths.Add(text); } } _releasedFrame = -1; } internal static bool ShouldSuppress(string action) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Invalid comparison between Unknown and I4 UpdateRelease(); if (_bindings == null || ZInput.instance == null || string.IsNullOrEmpty(action)) { return false; } ButtonDef buttonDef; try { buttonDef = ZInput.instance.GetButtonDef(action); } catch (Exception) { return false; } if (buttonDef == null || (int)buttonDef.Source != 180) { return false; } string actionPath; try { actionPath = buttonDef.GetActionPath(true); } catch (Exception) { return false; } if (!string.IsNullOrEmpty(actionPath)) { return Paths.Contains(actionPath); } return false; } internal static void Reset() { _bindings = null; Paths.Clear(); _releasedFrame = -1; ControllerBindings.Invalidate(); ControllerInput.ResetLog(); } private static void UpdateRelease() { if (_bindings == null) { return; } bool flag = false; for (int i = 0; i < 6; i++) { ButtonDef obj = _bindings.Definition(i); if (obj != null && obj.Held) { flag = true; break; } } if (flag) { _releasedFrame = -1; } else if (_releasedFrame < 0) { _releasedFrame = Time.frameCount; } else if (_releasedFrame != Time.frameCount) { _bindings = null; Paths.Clear(); _releasedFrame = -1; } } } internal static class HarmonyOrderIds { internal const string Interaction = "chazman.RunicInteraction"; internal const string Storage = "chazman.RunicStorage"; internal const string Agriculture = "chazman.RunicAgriculture"; } [HarmonyPatch(typeof(Player), "SetLocalPlayer", new Type[] { })] internal static class LocalPlayerPatch { private static void Postfix(Player __instance) { try { Plugin.Instance?.Runtime?.OnLocalPlayerChanged(__instance); } catch (Exception exception) { Diagnostics.Error(exception, "Local-player topology bind failed closed."); } } } [HarmonyPatch(typeof(Player), "Load", new Type[] { typeof(ZPackage) })] internal static class PlayerLoadPatch { private static void Prefix(Player __instance) { if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } try { Plugin.Instance?.Runtime?.OnPlayerLoadStarted(__instance); } catch (Exception exception) { Diagnostics.Error(exception, "Player-load topology scope failed closed."); } } private static void Postfix(Player __instance) { if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } try { Plugin.Instance?.Runtime?.OnPlayerLoadCompleted(__instance); } catch (Exception exception) { Diagnostics.Error(exception, "Loaded player topology bind failed closed."); } } private static Exception Finalizer(Player __instance, Exception __exception) { if (__exception == null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return __exception; } try { Plugin.Instance?.Runtime?.OnPlayerLoadFaulted(__instance); } catch (Exception exception) { Diagnostics.Error(exception, "Faulted player-load topology cleanup failed closed."); } return __exception; } } [HarmonyPatch(typeof(Inventory), "FindEmptySlot", new Type[] { typeof(bool) })] internal static class FindEmptySlotPatch { private static bool Prefix(Inventory __instance, bool __0, ref Vector2i __result) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) InventoryRuntime inventoryRuntime = Plugin.Instance?.Runtime; if (inventoryRuntime == null) { return true; } try { if (!inventoryRuntime.TryFindEmptySlot(__instance, __0, out var result)) { return true; } __result = result; return false; } catch (Exception exception) { Diagnostics.Error(exception, "Special-row empty-slot routing faulted; vanilla routing won for this call."); return true; } } } [HarmonyPatch(typeof(Inventory), "FindFreeStackItem", new Type[] { typeof(string), typeof(int), typeof(float) })] internal static class FindFreeStackPatch { private static void Postfix(Inventory __instance, string __0, int __1, float __2, ref ItemData __result) { try { Plugin.Instance?.Runtime?.ReplaceLockedFreeStack(__instance, __0, __1, __2, ref __result); } catch (Exception exception) { Diagnostics.Error(exception, "Locked stack routing faulted; automatic stacking was declined for this call."); __result = null; } } } [HarmonyPatch(typeof(Inventory), "CanAddItem", new Type[] { typeof(ItemData), typeof(int) })] internal static class CanAddItemPatch { private static void Postfix(Inventory __instance, ItemData __0, int __1, ref bool __result) { try { Plugin.Instance?.Runtime?.AdjustCanAddItem(__instance, __0, __1, ref __result); } catch (Exception exception) { Diagnostics.Error(exception, "Safe carrying-capacity validation faulted; capacity was declined."); __result = false; } } } [HarmonyPatch(typeof(InventoryGrid), "DropItem", new Type[] { typeof(Inventory), typeof(ItemData), typeof(int), typeof(Vector2i) })] internal static class InventoryGridDropPatch { private static bool Prefix(InventoryGrid __instance, Inventory __0, ItemData __1, int __2, Vector2i __3, ref bool __result) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) InventoryRuntime inventoryRuntime = Plugin.Instance?.Runtime; if (inventoryRuntime == null) { return true; } try { if (inventoryRuntime.AllowGridDrop(__instance.GetInventory(), __0, __1, __2, __3)) { return true; } __result = false; return false; } catch (Exception exception) { Diagnostics.Error(exception, "Inventory grid validation faulted; the requested move was declined."); __result = false; return false; } } private static void Postfix(InventoryGrid __instance, Vector2i __3, bool __result) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) try { Plugin.Instance?.Runtime?.AfterGridDrop(__instance.GetInventory(), __3, __result); } catch (Exception exception) { Diagnostics.Error(exception, "Equipment-role post-drop handling failed closed."); } } } [HarmonyPatch(typeof(InventoryGui), "OnSelectedItem", new Type[] { typeof(InventoryGrid), typeof(ItemData), typeof(Vector2i), typeof(Modifier) })] internal static class InventorySelectedActionPatch { [HarmonyPriority(800)] [HarmonyBefore(new string[] { "chazman.RunicInteraction" })] private static bool Prefix(InventoryGrid __0, ItemData __1, Modifier __3) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) try { return Plugin.Instance?.Runtime?.AllowSelectedAction(__0, __1, __3) ?? true; } catch (Exception exception) { Diagnostics.Error(exception, "Locked-slot selection validation faulted; the destructive/move action was declined."); return false; } } } [HarmonyPatch(typeof(InventoryGrid), "OnRightClick", new Type[] { typeof(UIInputHandler) })] internal static class InventoryGridRightClickLockPatch { [HarmonyPriority(800)] private static bool Prefix(InventoryGrid __instance) { try { return Plugin.Instance?.Runtime?.TryTogglePointerLock(__instance) != true; } catch (Exception exception) { Diagnostics.Error(exception, "Alt-right-click slot lock failed closed."); return false; } } } [HarmonyPatch(typeof(Humanoid), "DropItem", new Type[] { typeof(Inventory), typeof(ItemData), typeof(int) })] internal static class HumanoidDropItemPatch { private static bool Prefix(Humanoid __instance, Inventory __0, ItemData __1, ref bool __result) { try { if (Plugin.Instance?.Runtime?.AllowItemAction(__instance, __0, __1, "dropping it") ?? true) { return true; } __result = false; return false; } catch (Exception exception) { Diagnostics.Error(exception, "Locked-item drop validation faulted; the drop was declined."); __result = false; return false; } } } [HarmonyPatch(typeof(Humanoid), "UseItem", new Type[] { typeof(Inventory), typeof(ItemData), typeof(bool) })] internal static class HumanoidUseItemPatch { private static bool Prefix(Humanoid __instance, Inventory __0, ItemData __1) { try { return Plugin.Instance?.Runtime?.AllowItemAction(__instance, __0, __1, "using it") ?? true; } catch (Exception exception) { Diagnostics.Error(exception, "Locked-item use validation faulted; the use was declined."); return false; } } } [HarmonyPatch(typeof(Humanoid), "EquipItem", new Type[] { typeof(ItemData), typeof(bool) })] internal static class HumanoidEquipItemPatch { [HarmonyPriority(800)] [HarmonyBefore(new string[] { "chazman.RunicInteraction" })] private static bool Prefix(Humanoid __instance, ItemData __0, ref bool __result, ref bool __state) { try { InventoryRuntime inventoryRuntime = Plugin.Instance?.Runtime; if (inventoryRuntime == null || inventoryRuntime.AllowEquip(__instance, __0)) { __state = inventoryRuntime?.BeginEquipmentTransition(__instance, __0) ?? false; return true; } __result = false; return false; } catch (Exception exception) { Diagnostics.Error(exception, "Locked equipment-target validation faulted; replacement was declined."); __result = false; return false; } } [HarmonyPriority(0)] [HarmonyAfter(new string[] { "chazman.RunicInteraction" })] private static void Postfix(Humanoid __instance, ItemData __0, bool __result) { try { Plugin.Instance?.Runtime?.OnEquipped(__instance, __0, __result); } catch (Exception exception) { Diagnostics.Error(exception, "Equipment-role relocation failed closed."); } } private static Exception Finalizer(bool __state, Exception __exception) { try { Plugin.Instance?.Runtime?.EndEquipmentTransition(__state, __exception); } catch (Exception exception) { Diagnostics.Error(exception, "Equipment transition cleanup failed closed."); try { Plugin.Instance?.Runtime?.FailClosed("equipment.transition-cleanup-faulted"); } catch (Exception exception2) { Diagnostics.Error(exception2, "Equipment transition fail-closed publication faulted."); } } return __exception; } } [HarmonyPatch(typeof(Humanoid), "Pickup", new Type[] { typeof(GameObject), typeof(bool), typeof(bool) })] internal static class PickupFilterPatch { [HarmonyPriority(800)] [HarmonyBefore(new string[] { "chazman.RunicInteraction" })] private static bool Prefix(Humanoid __instance, GameObject __0, ref bool __result, ref EquipmentAdditionState __state) { try { InventoryRuntime inventoryRuntime = Plugin.Instance?.Runtime; if (inventoryRuntime == null || inventoryRuntime.AllowPickup(__instance, __0)) { __state = inventoryRuntime?.BeginEquipmentAddition(__instance, (!Object.op_Implicit((Object)(object)__0)) ? null : __0.GetComponent()?.m_itemData); return true; } __result = false; return false; } catch (Exception exception) { Diagnostics.Error(exception, "Pickup filter faulted; vanilla pickup handling won."); return true; } } private static void Postfix(bool __result, EquipmentAdditionState __state) { try { Plugin.Instance?.Runtime?.CompleteEquipmentAddition(__state, __result); } catch (Exception exception) { Diagnostics.Error(exception, "Picked-up equipment placement failed closed."); } } } [HarmonyPatch(typeof(ItemDrop), "GetHoverText", new Type[] { })] internal static class ItemDropHoverPatch { private static void Postfix(ItemDrop __instance, ref string __result) { try { Plugin.Instance?.Runtime?.AppendPickupPreview(__instance, ref __result); } catch (Exception exception) { Diagnostics.Error(exception, "Pickup preview failed closed; vanilla hover text was retained."); } } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting", new Type[] { typeof(Player) })] internal static class InventoryCraftingPatch { [HarmonyPriority(800)] [HarmonyBefore(new string[] { "chazman.RunicCrafting" })] private static bool Prefix(InventoryGui __instance, ref EquipmentAdditionState __state) { try { InventoryRuntime inventoryRuntime = Plugin.Instance?.Runtime; int num; if (inventoryRuntime == null) { num = 1; } else { num = (inventoryRuntime.AllowCrafting(__instance) ? 1 : 0); if (num == 0) { goto IL_0036; } } __state = inventoryRuntime?.BeginEquipmentAddition((Humanoid)(object)Player.m_localPlayer); goto IL_0036; IL_0036: return (byte)num != 0; } catch (Exception exception) { Diagnostics.Error(exception, "Locked upgrade/crafting selection validation faulted; the action was declined."); return false; } } private static void Postfix(EquipmentAdditionState __state) { try { Plugin.Instance?.Runtime?.CompleteEquipmentAddition(__state, succeeded: true); } catch (Exception exception) { Diagnostics.Error(exception, "Crafted equipment placement failed closed."); } } } [HarmonyPatch(typeof(InventoryGui), "RepairOneItem")] internal static class InventoryRepairAllowancePatch { [HarmonyPriority(800)] [HarmonyBefore(new string[] { "chazman.RunicCrafting" })] private static void Prefix(ref bool __state) { try { __state = Plugin.Instance?.Runtime?.BeginRepairAllowance() == true; } catch (Exception exception) { __state = false; Diagnostics.Error(exception, "Repair protection allowance could not start; vanilla repair remains available."); } } private static Exception Finalizer(bool __state, Exception __exception) { try { Plugin.Instance?.Runtime?.EndRepairAllowance(__state); } catch (Exception exception) { Diagnostics.Error(exception, "Repair protection allowance cleanup faulted."); } return __exception; } } internal static class StationLockGuard { internal static bool Allow(Humanoid actor, ItemData item, string action, ref bool result, string nativeBoundary = null) { try { if (Plugin.Instance?.Runtime?.AllowStationItem(actor, item, action) ?? true) { return true; } result = false; return false; } catch (Exception exception) { Diagnostics.Error(exception, "Locked-item station validation faulted; the station action was declined."); result = false; return false; } } } [HarmonyPatch(typeof(Smelter), "OnAddOre", new Type[] { typeof(Switch), typeof(Humanoid), typeof(ItemData) })] internal static class SmelterOreLockPatch { [HarmonyPriority(0)] [HarmonyAfter(new string[] { "chazman.RunicSafety" })] private static bool Prefix(Humanoid __1, ItemData __2, ref bool __result) { return StationLockGuard.Allow(__1, __2, "processing it", ref __result, "Smelter.OnAddOre"); } } [HarmonyPatch(typeof(Smelter), "OnAddFuel", new Type[] { typeof(Switch), typeof(Humanoid), typeof(ItemData) })] internal static class SmelterFuelLockPatch { [HarmonyPriority(0)] [HarmonyAfter(new string[] { "chazman.RunicSafety" })] private static bool Prefix(Humanoid __1, ItemData __2, ref bool __result) { return StationLockGuard.Allow(__1, __2, "processing it", ref __result, "Smelter.OnAddFuel"); } } [HarmonyPatch(typeof(CookingStation), "CookItem", new Type[] { typeof(Humanoid), typeof(ItemData) })] internal static class CookingItemLockPatch { private static bool Prefix(Humanoid __0, ItemData __1, ref bool __result) { return StationLockGuard.Allow(__0, __1, "cooking it", ref __result, "CookingStation.CookItem"); } } [HarmonyPatch(typeof(CookingStation), "OnAddFuelSwitch", new Type[] { typeof(Switch), typeof(Humanoid), typeof(ItemData) })] internal static class CookingFuelLockPatch { [HarmonyPriority(400)] [HarmonyAfter(new string[] { "chazman.RunicSafety" })] [HarmonyBefore(new string[] { "chazman.RunicProduction" })] private static bool Prefix(Humanoid __1, ItemData __2, ref bool __result) { return StationLockGuard.Allow(__1, __2, "processing it", ref __result, "CookingStation.OnAddFuelSwitch"); } } [HarmonyPatch(typeof(Fermenter), "AddItem", new Type[] { typeof(Humanoid), typeof(ItemData) })] internal static class FermenterLockPatch { [HarmonyPriority(400)] [HarmonyAfter(new string[] { "chazman.RunicSafety" })] [HarmonyBefore(new string[] { "chazman.RunicProduction" })] private static bool Prefix(Humanoid __0, ItemData __1, ref bool __result) { return StationLockGuard.Allow(__0, __1, "fermenting it", ref __result); } } [HarmonyPatch(typeof(Incinerator), "OnIncinerate", new Type[] { typeof(Switch), typeof(Humanoid), typeof(ItemData) })] internal static class IncineratorLockPatch { [HarmonyPriority(0)] [HarmonyAfter(new string[] { "chazman.RunicSafety" })] private static bool Prefix(Humanoid __1, ItemData __2, ref bool __result) { return StationLockGuard.Allow(__1, __2, "incinerating it", ref __result); } } [HarmonyPatch(typeof(ItemStand), "UseItem", new Type[] { typeof(Humanoid), typeof(ItemData) })] internal static class ItemStandLockPatch { [HarmonyPriority(0)] [HarmonyAfter(new string[] { "chazman.RunicSafety" })] private static bool Prefix(Humanoid __0, ItemData __1, ref bool __result) { return StationLockGuard.Allow(__0, __1, "displaying it", ref __result, "ItemStand.UseItem"); } } [HarmonyPatch(typeof(OfferingBowl), "UseItem", new Type[] { typeof(Humanoid), typeof(ItemData) })] internal static class OfferingLockPatch { private static bool Prefix(Humanoid __0, ItemData __1, ref bool __result) { return StationLockGuard.Allow(__0, __1, "sacrificing it", ref __result, "OfferingBowl.UseItem"); } } [HarmonyPatch(typeof(ShieldGenerator), "OnAddFuel", new Type[] { typeof(Switch), typeof(Humanoid), typeof(ItemData) })] internal static class ShieldFuelLockPatch { private static bool Prefix(Humanoid __1, ItemData __2, ref bool __result) { return StationLockGuard.Allow(__1, __2, "processing it", ref __result, "ShieldGenerator.OnAddFuel"); } } [HarmonyPatch(typeof(ZInput), "GetButton", new Type[] { typeof(string) })] internal static class ControllerGetButtonPatch { [HarmonyPriority(0)] [HarmonyAfter(new string[] { "chazman.RunicStorage", "chazman.RunicAgriculture" })] private static bool Prefix(string name, ref bool __result) { return Suppress(name, ref __result); } private static bool Suppress(string name, ref bool result) { if (!InputReservation.ShouldSuppress(name)) { return true; } result = false; return false; } } [HarmonyPatch(typeof(ZInput), "GetButtonDown", new Type[] { typeof(string) })] internal static class ControllerGetButtonDownPatch { [HarmonyPriority(0)] [HarmonyAfter(new string[] { "chazman.RunicStorage", "chazman.RunicAgriculture" })] private static bool Prefix(string name, ref bool __result) { if (!InputReservation.ShouldSuppress(name)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(ZInput), "GetButtonUp", new Type[] { typeof(string) })] internal static class ControllerGetButtonUpPatch { [HarmonyPriority(0)] [HarmonyAfter(new string[] { "chazman.RunicStorage", "chazman.RunicAgriculture" })] private static bool Prefix(string name, ref bool __result) { if (!InputReservation.ShouldSuppress(name)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(ZInput), "GetButtonPressedTimer", new Type[] { typeof(string) })] internal static class ControllerPressedTimerPatch { [HarmonyPriority(0)] [HarmonyAfter(new string[] { "chazman.RunicStorage", "chazman.RunicAgriculture" })] private static bool Prefix(string name, ref float __result) { if (!InputReservation.ShouldSuppress(name)) { return true; } __result = 0f; return false; } } [HarmonyPatch(typeof(ZInput), "GetButtonLastPressedTimer", new Type[] { typeof(string) })] internal static class ControllerLastPressedTimerPatch { [HarmonyPriority(0)] [HarmonyAfter(new string[] { "chazman.RunicStorage", "chazman.RunicAgriculture" })] private static bool Prefix(string name, ref float __result) { if (!InputReservation.ShouldSuppress(name)) { return true; } __result = 0f; return false; } } internal sealed class ItemMutationEvidence { internal ItemData Item { get; } internal Vector2i Coordinate { get; } internal string Fingerprint { get; } internal ItemMutationEvidence(ItemData item, Vector2i coordinate, string fingerprint) { //IL_000e: 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) Item = item; Coordinate = coordinate; Fingerprint = fingerprint; } } internal static class InventoryEvidence { private sealed class ReferenceComparer : IEqualityComparer where T : class { internal static readonly ReferenceComparer Instance = new ReferenceComparer(); public bool Equals(T x, T y) { return x == y; } public int GetHashCode(T obj) { return RuntimeHelpers.GetHashCode(obj); } } internal const int MaximumCustomEntriesPerItem = 64; internal const int MaximumCustomStringCharacters = 4096; internal const int MaximumAggregateCustomCharacters = 65536; internal const int MaximumSerializedBytes = 1048576; internal static bool TryCaptureMutation(Inventory inventory, out IReadOnlyList evidence, out string reasonCode) { //IL_00ee: Unknown result type (might be due to invalid IL or missing references) evidence = Array.Empty(); if (inventory == null) { reasonCode = "evidence.inventory-null"; return false; } List allItems = inventory.GetAllItems(); if (allItems == null || allItems.Count > 128) { reasonCode = "evidence.item-bound-exceeded"; return false; } HashSet hashSet = new HashSet(); List list = new List(allItems.Count); int aggregateCustom = 0; foreach (ItemData item in allItems) { if (item == null || item.m_gridPos.x < 0 || item.m_gridPos.x >= inventory.GetWidth() || item.m_gridPos.y < 0 || item.m_gridPos.y >= inventory.GetHeight() || !hashSet.Add(item.m_gridPos.y * inventory.GetWidth() + item.m_gridPos.x) || !TryFingerprint(item, includePosition: false, ref aggregateCustom, out var fingerprint)) { reasonCode = "evidence.item-invalid"; return false; } list.Add(new ItemMutationEvidence(item, item.m_gridPos, fingerprint)); } evidence = list.AsReadOnly(); reasonCode = "ok"; return true; } internal static bool VerifyUnchangedExceptPosition(Inventory inventory, IReadOnlyList before, out string reasonCode) { if (inventory == null || before == null || inventory.GetAllItems().Count != before.Count) { reasonCode = "evidence.item-count-changed"; return false; } HashSet hashSet = new HashSet(ReferenceComparer.Instance); foreach (ItemData allItem in inventory.GetAllItems()) { hashSet.Add(allItem); } HashSet hashSet2 = new HashSet(); int aggregateCustom = 0; foreach (ItemMutationEvidence item2 in before) { ItemData item = item2.Item; if (!hashSet.Contains(item) || item.m_gridPos.x < 0 || item.m_gridPos.x >= inventory.GetWidth() || item.m_gridPos.y < 0 || item.m_gridPos.y >= inventory.GetHeight() || !hashSet2.Add(item.m_gridPos.y * inventory.GetWidth() + item.m_gridPos.x) || !TryFingerprint(item, includePosition: false, ref aggregateCustom, out var fingerprint) || !string.Equals(fingerprint, item2.Fingerprint, StringComparison.Ordinal)) { reasonCode = "evidence.metadata-or-membership-changed"; return false; } } reasonCode = "ok"; return true; } internal static void RestorePositions(IReadOnlyList evidence) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) if (evidence == null) { return; } foreach (ItemMutationEvidence item in evidence) { if (item?.Item != null) { item.Item.m_gridPos = item.Coordinate; } } } internal static bool TryFingerprint(ItemData item, bool includePosition, out string fingerprint) { int aggregateCustom = 0; return TryFingerprint(item, includePosition, ref aggregateCustom, out fingerprint); } internal static bool TryDeterministicSave(Inventory inventory, out string reasonCode) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown try { ZPackage val = new ZPackage(); inventory.Save(val); byte[] array = val.GetArray(); if (array == null || array.Length > 1048576) { reasonCode = "serialization.payload-bound"; return false; } ZPackage val2 = new ZPackage(); inventory.Save(val2); byte[] array2 = val2.GetArray(); if (array2 == null || array2.Length != array.Length) { reasonCode = "serialization.nondeterministic"; return false; } int num = 0; for (int i = 0; i < array.Length; i++) { num |= array[i] ^ array2[i]; } reasonCode = ((num == 0) ? "ok" : "serialization.nondeterministic"); return num == 0; } catch (Exception) { reasonCode = "serialization.failed"; return false; } } internal static string HashTopology(string value) { using SHA256 sHA = SHA256.Create(); byte[] array = sHA.ComputeHash(Encoding.UTF8.GetBytes(value ?? string.Empty)); StringBuilder stringBuilder = new StringBuilder(64); byte[] array2 = array; foreach (byte b in array2) { stringBuilder.Append(b.ToString("x2", CultureInfo.InvariantCulture)); } return stringBuilder.ToString(); } private static bool TryFingerprint(ItemData item, bool includePosition, ref int aggregateCustom, out string fingerprint) { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected I4, but got Unknown fingerprint = string.Empty; if (item == null || item.m_shared == null || item.m_stack <= 0) { return false; } string text = ValheimContracts.PrefabId(item); string text2 = item.m_shared.m_name ?? string.Empty; string text3 = item.m_crafterName ?? string.Empty; if (text.Length == 0 || text2.Length > 256 || text3.Length > 256) { return false; } StringBuilder stringBuilder = new StringBuilder(512); Append(stringBuilder, text); Append(stringBuilder, text2); stringBuilder.Append('|').Append((int)item.m_shared.m_itemType).Append('|') .Append(item.m_stack) .Append('|') .Append(BitConverter.SingleToInt32Bits(item.m_durability)) .Append('|') .Append(item.m_equipped ? 1 : 0) .Append('|') .Append(item.m_quality) .Append('|') .Append(item.m_variant) .Append('|') .Append(item.m_crafterID) .Append('|') .Append(item.m_worldLevel) .Append('|') .Append(item.m_pickedUp ? 1 : 0); Append(stringBuilder, text3); if (includePosition) { stringBuilder.Append('|').Append(item.m_gridPos.x).Append('|') .Append(item.m_gridPos.y); } int num = item.m_customData?.Count ?? 0; if (num > 64) { return false; } List list = new List(num); if (item.m_customData != null) { foreach (KeyValuePair customDatum in item.m_customData) { string text4 = customDatum.Key ?? string.Empty; string text5 = customDatum.Value ?? string.Empty; if (text4.Length > 4096 || text5.Length > 4096) { return false; } aggregateCustom += text4.Length + text5.Length; if (aggregateCustom > 65536) { return false; } list.Add(text4); } list.Sort(StringComparer.Ordinal); foreach (string item2 in list) { Append(stringBuilder, item2); Append(stringBuilder, item.m_customData[item2] ?? string.Empty); } } fingerprint = HashTopology(stringBuilder.ToString()); return true; } private static void Append(StringBuilder builder, string value) { builder.Append('|').Append(value.Length).Append(':') .Append(value); } } internal sealed class EquipmentAdditionState { internal Player Player { get; } internal Inventory Inventory { get; } internal InventoryRoleKind? ExpectedRole { get; } internal IReadOnlyList Before { get; } internal EquipmentAdditionState(Player player, Inventory inventory, InventoryRoleKind? expectedRole, IReadOnlyList before) { Player = player; Inventory = inventory; ExpectedRole = expectedRole; Before = before; } } internal readonly struct StackCapacityKey : IEquatable { internal string SharedName { get; } internal int Quality { get; } internal int WorldLevel { get; } internal StackCapacityKey(string sharedName, int quality, int worldLevel) { SharedName = sharedName ?? string.Empty; Quality = quality; WorldLevel = worldLevel; } public bool Equals(StackCapacityKey other) { if (Quality == other.Quality && WorldLevel == other.WorldLevel) { return string.Equals(SharedName, other.SharedName, StringComparison.Ordinal); } return false; } public override bool Equals(object obj) { if (obj is StackCapacityKey other) { return Equals(other); } return false; } public override int GetHashCode() { return (((StringComparer.Ordinal.GetHashCode(SharedName) * 397) ^ Quality) * 397) ^ WorldLevel; } } internal sealed class InventoryRuntime : IInventoryTopologyService, IInventoryProtectionService, IInventoryStatusService, IItemProtectionQuery, IDisposable { private sealed class MutationScope : IDisposable { private InventoryRuntime _owner; internal MutationScope(InventoryRuntime owner) { _owner = owner; } public void Dispose() { InventoryRuntime inventoryRuntime = Interlocked.Exchange(ref _owner, null); if (inventoryRuntime != null) { Interlocked.Exchange(ref inventoryRuntime._mutationActive, 0); } } } private const int MaximumProtectionDiagnostics = 32; private readonly bool _batch; private readonly int _mainThreadId; private readonly Dictionary _stackCapacity = new Dictionary(); private readonly HashSet _protectionDiagnostics = new HashSet(StringComparer.Ordinal); private readonly object _protectionDiagnosticGate = new object(); private Player _player; private Inventory _inventory; private TopologyLayout _layout; private PersistedTopologyState _persisted; private InventoryTopologySnapshot _snapshot; private PickupFilterSet _filters; private InventoryAuthorityMode _mode; private string _reasonCode = "runtime.not-initialized"; private string _statusText = "Runic Inventory: waiting for the local player."; private long _generation; private int _freePickupSlots; private float _cachedWeight; private bool _topologyActive; private bool _playerLoadInProgress; private bool _loadMetadataRefreshed; private int _equipmentTransitionDepth; private int _repairAllowanceDepth; private int _mutationActive; private bool _equipmentRefreshPending; private bool _equipmentTransitionFaulted; private bool _disableCleanupPending; private bool _rebuilding; private bool _disposed; private float _nextMessageTime; private readonly Rect[] _roleSlotRects = (Rect[])(object)new Rect[8]; private static readonly string[] RoleLabels = new string[8] { "Helmet", "Chest", "Legs", "Cape", "Utility", "Quick 1", "Quick 2", "Quick 3" }; public string ProviderId => "runic.inventory"; internal bool TopologyActive { get { if (_topologyActive && !_disposed && _mode == InventoryAuthorityMode.AuthoritativeLocal && IsAuthoritativeLocal(_player)) { return LiveDimensionsMatch(); } return false; } } internal InventoryAuthorityMode Mode => _mode; internal bool AcceptsInput => CanEnforceLocks(); internal InventoryRuntime(bool batch) { _batch = batch; _mainThreadId = Thread.CurrentThread.ManagedThreadId; _filters = PickupFilterSet.Parse(InventoryConfig.FilteredPickupItems?.Value ?? string.Empty); _mode = (batch ? InventoryAuthorityMode.BatchInert : InventoryAuthorityMode.Unavailable); _reasonCode = (batch ? "authority.batch-inert" : "player.not-loaded"); } internal void Initialize() { if (_batch) { RebuildStatusOnly(); } else { Rebind(Player.m_localPlayer, "startup"); } } internal void Tick() { //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) if (_disposed || _batch) { return; } if (_player != Player.m_localPlayer) { Rebind(Player.m_localPlayer, "local-player-changed"); } if (!Object.op_Implicit((Object)(object)_player) || _inventory == null) { return; } if (_disableCleanupPending) { ConfigEntry enabled = InventoryConfig.Enabled; if (enabled != null && enabled.Value) { _disableCleanupPending = false; Rebind(Player.m_localPlayer, "disable-cleanup-cancelled"); } else { if (!TryCompleteDisableCleanup()) { return; } Rebind(Player.m_localPlayer, "disable-cleanup-completed"); } if (!Object.op_Implicit((Object)(object)_player) || _inventory == null) { return; } } if (_layout != null && !LiveDimensionsMatch()) { FailClosed("topology.runtime-dimension-changed"); return; } bool flag = IsAuthoritativeLocal(_player); if ((_mode == InventoryAuthorityMode.AuthoritativeLocal && !flag) || (_mode == InventoryAuthorityMode.RemoteDedicatedCompatibility && flag)) { Rebind(_player, "authority-changed"); if (!Object.op_Implicit((Object)(object)_player) || _inventory == null) { return; } } if (!AcceptsInput) { return; } bool flag2 = (Object)(object)InventoryGui.instance != (Object)null && InventoryGui.IsVisible(); if (flag2) { if (KeyboardInput.ShortcutDown(InventoryConfig.Sort.Value)) { SortSelectedRows("keyboard"); } if (KeyboardInput.ShortcutDown(InventoryConfig.ToggleLock.Value)) { ToggleFocusedLock("keyboard"); } } else if (ValheimContracts.PlayerMayTakeInput(_player)) { if (KeyboardInput.ShortcutDown(InventoryConfig.Quick1.Value)) { UseQuick(InventoryRoleKind.Quick1, "keyboard"); } else if (KeyboardInput.ShortcutDown(InventoryConfig.Quick2.Value)) { UseQuick(InventoryRoleKind.Quick2, "keyboard"); } else if (KeyboardInput.ShortcutDown(InventoryConfig.Quick3.Value)) { UseQuick(InventoryRoleKind.Quick3, "keyboard"); } } ControllerInput.Tick(this, flag2, !flag2 && ValheimContracts.PlayerMayTakeInput(_player)); } internal void Draw() { //IL_00ab: Unknown result type (might be due to invalid IL or missing references) if (_disposed || _batch || (Object)(object)InventoryGui.instance == (Object)null || !InventoryGui.IsVisible() || ValheimContracts.InventoryModalVisible()) { return; } if (TopologyActive) { ConfigEntry showRoleLabels = InventoryConfig.ShowRoleLabels; if (showRoleLabels == null || showRoleLabels.Value) { DrawRoleOverlay(); } DrawLockedSlotOverlay(); } ConfigEntry showInventoryStatus = InventoryConfig.ShowInventoryStatus; if (showInventoryStatus != null && showInventoryStatus.Value) { float num = Math.Min(500f, Math.Max(300f, (float)Screen.width - 20f)); GUI.Box(new Rect(Math.Max(10f, (float)Screen.width - num - 10f), 72f, num, 190f), _statusText); } } private void DrawRoleOverlay() { //IL_035c: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0202: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Expected O, but got Unknown //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0236: Unknown result type (might be due to invalid IL or missing references) //IL_026c: Unknown result type (might be due to invalid IL or missing references) //IL_02c9: Unknown result type (might be due to invalid IL or missing references) //IL_02fb: Unknown result type (might be due to invalid IL or missing references) //IL_032a: Unknown result type (might be due to invalid IL or missing references) //IL_0334: Unknown result type (might be due to invalid IL or missing references) InventoryGrid val = InventoryGui.instance?.m_playerGrid; if (!Object.op_Implicit((Object)(object)val) || _layout == null || !ValheimContracts.TryBottomRowScreenRects(val, _layout.SpecialRow, _roleSlotRects)) { return; } Color color = GUI.color; int depth = GUI.depth; try { GUI.depth = -650; Rect val2 = _roleSlotRects[0]; Rect val3 = _roleSlotRects[_roleSlotRects.Length - 1]; Rect val4 = default(Rect); ((Rect)(ref val4))..ctor(((Rect)(ref val2)).xMin - 2f, Math.Min(((Rect)(ref val2)).yMin, ((Rect)(ref val3)).yMin) - 2f, ((Rect)(ref val3)).xMax - ((Rect)(ref val2)).xMin + 4f, Math.Max(((Rect)(ref val2)).yMax, ((Rect)(ref val3)).yMax) - Math.Min(((Rect)(ref val2)).yMin, ((Rect)(ref val3)).yMin) + 4f); GUI.color = new Color(0.7f, 0.43f, 0.13f, 0.48f); GUI.DrawTexture(new Rect(((Rect)(ref val4)).xMin, ((Rect)(ref val4)).yMin, ((Rect)(ref val4)).width, 1f), (Texture)(object)Texture2D.whiteTexture); GUI.DrawTexture(new Rect(((Rect)(ref val4)).xMin, ((Rect)(ref val4)).yMax - 1f, ((Rect)(ref val4)).width, 1f), (Texture)(object)Texture2D.whiteTexture); GUI.DrawTexture(new Rect(((Rect)(ref val4)).xMin, ((Rect)(ref val4)).yMin, 1f, ((Rect)(ref val4)).height), (Texture)(object)Texture2D.whiteTexture); GUI.DrawTexture(new Rect(((Rect)(ref val4)).xMax - 1f, ((Rect)(ref val4)).yMin, 1f, ((Rect)(ref val4)).height), (Texture)(object)Texture2D.whiteTexture); GUIStyle val5 = new GUIStyle(GUI.skin.label) { alignment = (TextAnchor)7, fontStyle = (FontStyle)1, fontSize = Math.Max(8, Math.Min(11, (int)(((Rect)(ref _roleSlotRects[0])).width / 7f))), clipping = (TextClipping)1, wordWrap = false }; Rect val7 = default(Rect); for (int i = 0; i < _roleSlotRects.Length; i++) { Rect val6 = _roleSlotRects[i]; GUI.color = new Color(0f, 0f, 0f, 0.7f); GUI.DrawTexture(new Rect(((Rect)(ref val6)).x + 1f, ((Rect)(ref val6)).y + 1f, ((Rect)(ref val6)).width - 2f, 17f), (Texture)(object)Texture2D.whiteTexture); ((Rect)(ref val7))..ctor(((Rect)(ref val6)).x + 2f, ((Rect)(ref val6)).y + 1f, ((Rect)(ref val6)).width - 4f, 16f); val5.normal.textColor = new Color(0.05f, 0.03f, 0.01f, 0.92f); GUI.Label(new Rect(((Rect)(ref val7)).x + 1f, ((Rect)(ref val7)).y + 1f, ((Rect)(ref val7)).width, ((Rect)(ref val7)).height), RoleLabels[i], val5); val5.normal.textColor = new Color(1f, 0.78f, 0.34f, 0.98f); GUI.Label(val7, RoleLabels[i], val5); } } finally { GUI.color = color; GUI.depth = depth; } } private void DrawLockedSlotOverlay() { //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) InventoryGrid val = InventoryGui.instance?.m_playerGrid; if (!Object.op_Implicit((Object)(object)val) || _layout == null || _persisted == null) { return; } Color color = GUI.color; int depth = GUI.depth; try { GUI.depth = -660; GUI.color = new Color(1f, 0.84f, 0.08f, 1f); foreach (InventorySlotCoordinate item in _persisted.LockedSlots()) { if (ValheimContracts.TrySlotScreenRect(val, item.X, item.Y, out var slot)) { DrawOutline(new Rect(((Rect)(ref slot)).xMin - 2f, ((Rect)(ref slot)).yMin - 2f, ((Rect)(ref slot)).width + 4f, ((Rect)(ref slot)).height + 4f), 4f); } } } finally { GUI.color = color; GUI.depth = depth; } } private static void DrawOutline(Rect rect, float thickness) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) GUI.DrawTexture(new Rect(((Rect)(ref rect)).xMin, ((Rect)(ref rect)).yMin, ((Rect)(ref rect)).width, thickness), (Texture)(object)Texture2D.whiteTexture); GUI.DrawTexture(new Rect(((Rect)(ref rect)).xMin, ((Rect)(ref rect)).yMax - thickness, ((Rect)(ref rect)).width, thickness), (Texture)(object)Texture2D.whiteTexture); GUI.DrawTexture(new Rect(((Rect)(ref rect)).xMin, ((Rect)(ref rect)).yMin, thickness, ((Rect)(ref rect)).height), (Texture)(object)Texture2D.whiteTexture); GUI.DrawTexture(new Rect(((Rect)(ref rect)).xMax - thickness, ((Rect)(ref rect)).yMin, thickness, ((Rect)(ref rect)).height), (Texture)(object)Texture2D.whiteTexture); } internal void OnConfigurationChanged() { if (!_disposed) { if ((_filters = PickupFilterSet.Parse(InventoryConfig.FilteredPickupItems.Value)).Truncated) { Diagnostics.Warn("Pickup filter exceeded 128 safe unique rules; additional entries were ignored."); } ControllerBindings.Invalidate(); ControllerChordSession.Reset(); ConfigEntry enabled = InventoryConfig.Enabled; if (enabled != null && enabled.Value) { _disableCleanupPending = false; } Rebind(Player.m_localPlayer, "configuration-changed"); } } internal void OnLocalPlayerChanged(Player player) { Rebind(player, "set-local-player"); } internal void OnPlayerLoadStarted(Player player) { if (!_disposed && !((Object)(object)player != (Object)(object)_player) && !((Object)(object)player != (Object)(object)Player.m_localPlayer)) { _playerLoadInProgress = true; _loadMetadataRefreshed = false; } } internal void OnPlayerLoadCompleted(Player player) { if (!_disposed && !((Object)(object)player != (Object)(object)Player.m_localPlayer)) { _playerLoadInProgress = false; _loadMetadataRefreshed = false; Rebind(player, "player-load-completed"); } } internal void OnPlayerLoadFaulted(Player player) { if (!_disposed && !((Object)(object)player != (Object)(object)_player)) { _playerLoadInProgress = false; _loadMetadataRefreshed = false; FailClosed("player.load-faulted"); } } internal void FailClosed(string reasonCode) { _repairAllowanceDepth = 0; _topologyActive = false; _snapshot = null; _mode = (_batch ? InventoryAuthorityMode.BatchInert : InventoryAuthorityMode.MigrationSafeCompatibility); _reasonCode = BoundReason(reasonCode, "runtime.fail-closed"); RebuildStatusOnly(); } public bool TryCapture(long playerId, out InventoryTopologySnapshot snapshot, out string failureCode) { snapshot = null; if (_disposed) { failureCode = "provider.disposed"; return false; } if (Thread.CurrentThread.ManagedThreadId != _mainThreadId) { failureCode = "thread.main-required"; return false; } if (!Object.op_Implicit((Object)(object)_player) || playerId <= 0 || playerId != _player.GetPlayerID()) { failureCode = "player.not-local"; return false; } if (_layout != null && !LiveDimensionsMatch()) { FailClosed("topology.runtime-dimension-changed"); failureCode = "topology.runtime-dimension-changed"; return false; } bool flag = IsAuthoritativeLocal(_player); if ((_mode == InventoryAuthorityMode.AuthoritativeLocal && !flag) || (_mode == InventoryAuthorityMode.RemoteDedicatedCompatibility && flag)) { failureCode = "authority.transition-pending"; return false; } if (_snapshot == null || !_snapshot.SerializationVerified) { RebuildCache("api-capture", verifySerialization: true); if (_snapshot == null || !_snapshot.SerializationVerified) { failureCode = ((_snapshot == null) ? _reasonCode : "serialization.unverified"); return false; } } snapshot = _snapshot; failureCode = "ok"; return true; } public bool TryIsLocked(long playerId, InventorySlotCoordinate coordinate, out bool locked, out string failureCode) { locked = false; if (_disposed || Thread.CurrentThread.ManagedThreadId != _mainThreadId) { failureCode = (_disposed ? "provider.disposed" : "thread.main-required"); return false; } if (!Object.op_Implicit((Object)(object)_player) || playerId <= 0 || playerId != _player.GetPlayerID()) { failureCode = "player.not-local"; return false; } if (!CanEnforceLocks()) { failureCode = "authority.local-required"; return false; } if (_persisted == null || _layout == null || !_layout.InBounds(coordinate) || _persisted.Width != _layout.Width || _persisted.Height != _layout.Height) { failureCode = "locks.topology-unavailable"; return false; } locked = _persisted.IsLocked(coordinate.X, coordinate.Y); failureCode = "ok"; return true; } public bool TryGetProtection(object nativeItem, out ItemProtectionState state) { state = ItemProtectionState.Unknown; ItemData val = (ItemData)((nativeItem is ItemData) ? nativeItem : null); if (val == null) { TraceProtectionDecision("not-applicable.not-native-item"); return false; } if ((InventoryConfig.Enabled != null && !InventoryConfig.Enabled.Value) || _mode == InventoryAuthorityMode.Disabled) { TraceProtectionDecision("not-applicable.feature-disabled"); return false; } if (_disposed) { TraceProtectionDecision("unknown.provider-disposed"); return true; } if (_inventory == null) { TraceProtectionDecision("unknown.inventory-unavailable"); return true; } if (!Object.op_Implicit((Object)(object)_player)) { TraceProtectionDecision("unknown.player-unavailable"); return true; } if (Thread.CurrentThread.ManagedThreadId != _mainThreadId) { TraceProtectionDecision("unknown.thread-main-required"); return true; } try { List allItems = _inventory.GetAllItems(); switch (ItemProtectionDomain.Evaluate(allItems, val, 128)) { case ItemProtectionDomainEvidence.NotApplicable: TraceProtectionDecision("not-applicable.foreign-inventory-item"); return false; default: TraceProtectionDecision("unknown.domain-membership-indeterminate"); return true; case ItemProtectionDomainEvidence.ExactCurrentMember: { if (val.m_shared == null || val.m_stack <= 0) { TraceProtectionDecision("unknown.domain-item-shape-invalid"); return true; } if (!CanEnforceLocks()) { TraceProtectionDecision("unknown.topology-or-authority-inactive"); return true; } if (_layout == null || _persisted == null) { TraceProtectionDecision("unknown.topology-unavailable"); return true; } if (!LiveDimensionsMatch()) { TraceProtectionDecision("unknown.dimensions-live-mismatch"); return true; } if (_persisted.Width != _layout.Width || _persisted.Height != _layout.Height) { TraceProtectionDecision("unknown.dimensions-persisted-mismatch"); return true; } int x = val.m_gridPos.x; int y = val.m_gridPos.y; if (x < 0 || x >= _layout.Width || y < 0 || y >= _layout.Height) { TraceProtectionDecision("unknown.domain-item-coordinate-invalid"); return true; } ulong num = 0uL; ulong num2 = 0uL; for (int i = 0; i < allItems.Count; i++) { ItemData val2 = allItems[i]; if (val2 == null || val2.m_shared == null || val2.m_stack <= 0 || val2.m_gridPos.x < 0 || val2.m_gridPos.x >= _layout.Width || val2.m_gridPos.y < 0 || val2.m_gridPos.y >= _layout.Height) { TraceProtectionDecision("unknown.domain-member-shape-invalid"); return true; } int num3 = val2.m_gridPos.y * _layout.Width + val2.m_gridPos.x; if (num3 < 64) { ulong num4 = (ulong)(1L << num3); if ((num & num4) != 0L) { TraceProtectionDecision("unknown.domain-duplicate-coordinate"); return true; } num |= num4; } else { ulong num5 = (ulong)(1L << num3 - 64); if ((num2 & num5) != 0L) { TraceProtectionDecision("unknown.domain-duplicate-coordinate"); return true; } num2 |= num5; } } if (val.m_gridPos.x != x || val.m_gridPos.y != y) { TraceProtectionDecision("unknown.reference-item-coordinate-mutated"); return true; } List allItems2 = _inventory.GetAllItems(); if (!SameItemReferences(allItems, allItems2)) { TraceProtectionDecision("unknown.reference-membership-list-replaced"); return true; } if (_inventory.GetItemAt(x, y) != val) { TraceProtectionDecision("unknown.reference-coordinate-occupant-mutated"); return true; } if (!CanEnforceLocks()) { TraceProtectionDecision("unknown.topology-transition-during-proof"); return true; } if (!LiveDimensionsMatch()) { TraceProtectionDecision("unknown.dimensions-transition-during-proof"); return true; } state = ((_repairAllowanceDepth > 0) ? ItemProtectionState.Unlocked : ItemProtectionDomain.ClassifyExactMember(y == _layout.SpecialRow, _persisted.IsLocked(x, y))); return true; } } } catch (Exception exception) { state = ItemProtectionState.Unknown; TraceProtectionDecision("unknown.exception", exception); return true; } } public InventoryFeatureStatus Snapshot() { if (_disposed) { return new InventoryFeatureStatus(InventoryAuthorityMode.Unavailable, topologyActive: false, "provider.disposed"); } if (Thread.CurrentThread.ManagedThreadId != _mainThreadId) { return new InventoryFeatureStatus(InventoryAuthorityMode.Unavailable, topologyActive: false, "thread.main-required"); } if (_layout != null && !LiveDimensionsMatch()) { return new InventoryFeatureStatus(InventoryAuthorityMode.MigrationSafeCompatibility, topologyActive: false, "topology.runtime-dimension-changed"); } bool flag = IsAuthoritativeLocal(_player); if ((_mode == InventoryAuthorityMode.AuthoritativeLocal && !flag) || (_mode == InventoryAuthorityMode.RemoteDedicatedCompatibility && flag)) { return new InventoryFeatureStatus(InventoryAuthorityMode.MigrationSafeCompatibility, topologyActive: false, "authority.transition-pending"); } return new InventoryFeatureStatus(_mode, TopologyActive, _reasonCode); } internal bool TryFindEmptySlot(Inventory inventory, bool topFirst, out Vector2i result) { //IL_0003: 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) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) result = new Vector2i(-1, -1); if (!TopologyActive || inventory != _inventory || _layout == null) { return false; } if (topFirst) { for (int i = 0; i < _layout.SpecialRow; i++) { for (int j = 0; j < _layout.Width; j++) { if (inventory.GetItemAt(j, i) == null) { result = new Vector2i(j, i); return true; } } } } else { for (int num = _layout.SpecialRow - 1; num >= 0; num--) { for (int k = 0; k < _layout.Width; k++) { if (inventory.GetItemAt(k, num) == null) { result = new Vector2i(k, num); return true; } } } } return true; } internal bool AllowGridDrop(Inventory destination, Inventory source, ItemData item, int amount, Vector2i destinationPosition) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) if (_disposed || item == null) { return true; } if (source == _inventory && IsLocked(item)) { Notify("Runic Inventory: that slot is locked."); return false; } if (destination == _inventory && IsLocked(destinationPosition.x, destinationPosition.y) && (source != _inventory || item.m_gridPos.x != destinationPosition.x || item.m_gridPos.y != destinationPosition.y)) { Notify("Runic Inventory: the destination slot is locked."); return false; } if (!TopologyActive || destination != _inventory || _layout == null) { return true; } if (destinationPosition.x < 0 || destinationPosition.x >= _layout.Width || destinationPosition.y < 0 || destinationPosition.y >= _layout.Height) { return true; } if (_layout.TryRoleAt(destinationPosition.x, destinationPosition.y, out var role) && !TopologyLayout.Accepts(role, ValheimContracts.Category(item))) { Notify("Runic Inventory: that item does not belong in " + RoleLabel(role) + "."); return false; } if (source == _inventory && item.m_equipped && _layout.TryRoleAt(item.m_gridPos.x, item.m_gridPos.y, out var role2) && IsEquipmentRole(role2) && (destinationPosition.x != item.m_gridPos.x || destinationPosition.y != item.m_gridPos.y)) { Notify("Runic Inventory: unequip that item before moving it out of its equipment role."); return false; } return amount > 0; } internal void ReplaceLockedFreeStack(Inventory inventory, string sharedName, int quality, float worldLevel, ref ItemData result) { if (inventory != _inventory || result == null || !IsLocked(result)) { return; } result = null; List allItems = inventory.GetAllItems(); if (allItems == null || allItems.Count > 128) { FailClosed("locks.stack-evidence-bound"); return; } foreach (ItemData item in allItems) { if (item?.m_shared != null && !IsLocked(item) && item.m_shared.m_name == sharedName && item.m_quality == quality && item.m_stack < item.m_shared.m_maxStackSize && (float)item.m_worldLevel == worldLevel) { result = item; break; } } } internal void AdjustCanAddItem(Inventory inventory, ItemData item, int requestedStack, ref bool result) { if (result && TopologyActive && inventory == _inventory && item?.m_shared != null) { int num = ((requestedStack <= 0) ? item.m_stack : requestedStack); if (num <= 0) { result = false; return; } _stackCapacity.TryGetValue(new StackCapacityKey(item.m_shared.m_name, item.m_quality, item.m_worldLevel), out var value); long num2 = value + (long)_freePickupSlots * (long)Math.Max(1, item.m_shared.m_maxStackSize); result = num2 >= num; } } internal void AfterGridDrop(Inventory destination, Vector2i position, bool succeeded) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) if (!succeeded || !TopologyActive || destination != _inventory || _layout == null || !_layout.TryRoleAt(position.x, position.y, out var role) || !IsEquipmentRole(role)) { return; } ItemData itemAt = _inventory.GetItemAt(position.x, position.y); if (itemAt == null || itemAt.m_equipped || !TopologyLayout.Accepts(role, ValheimContracts.Category(itemAt))) { return; } try { ((Humanoid)_player).EquipItem(itemAt, true); } catch (Exception exception) { Diagnostics.Error(exception, "Equipment role could not invoke vanilla EquipItem; the carried item remains intact."); } } internal bool AllowSelectedAction(InventoryGrid grid, ItemData item, Modifier modifier) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Invalid comparison between Unknown and I4 //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Invalid comparison between Unknown and I4 if (_disposed || item == null || (Object)(object)grid == (Object)null || grid.GetInventory() != _inventory) { return true; } if ((int)modifier != 2 && (int)modifier != 3) { return true; } if (!IsLocked(item)) { return true; } Notify("Runic Inventory: that slot is locked."); return false; } internal bool AllowItemAction(Humanoid actor, Inventory inventory, ItemData item, string action) { if (_disposed || (Object)(object)actor != (Object)(object)_player || inventory != _inventory || item == null || !IsLocked(item)) { return true; } Notify("Runic Inventory: unlock that slot before " + action + "."); return false; } internal bool AllowStationItem(Humanoid actor, ItemData item, string action) { return AllowItemAction(actor, (actor != null) ? actor.GetInventory() : null, item, action); } internal bool AllowEquip(Humanoid actor, ItemData item) { if (_disposed || (Object)(object)actor != (Object)(object)_player || item == null || !CanEnforceLocks() || _layout == null) { return true; } if (_playerLoadInProgress && !_loadMetadataRefreshed && !TryRefreshLoadMetadata()) { return true; } if (!TopologyLayout.TryEquipmentRole(ValheimContracts.Category(item), out var role)) { return true; } InventorySlotCoordinate inventorySlotCoordinate = _layout.Coordinate(role); ItemData itemAt = _inventory.GetItemAt(inventorySlotCoordinate.X, inventorySlotCoordinate.Y); if (IsLocked(item) && itemAt != item) { Notify("Runic Inventory: unlock that item before equipping it."); return false; } if (!IsLocked(inventorySlotCoordinate.X, inventorySlotCoordinate.Y)) { return true; } if (itemAt == item) { return true; } Notify("Runic Inventory: unlock the " + RoleLabel(role) + " before replacing it."); return false; } internal bool BeginEquipmentTransition(Humanoid actor, ItemData item) { if (_disposed || (Object)(object)actor != (Object)(object)_player || item == null || !TopologyActive || !TopologyLayout.TryEquipmentRole(ValheimContracts.Category(item), out var _)) { return false; } if (_equipmentTransitionDepth == int.MaxValue) { FailClosed("equipment.transition-depth-exceeded"); return false; } _equipmentTransitionDepth++; return true; } internal bool AllowCrafting(InventoryGui gui) { ItemData val = ValheimContracts.CraftingCommitItem(gui); if (val == null || !IsLocked(val)) { return true; } Notify("Runic Inventory: unlock the selected item before upgrading or processing it."); return false; } internal bool AllowPickup(Humanoid actor, GameObject worldObject) { if (!_disposed) { ConfigEntry enabled = InventoryConfig.Enabled; if (enabled != null && enabled.Value && !((Object)(object)actor != (Object)(object)_player) && Object.op_Implicit((Object)(object)worldObject) && _filters.Count != 0) { ItemData val = worldObject.GetComponent()?.m_itemData; if (val?.m_shared == null || val.m_shared.m_questItem) { return true; } return !_filters.Matches(ValheimContracts.PrefabId(val), val.m_shared.m_name); } } return true; } internal EquipmentAdditionState BeginEquipmentAddition(Humanoid actor, ItemData expectedItem = null) { if (_disposed || (Object)(object)actor != (Object)(object)_player || !TopologyActive || _inventory == null || _layout == null) { return null; } InventoryRoleKind? expectedRole = null; if (expectedItem != null) { if (!TopologyLayout.TryEquipmentRole(ValheimContracts.Category(expectedItem), out var role)) { return null; } InventorySlotCoordinate inventorySlotCoordinate = _layout.Coordinate(role); if (_inventory.GetItemAt(inventorySlotCoordinate.X, inventorySlotCoordinate.Y) != null) { return null; } expectedRole = role; } List allItems = _inventory.GetAllItems(); if (allItems == null || allItems.Count > 128) { return null; } return new EquipmentAdditionState(_player, _inventory, expectedRole, new List(allItems)); } internal void CompleteEquipmentAddition(EquipmentAdditionState state, bool succeeded) { if (!succeeded || state == null || (Object)(object)state.Player != (Object)(object)_player || state.Inventory != _inventory || !TopologyActive) { return; } List allItems = _inventory.GetAllItems(); ItemData val = null; InventoryRoleKind role = (InventoryRoleKind)0; foreach (ItemData item in allItems) { bool flag = false; foreach (ItemData item2 in state.Before) { if (item2 == item) { flag = true; break; } } if (!flag && TopologyLayout.TryEquipmentRole(ValheimContracts.Category(item), out var role2) && (!state.ExpectedRole.HasValue || state.ExpectedRole.Value == role2)) { InventorySlotCoordinate inventorySlotCoordinate = _layout.Coordinate(role2); if (_inventory.GetItemAt(inventorySlotCoordinate.X, inventorySlotCoordinate.Y) != null || val != null) { return; } val = item; role = role2; } } if (val != null) { InventorySlotCoordinate inventorySlotCoordinate2 = _layout.Coordinate(role); if (_inventory.GetItemAt(inventorySlotCoordinate2.X, inventorySlotCoordinate2.Y) == null) { ((Humanoid)_player).EquipItem(val, true); } } } internal void AppendPickupPreview(ItemDrop drop, ref string hoverText) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) if (_disposed) { return; } ConfigEntry enabled = InventoryConfig.Enabled; if (enabled == null || !enabled.Value) { return; } ConfigEntry showPickupPreview = InventoryConfig.ShowPickupPreview; if (showPickupPreview == null || !showPickupPreview.Value || !Object.op_Implicit((Object)(object)_player) || !Object.op_Implicit((Object)(object)drop) || drop.m_itemData?.m_shared == null || hoverText == null || hoverText.Length > 8192) { return; } Vector3 val = ((Component)drop).transform.position - ((Component)_player).transform.position; if (!(((Vector3)(ref val)).sqrMagnitude > 25f)) { ItemData itemData = drop.m_itemData; int stack = Math.Max(0, itemData.m_stack); int maximumStack = Math.Max(1, itemData.m_shared.m_maxStackSize); _stackCapacity.TryGetValue(new StackCapacityKey(itemData.m_shared.m_name, itemData.m_quality, itemData.m_worldLevel), out var value); bool filtered = !itemData.m_shared.m_questItem && _filters.Matches(ValheimContracts.PrefabId(itemData), itemData.m_shared.m_name); float unitWeight; try { unitWeight = Math.Max(0f, itemData.GetWeight(1)); } catch (Exception) { return; } PickupDecision pickupDecision = PickupPlanner.Evaluate(stack, maximumStack, value, _freePickupSlots, unitWeight, Math.Max(0f, _cachedWeight), Math.Max(0f, _player.GetMaxCarryWeight()), filtered); string text = (pickupDecision.Filtered ? "\nRunic pickup: filtered; quest items always remain allowed." : ((pickupDecision.OverflowItems > 0) ? ("\nRunic pickup: " + pickupDecision.OverflowItems + " item(s) would overflow available safe slots.") : ((!pickupDecision.Encumbered) ? ("\nRunic pickup: fits; projected weight " + pickupDecision.ResultingWeight.ToString("0.#") + ".") : ("\nRunic pickup: fits, but would encumber you (" + pickupDecision.ResultingWeight.ToString("0.#") + ").")))); if (text.Length <= 192 && hoverText.Length + text.Length <= 8384) { hoverText += text; } } } internal void OnEquipped(Humanoid actor, ItemData item, bool succeeded) { if (succeeded && !((Object)(object)actor != (Object)(object)_player) && TopologyActive && item != null && item.m_equipped && _layout != null && TopologyLayout.TryEquipmentRole(ValheimContracts.Category(item), out var role)) { RelocateEquippedItem(item, role); } } internal void EndEquipmentTransition(bool started, Exception failure) { if (!started || _disposed) { return; } if (_equipmentTransitionDepth <= 0) { FailClosed("equipment.transition-scope-invalid"); return; } if (failure != null) { _equipmentTransitionFaulted = true; } _equipmentTransitionDepth--; if (_equipmentTransitionDepth == 0) { bool equipmentRefreshPending = _equipmentRefreshPending; bool equipmentTransitionFaulted = _equipmentTransitionFaulted; _equipmentRefreshPending = false; _equipmentTransitionFaulted = false; if (equipmentTransitionFaulted) { FailClosed("equipment.native-call-faulted"); } else if (equipmentRefreshPending) { RebuildCache("equipment-transition-completed"); } } } internal bool ShouldBlockStorageAction(string methodName) { if (!TopologyActive) { return false; } Notify("Runic Storage " + SafeWord(methodName) + " is paused while native special slots are active; a topology-aware peer is required."); return true; } internal void SortSelectedRows(string source) { //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Expected I4, but got Unknown //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_0317: Unknown result type (might be due to invalid IL or missing references) //IL_032f: Unknown result type (might be due to invalid IL or missing references) //IL_0352: Unknown result type (might be due to invalid IL or missing references) //IL_034b: Unknown result type (might be due to invalid IL or missing references) //IL_0354: Unknown result type (might be due to invalid IL or missing references) //IL_0361: Unknown result type (might be due to invalid IL or missing references) //IL_0366: Unknown result type (might be due to invalid IL or missing references) if (!RequireAuthoritativeTopology("sort")) { return; } if (!SelectedRowPolicy.TryParse(InventoryConfig.SortRows.Value, _layout, out var rows, out var reasonCode)) { Notify("Runic Inventory: sort rows were rejected (" + reasonCode + ")."); return; } if (!TryEnterMutation("runic.inventory/sort", out var lease)) { Notify("Runic Inventory: another inventory transaction is active; sort was skipped."); return; } using (lease) { if (!InventoryEvidence.TryCaptureMutation(_inventory, out var before, out var reasonCode2)) { Notify("Runic Inventory: sort proof failed (" + reasonCode2 + ")."); return; } List list = new List(before.Count); Dictionary dictionary = new Dictionary(); foreach (ItemMutationEvidence item2 in before) { ItemData item = item2.Item; string text = item.m_shared?.m_name; if (string.IsNullOrEmpty(text) || text.Length > 160) { Notify("Runic Inventory: an item has no bounded stable sort identity; nothing moved."); return; } float weight; try { weight = Math.Max(0f, item.GetWeight(item.m_stack)); } catch (Exception) { weight = 0f; } InventorySlotCoordinate coordinate = new InventorySlotCoordinate(item.m_gridPos.x, item.m_gridPos.y); list.Add(new SortItemDescriptor(coordinate, (int)item.m_shared.m_itemType, text, Math.Max(0, item.m_quality), weight, item.m_equipped)); dictionary.Add(item.m_gridPos.y * _layout.Width + item.m_gridPos.x, item); } if (!SafeSortPlanner.TryPlan(_layout, list, _persisted.LockedSlots(), rows, out var plan, out var reasonCode3)) { Notify("Runic Inventory: sort was rejected (" + reasonCode3 + ")."); return; } if (!plan.ChangesAnything) { Notify("Runic Inventory: the selected safe region is already sorted."); return; } Dictionary dictionary2 = new Dictionary(); foreach (SortMove move in plan.Moves) { int key = move.Source.Y * _layout.Width + move.Source.X; if (!dictionary.ContainsKey(key) || dictionary2.ContainsKey(key)) { Notify("Runic Inventory: sort sources changed before commit; nothing moved."); return; } dictionary2.Add(key, new Vector2i(move.Destination.X, move.Destination.Y)); } List> list2 = new List>(before.Count); foreach (ItemMutationEvidence item3 in before) { int key2 = item3.Coordinate.y * _layout.Width + item3.Coordinate.x; Vector2i value; Vector2i destination = (dictionary2.TryGetValue(key2, out value) ? value : item3.Coordinate); list2.Add(new PositionChange(item3.Item, item3.Coordinate, destination)); } string verifyReason = "evidence.verification-not-run"; if (!AtomicPositionTransaction.TryCommit(list2, delegate(ItemData target, Vector2i gridPos) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) target.m_gridPos = gridPos; }, () => InventoryEvidence.VerifyUnchangedExceptPosition(_inventory, before, out verifyReason), delegate { ValheimContracts.NotifyChanged(_inventory); }, out var failure, out var rollbackFailure)) { if (rollbackFailure != null) { Diagnostics.Error(rollbackFailure, "Sort positions were restored in memory but rollback publication faulted."); } Diagnostics.Error(failure ?? new InvalidOperationException(verifyReason), "Regional sort rolled back without changing item metadata."); Notify("Runic Inventory: sort failed and every restorable original position was restored."); } else { Notify("Runic Inventory: sorted " + plan.MovableCount + " stack(s) in the selected safe region."); } } } internal void ToggleFocusedLock(string source) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) if (RequireAuthoritativeTopology("lock")) { InventoryGrid val = InventoryGui.instance?.m_playerGrid; if (!Object.op_Implicit((Object)(object)val) || val.GetInventory() != _inventory || !ValheimContracts.TryFocusedSlot(val, out var coordinate) || coordinate.x < 0 || coordinate.x >= _layout.Width || coordinate.y < 0 || coordinate.y >= _layout.Height) { Notify("Runic Inventory: focus a player inventory slot before toggling its lock."); } else { ToggleLockAt(coordinate); } } } internal bool TryTogglePointerLock(InventoryGrid grid) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0083: 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) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) if (_disposed || !ZInput.GetKey((KeyCode)308, false) || (Object)(object)InventoryGui.instance == (Object)null || !InventoryGui.IsVisible() || !Object.op_Implicit((Object)(object)grid) || grid != InventoryGui.instance.m_playerGrid || grid.GetInventory() != _inventory) { return false; } if (!RequireAuthoritativeTopology("lock")) { return true; } if (!ValheimContracts.TryFocusedSlot(grid, out var coordinate) || coordinate.x < 0 || coordinate.x >= _layout.Width || coordinate.y < 0 || coordinate.y >= _layout.Height) { Notify("Runic Inventory: point at a player inventory slot before toggling its lock."); return true; } ToggleLockAt(coordinate); return true; } private void ToggleLockAt(Vector2i focused) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (!TryEnterMutation("runic.inventory/lock-metadata", out var lease)) { Notify("Runic Inventory: another inventory transaction is active; the lock was unchanged."); return; } using (lease) { InventorySlotCoordinate item = new InventorySlotCoordinate(focused.x, focused.y); List list = new List(_persisted.LockedSlots()); if (!list.Remove(item)) { list.Add(item); } string reasonCode = "not-attempted"; string reasonCode2 = "not-attempted"; PersistedTopologyState state = null; string payload; string reasonCode3; bool num = TopologyPersistenceCodec.TryEncode(_layout, list, out payload, out reasonCode3); bool flag = num && TryWritePersistedMetadata(payload, out reasonCode); bool flag2 = flag && TopologyPersistenceCodec.TryDecode(payload, out state, out reasonCode2); if (!num || !flag || !flag2) { Notify("Runic Inventory: lock change was rejected (" + FirstFailure(reasonCode3, reasonCode, reasonCode2) + ")."); } else { _persisted = state; RebuildCache("lock-changed"); } } } internal bool BeginRepairAllowance() { if (_disposed || _batch) { return false; } _repairAllowanceDepth++; return true; } internal void EndRepairAllowance(bool entered) { if (entered) { _repairAllowanceDepth = Math.Max(0, _repairAllowanceDepth - 1); } } internal void UseQuick(InventoryRoleKind role, string source) { if (!RequireAuthoritativeTopology("quick-slot")) { return; } InventorySlotCoordinate inventorySlotCoordinate = _layout.Coordinate(role); ItemData itemAt = _inventory.GetItemAt(inventorySlotCoordinate.X, inventorySlotCoordinate.Y); if (itemAt == null) { Notify("Runic Inventory: " + RoleLabel(role) + " is empty."); return; } if (_persisted.IsLocked(inventorySlotCoordinate.X, inventorySlotCoordinate.Y)) { Notify("Runic Inventory: " + RoleLabel(role) + " is locked."); return; } if (!TopologyLayout.Accepts(role, ValheimContracts.Category(itemAt))) { FailClosed("topology.quick-role-invalid"); Notify("Runic Inventory: the quick-slot topology changed; no item was used."); return; } if (!TryEnterMutation("runic.inventory/quick-use", out var lease)) { Notify("Runic Inventory: another inventory transaction is active; no item was used."); return; } using (lease) { try { ((Humanoid)_player).UseItem(_inventory, itemAt, false); } catch (Exception exception) { Diagnostics.Error(exception, "Vanilla quick-slot UseItem faulted; no synthetic consumption was attempted."); Notify("Runic Inventory: Valheim rejected that manual use."); } } } public void Dispose() { if (!_disposed) { _disposed = true; _playerLoadInProgress = false; _loadMetadataRefreshed = false; _disableCleanupPending = false; _repairAllowanceDepth = 0; DetachInventory(); _snapshot = null; _layout = null; _persisted = null; _stackCapacity.Clear(); _topologyActive = false; } } private void Rebind(Player player, string reason) { if (_disposed) { return; } _repairAllowanceDepth = 0; DetachInventory(); _player = player; if (_batch) { _mode = InventoryAuthorityMode.BatchInert; _reasonCode = "authority.batch-inert"; RebuildStatusOnly(); return; } if (!Object.op_Implicit((Object)(object)player)) { _mode = InventoryAuthorityMode.Unavailable; _reasonCode = "player.not-loaded"; RebuildStatusOnly(); return; } _inventory = ((Humanoid)player).GetInventory(); if (_inventory == null) { _mode = InventoryAuthorityMode.Unavailable; _reasonCode = "inventory.missing"; RebuildStatusOnly(); return; } Inventory inventory = _inventory; inventory.m_onChanged = (Action)Delegate.Combine(inventory.m_onChanged, new Action(OnInventoryChanged)); bool flag = IsAuthoritativeLocal(player); ConfigEntry enabled = InventoryConfig.Enabled; if (enabled == null || !enabled.Value) { _disableCleanupPending = _disableCleanupPending || (player.m_customData != null && player.m_customData.ContainsKey("runic.inventory.topology.v1")); if (!(_disableCleanupPending && flag) || TryCompleteDisableCleanup()) { _mode = InventoryAuthorityMode.Disabled; _reasonCode = "feature.disabled"; RebuildStatusOnly(); } return; } if (!TopologyLayout.TryCreate(_inventory.GetWidth(), _inventory.GetHeight(), out _layout, out var reasonCode)) { _mode = InventoryAuthorityMode.MigrationSafeCompatibility; _reasonCode = reasonCode; RebuildCache("layout-invalid"); return; } string value = null; bool flag2 = player.m_customData != null && player.m_customData.TryGetValue("runic.inventory.topology.v1", out value); if (flag2) { if (!TopologyPersistenceCodec.TryDecode(value, out _persisted, out var reasonCode2) || _persisted.Width != _layout.Width || _persisted.Height != _layout.Height) { _persisted = null; _mode = (flag ? InventoryAuthorityMode.MigrationSafeCompatibility : InventoryAuthorityMode.RemoteDedicatedCompatibility); _reasonCode = ((reasonCode2 == "ok") ? "persistence.topology-mismatch" : reasonCode2); RebuildCache("persistence-invalid"); return; } } else { if (!CreateEmptyPersistedState(_layout, out var state, out var reasonCode3)) { _mode = InventoryAuthorityMode.MigrationSafeCompatibility; _reasonCode = reasonCode3; RebuildCache("persistence-empty-failed"); return; } _persisted = state; } if (!ValidateRoleContents(out var reasonCode4)) { _mode = (flag ? InventoryAuthorityMode.MigrationSafeCompatibility : InventoryAuthorityMode.RemoteDedicatedCompatibility); _reasonCode = reasonCode4; RebuildCache("role-content-invalid"); return; } if (!flag) { _mode = InventoryAuthorityMode.RemoteDedicatedCompatibility; _reasonCode = "authority.local-player-owner-required"; RebuildCache("non-owner-compatibility", verifySerialization: true); return; } if (!flag2) { string reasonCode5 = "not-attempted"; string payload; string reasonCode6; bool flag3 = TopologyPersistenceCodec.TryEncode(_layout, Array.Empty(), out payload, out reasonCode6); bool flag4 = false; if (flag3 && TryEnterMutation("runic.inventory/topology-bootstrap", out var lease)) { using (lease) { flag4 = TryWritePersistedMetadata(payload, out reasonCode5); } } else if (flag3) { reasonCode5 = "persistence.transaction-busy"; } if (!flag3 || !flag4) { _mode = InventoryAuthorityMode.MigrationSafeCompatibility; _reasonCode = ((reasonCode6 == "ok") ? reasonCode5 : reasonCode6); RebuildCache("bootstrap-persistence-failed"); return; } } _mode = InventoryAuthorityMode.AuthoritativeLocal; _reasonCode = "ok"; _topologyActive = true; RebuildCache(reason, verifySerialization: true); } private void DetachInventory() { if (_inventory != null) { Inventory inventory = _inventory; inventory.m_onChanged = (Action)Delegate.Remove(inventory.m_onChanged, new Action(OnInventoryChanged)); } _inventory = null; _layout = null; _persisted = null; _snapshot = null; _topologyActive = false; _equipmentTransitionDepth = 0; _equipmentRefreshPending = false; _equipmentTransitionFaulted = false; _stackCapacity.Clear(); _freePickupSlots = 0; _cachedWeight = 0f; } private void OnInventoryChanged() { if (_equipmentTransitionDepth > 0) { _equipmentRefreshPending = true; return; } try { RebuildCache("inventory-changed"); } catch (Exception exception) { Diagnostics.Error(exception, "Inventory change cache rebuild failed closed."); FailClosed("cache.rebuild-failed"); } } private void RebuildCache(string cause, bool verifySerialization = false) { if (_rebuilding || _inventory == null) { return; } _rebuilding = true; try { _stackCapacity.Clear(); _freePickupSlots = 0; _cachedWeight = 0f; if (_mode == InventoryAuthorityMode.Disabled) { _snapshot = null; _topologyActive = false; RebuildStatusOnly(); return; } if (_layout != null && !LiveDimensionsMatch()) { FailClosed("topology.runtime-dimension-changed"); return; } _cachedWeight = Math.Max(0f, _inventory.GetTotalWeight()); List allItems = _inventory.GetAllItems(); if (allItems == null || allItems.Count > 128) { _snapshot = null; _reasonCode = "topology.item-bound-exceeded"; _topologyActive = false; RebuildStatusOnly(); return; } HashSet hashSet = new HashSet(); bool flag = _mode == InventoryAuthorityMode.MigrationSafeCompatibility && IsRoleContentCompatibilityReason(_reasonCode) && _layout != null && _persisted != null && IsAuthoritativeLocal(_player); bool flag2 = CanEnforceLocks() || flag; foreach (ItemData item in allItems) { if (item == null || item.m_shared == null || item.m_stack <= 0 || item.m_gridPos.x < 0 || item.m_gridPos.x >= _inventory.GetWidth() || item.m_gridPos.y < 0 || item.m_gridPos.y >= _inventory.GetHeight() || !hashSet.Add(item.m_gridPos.y * _inventory.GetWidth() + item.m_gridPos.x)) { _snapshot = null; _reasonCode = "topology.item-proof-failed"; _topologyActive = false; RebuildStatusOnly(); return; } int num = Math.Max(1, item.m_shared.m_maxStackSize); int num2 = Math.Max(0, num - item.m_stack); if (num2 > 0 && (!flag2 || !_persisted.IsLocked(item.m_gridPos.x, item.m_gridPos.y))) { StackCapacityKey key = new StackCapacityKey(item.m_shared.m_name, item.m_quality, item.m_worldLevel); _stackCapacity.TryGetValue(key, out var value); _stackCapacity[key] = ((value > int.MaxValue - num2) ? int.MaxValue : (value + num2)); } } bool flag3 = TopologyActive || flag; for (int i = 0; i < _inventory.GetHeight(); i++) { for (int j = 0; j < _inventory.GetWidth(); j++) { if (_inventory.GetItemAt(j, i) == null && (!flag3 || _layout == null || i != _layout.SpecialRow)) { _freePickupSlots++; } } } string reasonCode = "topology.unavailable"; if (_layout == null || _persisted == null || _persisted.Width != _layout.Width || _persisted.Height != _layout.Height || !ValidateRoleContents(out reasonCode)) { _snapshot = null; if (_layout != null && _persisted != null) { _reasonCode = reasonCode; } if (_mode == InventoryAuthorityMode.AuthoritativeLocal) { _mode = InventoryAuthorityMode.MigrationSafeCompatibility; _topologyActive = false; } RebuildStatusOnly(); return; } if (_mode == InventoryAuthorityMode.MigrationSafeCompatibility && IsRoleContentCompatibilityReason(_reasonCode) && IsAuthoritativeLocal(_player)) { _mode = InventoryAuthorityMode.AuthoritativeLocal; _reasonCode = "ok"; _topologyActive = true; } else if (_mode == InventoryAuthorityMode.AuthoritativeLocal && IsAuthoritativeLocal(_player)) { _reasonCode = "ok"; _topologyActive = true; } List list = new List(8); StringBuilder stringBuilder = new StringBuilder(1024).Append(_layout.Width).Append('|').Append(_layout.Height) .Append('|') .Append((int)_mode); foreach (InventoryRoleKind value2 in Enum.GetValues(typeof(InventoryRoleKind))) { InventorySlotCoordinate inventorySlotCoordinate = _layout.Coordinate(value2); ItemData itemAt = _inventory.GetItemAt(inventorySlotCoordinate.X, inventorySlotCoordinate.Y); bool flag4 = _persisted.IsLocked(inventorySlotCoordinate.X, inventorySlotCoordinate.Y); string prefabId = string.Empty; string fingerprint = string.Empty; int stack = 0; bool equipped = false; if (itemAt != null) { if (!InventoryEvidence.TryFingerprint(itemAt, includePosition: true, out fingerprint)) { _snapshot = null; _reasonCode = "topology.item-evidence-unbounded"; _topologyActive = false; RebuildStatusOnly(); return; } prefabId = ValheimContracts.PrefabId(itemAt); stack = itemAt.m_stack; equipped = itemAt.m_equipped; } list.Add(new InventoryRoleSnapshot(value2, inventorySlotCoordinate, itemAt != null, equipped, flag4, stack, prefabId, fingerprint)); stringBuilder.Append('|').Append((int)value2).Append(':') .Append(inventorySlotCoordinate) .Append(':') .Append(flag4 ? 1 : 0) .Append(':') .Append(fingerprint); } IReadOnlyList readOnlyList = _persisted.LockedSlots(); foreach (InventorySlotCoordinate item2 in readOnlyList) { stringBuilder.Append("|L:").Append(item2); } string reasonCode2 = "serialization.not-requested"; bool flag5 = verifySerialization && InventoryEvidence.TryDeterministicSave(_inventory, out reasonCode2); if (verifySerialization && !flag5) { Diagnostics.Trace("Topology serialization proof failed: " + reasonCode2); } if (_generation == long.MaxValue) { _snapshot = null; _topologyActive = false; _reasonCode = "topology.generation-exhausted"; RebuildStatusOnly(); return; } _generation++; _snapshot = new InventoryTopologySnapshot("runic.inventory", "1.0", _generation, _mode, _layout.Width, _layout.Height, allItems.Count, flag5, list, readOnlyList, InventoryEvidence.HashTopology(stringBuilder.ToString())); RebuildStatus(list, readOnlyList, flag5, verifySerialization); Diagnostics.Trace("Topology cache rebuilt cause=" + SafeWord(cause) + " generation=" + _generation + "."); } finally { _rebuilding = false; } } private void RebuildStatus(IReadOnlyList roles, IReadOnlyList lockedSlots, bool serializationVerified, bool serializationAttempted) { StringBuilder stringBuilder = new StringBuilder(768).Append("Runic Inventory — ").Append(_mode).Append("\n") .Append("Status: ") .Append(_reasonCode) .Append(" | native 8×") .Append(_layout?.Height ?? 0) .Append(" | save proof ") .Append(serializationVerified ? "verified" : (serializationAttempted ? "FAILED" : "pending API capture")) .Append("\nBottom row: "); for (int i = 0; i < roles.Count; i++) { InventoryRoleSnapshot inventoryRoleSnapshot = roles[i]; if (i > 0) { stringBuilder.Append(" "); } stringBuilder.Append(RoleShort(inventoryRoleSnapshot.Role)).Append('='); if (!inventoryRoleSnapshot.Occupied) { stringBuilder.Append("empty"); } else { stringBuilder.Append(SafePrefab(inventoryRoleSnapshot.PrefabId)).Append('×').Append(inventoryRoleSnapshot.Stack); } if (inventoryRoleSnapshot.Locked) { stringBuilder.Append("[LOCK]"); } } stringBuilder.Append("\nLocks: "); if (lockedSlots.Count == 0) { stringBuilder.Append("none"); } else { int num = Math.Min(16, lockedSlots.Count); for (int j = 0; j < num; j++) { if (j > 0) { stringBuilder.Append(' '); } stringBuilder.Append(lockedSlots[j]); } if (num < lockedSlots.Count) { stringBuilder.Append(" +").Append(lockedSlots.Count - num); } } stringBuilder.Append("\nKeyboard: Alt+1/2/3 use | Alt+I sort | focus slot then Alt+L lock").Append("\nController: validated ModifierAction chords only. Dedicated clients use their owning local Player."); _statusText = ((stringBuilder.Length <= 1024) ? stringBuilder.ToString() : stringBuilder.ToString(0, 1024)); } private void RebuildStatusOnly() { _statusText = "Runic Inventory — " + _mode.ToString() + "\nStatus: " + _reasonCode + "\nNo Runic item mutation is authorized. Native Valheim inventory behavior remains available." + CompatibilityRemedy(); } private string CompatibilityRemedy() { try { if (_layout == null || _inventory == null) { return string.Empty; } if (_reasonCode == "topology.clear-incompatible-special-row") { foreach (InventoryRoleKind value in Enum.GetValues(typeof(InventoryRoleKind))) { InventorySlotCoordinate inventorySlotCoordinate = _layout.Coordinate(value); ItemData itemAt = _inventory.GetItemAt(inventorySlotCoordinate.X, inventorySlotCoordinate.Y); if (itemAt != null && !TopologyLayout.Accepts(value, ValheimContracts.Category(itemAt))) { return "\nMove " + SafePrefab(ValheimContracts.PrefabId(itemAt)) + " out of bottom slot " + (inventorySlotCoordinate.X + 1) + " (" + RoleLabel(value) + "). The row reactivates automatically when valid."; } } } else if (_reasonCode == "topology.equipped-item-outside-role") { List allItems = _inventory.GetAllItems(); if (allItems == null || allItems.Count > 128) { return string.Empty; } foreach (ItemData item in allItems) { if (item != null && item.m_equipped && TopologyLayout.TryEquipmentRole(ValheimContracts.Category(item), out var role2)) { InventorySlotCoordinate inventorySlotCoordinate2 = _layout.Coordinate(role2); if (item.m_gridPos.x != inventorySlotCoordinate2.X || item.m_gridPos.y != inventorySlotCoordinate2.Y) { return "\nUnequip " + SafePrefab(ValheimContracts.PrefabId(item)) + "; its canonical role is bottom slot " + (inventorySlotCoordinate2.X + 1) + " (" + RoleLabel(role2) + "). The row reactivates automatically when valid."; } } } } return string.Empty; } catch (Exception) { return "\nClear incompatible bottom-row items or unequip out-of-role equipment; the row reactivates when valid."; } } private static bool IsRoleContentCompatibilityReason(string reasonCode) { if (!(reasonCode == "topology.clear-incompatible-special-row")) { return reasonCode == "topology.equipped-item-outside-role"; } return true; } private bool ValidateRoleContents(out string reasonCode) { if (_layout == null || _inventory == null) { reasonCode = "topology.unavailable"; return false; } foreach (InventoryRoleKind value in Enum.GetValues(typeof(InventoryRoleKind))) { InventorySlotCoordinate inventorySlotCoordinate = _layout.Coordinate(value); ItemData itemAt = _inventory.GetItemAt(inventorySlotCoordinate.X, inventorySlotCoordinate.Y); if (itemAt != null && !TopologyLayout.Accepts(value, ValheimContracts.Category(itemAt))) { reasonCode = "topology.clear-incompatible-special-row"; return false; } } if (_playerLoadInProgress) { reasonCode = "ok"; return true; } List allItems = _inventory.GetAllItems(); if (allItems == null || allItems.Count > 128) { reasonCode = "topology.item-bound-exceeded"; return false; } foreach (ItemData item in allItems) { if (item != null && item.m_equipped && TopologyLayout.TryEquipmentRole(ValheimContracts.Category(item), out var role2)) { InventorySlotCoordinate inventorySlotCoordinate2 = _layout.Coordinate(role2); if (item.m_gridPos.x != inventorySlotCoordinate2.X || item.m_gridPos.y != inventorySlotCoordinate2.Y || _inventory.GetItemAt(inventorySlotCoordinate2.X, inventorySlotCoordinate2.Y) != item) { reasonCode = "topology.equipped-item-outside-role"; return false; } } } reasonCode = "ok"; return true; } private void RelocateEquippedItem(ItemData item, InventoryRoleKind role) { //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_0159: 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_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) InventorySlotCoordinate inventorySlotCoordinate = _layout.Coordinate(role); if (item.m_gridPos.x == inventorySlotCoordinate.X && item.m_gridPos.y == inventorySlotCoordinate.Y) { return; } if (IsLocked(item) || IsLocked(inventorySlotCoordinate.X, inventorySlotCoordinate.Y)) { FailClosed("equipment.relocation-locked"); Notify("Runic Inventory: equipment relocation was blocked by a slot lock; special roles are paused."); return; } if (!TryEnterMutation("runic.inventory/equipment-relocate", out var lease)) { FailClosed("equipment.relocation-transaction-busy"); Notify("Runic Inventory: equipment relocation conflicted with another transaction; special roles are paused."); return; } using (lease) { if (!InventoryEvidence.TryCaptureMutation(_inventory, out var before, out var reasonCode)) { Diagnostics.Warn("Equipment relocation skipped: " + reasonCode + "."); FailClosed("equipment.relocation-proof-failed"); return; } Vector2i gridPos = item.m_gridPos; ItemData itemAt = _inventory.GetItemAt(inventorySlotCoordinate.X, inventorySlotCoordinate.Y); List> list = new List>(before.Count); foreach (ItemMutationEvidence item2 in before) { Vector2i destination = (Vector2i)((item2.Item == item) ? new Vector2i(inventorySlotCoordinate.X, inventorySlotCoordinate.Y) : ((item2.Item == itemAt) ? gridPos : item2.Coordinate)); list.Add(new PositionChange(item2.Item, item2.Coordinate, destination)); } string verifyReason = "evidence.verification-not-run"; if (!AtomicPositionTransaction.TryCommit(list, delegate(ItemData targetItem, Vector2i coordinate) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) targetItem.m_gridPos = coordinate; }, () => InventoryEvidence.VerifyUnchangedExceptPosition(_inventory, before, out verifyReason), delegate { ValheimContracts.NotifyChanged(_inventory); }, out var failure, out var rollbackFailure)) { if (rollbackFailure != null) { Diagnostics.Error(rollbackFailure, "Equipment positions were restored in memory but rollback publication faulted."); } Diagnostics.Error(failure ?? new InvalidOperationException(verifyReason), "Equipment position relocation rolled back."); FailClosed("equipment.relocation-failed"); } } } private bool RequireAuthoritativeTopology(string action) { if (TopologyActive && _mode == InventoryAuthorityMode.AuthoritativeLocal && IsAuthoritativeLocal(_player)) { return true; } Notify("Runic Inventory: " + SafeWord(action) + " requires the owning local player; mode is " + _mode.ToString() + "."); return false; } private bool IsLocked(ItemData item) { if (_repairAllowanceDepth == 0 && CanEnforceLocks() && item != null && _persisted != null && _layout != null && item.m_gridPos.x >= 0 && item.m_gridPos.x < _layout.Width && item.m_gridPos.y >= 0 && item.m_gridPos.y < _layout.Height && _persisted.Width == _layout.Width && _persisted.Height == _layout.Height) { return _persisted.IsLocked(item.m_gridPos.x, item.m_gridPos.y); } return false; } private bool IsLocked(int x, int y) { if (_repairAllowanceDepth == 0 && CanEnforceLocks() && _persisted != null && _layout != null && x >= 0 && x < _layout.Width && y >= 0 && y < _layout.Height && _persisted.Width == _layout.Width && _persisted.Height == _layout.Height) { return _persisted.IsLocked(x, y); } return false; } private bool CanEnforceLocks() { if (TopologyActive && _mode == InventoryAuthorityMode.AuthoritativeLocal) { return IsAuthoritativeLocal(_player); } return false; } private bool LiveDimensionsMatch() { if (_inventory != null && _layout != null) { return _layout.MatchesNativeDimensions(_inventory.GetWidth(), _inventory.GetHeight()); } return false; } private bool TryWritePersistedMetadata(string payload, out string reasonCode) { reasonCode = "persistence.write-failed"; if (!IsAuthoritativeLocal(_player) || payload == null || payload.Length > 256 || _player.m_customData == null) { return false; } string value; bool flag = _player.m_customData.TryGetValue("runic.inventory.topology.v1", out value); try { _player.m_customData["runic.inventory.topology.v1"] = payload; if (!_player.m_customData.TryGetValue("runic.inventory.topology.v1", out var value2) || !string.Equals(value2, payload, StringComparison.Ordinal) || !TopologyPersistenceCodec.TryDecode(value2, out var _, out var _)) { throw new InvalidOperationException("Persisted metadata did not verify after assignment."); } reasonCode = "ok"; return true; } catch (Exception) { try { if (flag) { _player.m_customData["runic.inventory.topology.v1"] = value; } else { _player.m_customData.Remove("runic.inventory.topology.v1"); } } catch (Exception) { } return false; } } private bool TryRefreshLoadMetadata() { _loadMetadataRefreshed = true; if (!_playerLoadInProgress || _player?.m_customData == null || _layout == null) { FailClosed("player.load-metadata-unavailable"); return false; } if (!_player.m_customData.TryGetValue("runic.inventory.topology.v1", out var value)) { if (CreateEmptyPersistedState(_layout, out var state, out var reasonCode)) { _persisted = state; return true; } FailClosed(reasonCode); return false; } if (!TopologyPersistenceCodec.TryDecode(value, out var state2, out var reasonCode2) || state2.Width != _layout.Width || state2.Height != _layout.Height) { FailClosed((reasonCode2 == "ok") ? "persistence.topology-mismatch" : reasonCode2); return false; } _persisted = state2; return true; } private bool TryCompleteDisableCleanup() { if (!_disableCleanupPending) { return true; } ConfigEntry enabled = InventoryConfig.Enabled; if (enabled != null && enabled.Value) { _disableCleanupPending = false; return true; } if (!IsAuthoritativeLocal(_player)) { FailClosed("config.disable-owner-pending"); return false; } if (!TryEnterMutation("runic.inventory/config-disable", out var lease)) { FailClosed("config.disable-transaction-busy"); return false; } using (lease) { if (!TryRemovePersistedMetadata(out var reasonCode)) { FailClosed(reasonCode); return false; } } _disableCleanupPending = false; Diagnostics.Trace("Disabled Inventory topology metadata cleanup committed."); return true; } private bool TryRemovePersistedMetadata(out string reasonCode) { reasonCode = "persistence.remove-failed"; if (!IsAuthoritativeLocal(_player) || _player.m_customData == null) { return false; } if (!_player.m_customData.TryGetValue("runic.inventory.topology.v1", out var value)) { reasonCode = "ok"; return true; } try { _player.m_customData.Remove("runic.inventory.topology.v1"); if (_player.m_customData.ContainsKey("runic.inventory.topology.v1")) { throw new InvalidOperationException("Metadata removal did not commit."); } reasonCode = "ok"; return true; } catch (Exception) { try { _player.m_customData["runic.inventory.topology.v1"] = value; } catch (Exception) { } return false; } } private static bool CreateEmptyPersistedState(TopologyLayout layout, out PersistedTopologyState state, out string reasonCode) { state = null; if (!TopologyPersistenceCodec.TryEncode(layout, Array.Empty(), out var payload, out reasonCode)) { return false; } return TopologyPersistenceCodec.TryDecode(payload, out state, out reasonCode); } private bool TryEnterMutation(string boundary, out IDisposable lease) { lease = null; if (_disposed || Interlocked.CompareExchange(ref _mutationActive, 1, 0) != 0) { return false; } lease = new MutationScope(this); return true; } private static bool IsAuthoritativeLocal(Player player) { if (Object.op_Implicit((Object)(object)player) && (Object)(object)player == (Object)(object)Player.m_localPlayer) { return ((Character)player).IsOwner(); } return false; } private void TraceProtectionDecision(string decisionCode, Exception exception = null) { ConfigEntry verboseDiagnostics = InventoryConfig.VerboseDiagnostics; if (verboseDiagnostics == null || !verboseDiagnostics.Value) { return; } string text = BoundReason(decisionCode, "protection.unknown"); string text2 = BoundReason(_reasonCode, "runtime.unknown"); string text3 = ((exception == null) ? string.Empty : SafeWord(exception.GetType().Name)); string[] obj = new string[7] { text, "|", null, null, null, null, null }; int mode = (int)_mode; obj[2] = mode.ToString(); obj[3] = "|"; obj[4] = text2; obj[5] = "|"; obj[6] = text3; string item = string.Concat(obj); lock (_protectionDiagnosticGate) { if (_protectionDiagnostics.Count >= 32 || !_protectionDiagnostics.Add(item)) { return; } } Diagnostics.Trace("Item protection decision=" + text + " mode=" + _mode.ToString() + " topology=" + text2 + ((text3.Length == 0) ? "." : (" exception=" + text3 + "."))); } private void Notify(string text) { if (Object.op_Implicit((Object)(object)_player) && !(Time.unscaledTime < _nextMessageTime)) { _nextMessageTime = Time.unscaledTime + 0.35f; ((Character)_player).Message((MessageType)2, text, 0, (Sprite)null); } } private static string RoleLabel(InventoryRoleKind role) { return role switch { InventoryRoleKind.Quick3 => "quick slot 3", InventoryRoleKind.Quick2 => "quick slot 2", InventoryRoleKind.Quick1 => "quick slot 1", _ => role.ToString().ToLowerInvariant() + " equipment", }; } private static string RoleShort(InventoryRoleKind role) { return role switch { InventoryRoleKind.Quick3 => "Q3", InventoryRoleKind.Quick2 => "Q2", InventoryRoleKind.Quick1 => "Q1", _ => role.ToString(), }; } private static bool IsEquipmentRole(InventoryRoleKind role) { if (role >= InventoryRoleKind.Head) { return role <= InventoryRoleKind.Utility; } return false; } private static bool SameItemReferences(IReadOnlyList expected, IReadOnlyList actual) { if (expected == null || actual == null || expected.Count != actual.Count) { return false; } for (int i = 0; i < expected.Count; i++) { if (expected[i] != actual[i]) { return false; } } return true; } private static string SafePrefab(string value) { string text = value ?? string.Empty; if (text.Length > 28) { text = text.Substring(0, 28); } return text.Replace("<", string.Empty).Replace(">", string.Empty).Replace("\n", string.Empty) .Replace("\r", string.Empty); } private static string SafeWord(string value) { string text = value ?? "action"; StringBuilder stringBuilder = new StringBuilder(Math.Min(48, text.Length)); for (int i = 0; i < text.Length; i++) { if (stringBuilder.Length >= 48) { break; } char c = text[i]; if (char.IsLetterOrDigit(c) || c == '-' || c == '_') { stringBuilder.Append(c); } } if (stringBuilder.Length != 0) { return stringBuilder.ToString(); } return "action"; } private static string BoundReason(string value, string fallback) { string text = value ?? string.Empty; if (text.Length == 0 || text.Length > 96) { return fallback; } for (int i = 0; i < text.Length; i++) { if (!char.IsLetterOrDigit(text[i]) && text[i] != '.' && text[i] != '-') { return fallback; } } return text; } private static string FirstFailure(params string[] values) { foreach (string text in values) { if (!string.IsNullOrEmpty(text) && !string.Equals(text, "ok", StringComparison.Ordinal)) { return text; } } return "unknown"; } } internal static class ValheimContracts { internal delegate void InventoryChangedDelegate(Inventory instance); internal delegate Vector2i GridButtonPositionDelegate(InventoryGrid instance, GameObject button); internal delegate bool PlayerTakeInputDelegate(Player instance); internal const string AuditedGameVersion = "0.221.12"; private const BindingFlags InstanceAll = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static InventoryChangedDelegate _changed; private static GridButtonPositionDelegate _buttonPosition; private static PlayerTakeInputDelegate _takeInput; private static FieldInfo _gridSelected; private static MethodInfo _hoveredElement; private static MethodInfo _getElement; private static FieldInfo _elementGameObject; private static FieldInfo _craftUpgradeItem; private static bool _ready; internal static bool Initialize(out string problem) { try { string text = (string)Exact(typeof(Player).Assembly.GetType("Version", throwOnError: true), "GetVersionString", BindingFlags.Static | BindingFlags.Public, typeof(bool)).Invoke(null, new object[1] { false }); if (!string.Equals(text, "0.221.12", StringComparison.Ordinal)) { throw new MissingMethodException("Runic Inventory 1.0.0 is audited for Valheim 0.221.12; installed " + text + "."); } _changed = AccessTools.MethodDelegate(Exact(typeof(Inventory), "Changed", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic), (object)null, true); _buttonPosition = AccessTools.MethodDelegate(Exact(typeof(InventoryGrid), "GetButtonPos", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(GameObject)), (object)null, true); _takeInput = AccessTools.MethodDelegate(Exact(typeof(Player), "TakeInput", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic), (object)null, true); _gridSelected = ExactField(typeof(InventoryGrid), "m_selected", typeof(Vector2i)); _hoveredElement = Exact(typeof(InventoryGrid), "GetHoveredElement", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _getElement = Exact(typeof(InventoryGrid), "GetElement", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(int), typeof(int), typeof(int)); Type nestedType = typeof(InventoryGrid).GetNestedType("Element", BindingFlags.NonPublic); if (nestedType == null || _hoveredElement.ReturnType != nestedType || _getElement.ReturnType != nestedType) { throw new MissingMemberException("InventoryGrid.Element/GetHoveredElement"); } _elementGameObject = ExactField(nestedType, "m_go", typeof(GameObject)); _craftUpgradeItem = ExactField(typeof(InventoryGui), "m_craftUpgradeItem", typeof(ItemData)); Exact(typeof(Inventory), "GetWidth", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Exact(typeof(Inventory), "GetHeight", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Exact(typeof(Inventory), "GetAllItems", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Exact(typeof(Inventory), "GetItemAt", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(int), typeof(int)); Exact(typeof(Inventory), "Save", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(ZPackage)); Exact(typeof(Inventory), "FindEmptySlot", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(bool)); Exact(typeof(Inventory), "FindFreeStackItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(string), typeof(int), typeof(float)); Exact(typeof(Inventory), "CanAddItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(ItemData), typeof(int)); Exact(typeof(InventoryGrid), "DropItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Inventory), typeof(ItemData), typeof(int), typeof(Vector2i)); Exact(typeof(InventoryGrid), "OnLeftClick", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(UIInputHandler)); Exact(typeof(InventoryGrid), "OnRightClick", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(UIInputHandler)); Exact(typeof(InventoryGui), "OnSelectedItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(InventoryGrid), typeof(ItemData), typeof(Vector2i), typeof(Modifier)); Exact(typeof(Humanoid), "DropItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Inventory), typeof(ItemData), typeof(int)); Exact(typeof(Humanoid), "UseItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Inventory), typeof(ItemData), typeof(bool)); Exact(typeof(Humanoid), "EquipItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(ItemData), typeof(bool)); Exact(typeof(Humanoid), "HideHandItems", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(bool), typeof(bool)); Exact(typeof(Humanoid), "ShowHandItems", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(bool), typeof(bool)); Exact(typeof(Humanoid), "SetUseHandVisual", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(GameObject), typeof(float)); Exact(typeof(Humanoid), "DoInteractAnimation", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(GameObject)); Exact(typeof(Humanoid), "Pickup", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(GameObject), typeof(bool), typeof(bool)); Exact(typeof(Player), "UpdateActionQueue", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(float)); Exact(typeof(Player), "UpdateWeaponLoading", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(ItemData), typeof(float)); Exact(typeof(Player), "SetWeaponLoaded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(ItemData)); Exact(typeof(Player), "ResetLoadedWeapon", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Exact(typeof(Player), "ToggleEquipped", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(ItemData)); Exact(typeof(Player), "TryPlacePiece", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Piece)); Exact(typeof(Player), "SetCraftingStation", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(CraftingStation)); Exact(typeof(Player), "AttachStart", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Transform), typeof(GameObject), typeof(bool), typeof(bool), typeof(bool), typeof(string), typeof(Vector3), typeof(Transform)); Exact(typeof(Player), "SetLocalPlayer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Exact(typeof(Player), "CreateTombStone", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Exact(typeof(Player), "Save", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(ZPackage)); Exact(typeof(Player), "Load", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(ZPackage)); Exact(typeof(ItemDrop), "GetHoverText", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Exact(typeof(Smelter), "OnAddOre", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Switch), typeof(Humanoid), typeof(ItemData)); Exact(typeof(Smelter), "OnAddFuel", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Switch), typeof(Humanoid), typeof(ItemData)); Exact(typeof(CookingStation), "CookItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Humanoid), typeof(ItemData)); Exact(typeof(CookingStation), "OnAddFuelSwitch", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Switch), typeof(Humanoid), typeof(ItemData)); Exact(typeof(Fermenter), "AddItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Humanoid), typeof(ItemData)); Exact(typeof(Incinerator), "OnIncinerate", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Switch), typeof(Humanoid), typeof(ItemData)); Exact(typeof(ItemStand), "UseItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Humanoid), typeof(ItemData)); Exact(typeof(Attack), "ConsumeItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Exact(typeof(Attack), "UseAmmo", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(ItemData).MakeByRefType()); Exact(typeof(ArmorStand), "UpdateAttach", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Exact(typeof(ItemStand), "UpdateAttach", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Exact(typeof(Catapult), "OnLoadPointUse", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Switch), typeof(Humanoid), typeof(ItemData)); Exact(typeof(OfferingBowl), "UseItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Humanoid), typeof(ItemData)); Exact(typeof(ShieldGenerator), "OnAddFuel", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Switch), typeof(Humanoid), typeof(ItemData)); Exact(typeof(Container), "RPC_StackResponse", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(long), typeof(bool)); Exact(typeof(Container), "RPC_TakeAllRespons", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(long), typeof(bool)); Exact(typeof(Fireplace), "Interact", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Humanoid), typeof(bool), typeof(bool)); Exact(typeof(Fireplace), "UseItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Humanoid), typeof(ItemData)); Exact(typeof(Turret), "UseItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Humanoid), typeof(ItemData)); Exact(typeof(Trader), "UseItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Humanoid), typeof(ItemData)); Exact(typeof(Pet), "UseItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Humanoid), typeof(ItemData)); Exact(typeof(Tameable), "UseItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Humanoid), typeof(ItemData)); Exact(typeof(StoreGui), "SellItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Exact(typeof(FishingFloat), "FixedUpdate", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (Exact(typeof(FishingFloat), "GetOwner", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ReturnType != typeof(Character)) { throw new MissingMethodException("FishingFloat.GetOwner() -> Character"); } Exact(typeof(OfferingBowl), "RPC_RemoveBossSpawnInventoryItems", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(long)); Exact(typeof(OfferingBowl), "InitiateSpawnBoss", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Vector3), typeof(bool)); ExactField(typeof(OfferingBowl), "m_interactUser", typeof(Humanoid)); ExactField(typeof(OfferingBowl), "m_usedSpawnItem", typeof(ItemData)); Exact(typeof(PlayerCustomizaton), "ShowBarberGui", BindingFlags.Static | BindingFlags.Public); if (typeof(Player).GetField("m_customData", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.FieldType != typeof(Dictionary)) { throw new MissingFieldException("Player.m_customData dictionary is missing."); } string[] array = new string[13] { "m_gridPos", "m_stack", "m_durability", "m_equipped", "m_quality", "m_variant", "m_crafterID", "m_crafterName", "m_customData", "m_worldLevel", "m_pickedUp", "m_shared", "m_dropPrefab" }; foreach (string text2 in array) { if (typeof(ItemData).GetField(text2, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) == null) { throw new MissingFieldException("ItemDrop.ItemData." + text2 + " is missing."); } } _ready = true; problem = string.Empty; return true; } catch (Exception ex) { _ready = false; problem = ex.GetType().Name + ": " + ex.Message; return false; } } internal static void NotifyChanged(Inventory inventory) { if (!_ready || inventory == null) { throw new InvalidOperationException("Installed Inventory.Changed contract is unavailable."); } _changed(inventory); } internal static bool PlayerMayTakeInput(Player player) { if (_ready && Object.op_Implicit((Object)(object)player)) { return _takeInput(player); } return false; } internal static bool TryFocusedSlot(InventoryGrid grid, out Vector2i coordinate) { //IL_0003: 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) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) coordinate = new Vector2i(-1, -1); if (!_ready || !Object.op_Implicit((Object)(object)grid)) { return false; } try { if (ZInput.IsGamepadActive()) { coordinate = (Vector2i)_gridSelected.GetValue(grid); return coordinate.x >= 0 && coordinate.y >= 0; } object obj = _hoveredElement.Invoke(grid, null); GameObject val = (GameObject)((obj == null) ? null : /*isinst with value type is only supported in some contexts*/); if (Object.op_Implicit((Object)(object)val)) { coordinate = _buttonPosition(grid, val); if (coordinate.x >= 0 && coordinate.y >= 0) { return true; } } EventSystem current = EventSystem.current; if (Object.op_Implicit((Object)(object)((current != null) ? current.currentSelectedGameObject : null))) { GameObject currentSelectedGameObject = EventSystem.current.currentSelectedGameObject; if (currentSelectedGameObject.transform.IsChildOf(((Component)grid).transform)) { coordinate = _buttonPosition(grid, currentSelectedGameObject); return coordinate.x >= 0 && coordinate.y >= 0; } } } catch (Exception) { } return false; } internal static bool TryBottomRowScreenRects(InventoryGrid grid, int row, Rect[] slots) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (!_ready || !Object.op_Implicit((Object)(object)grid) || row < 0 || slots == null || slots.Length != 8) { return false; } for (int i = 0; i < slots.Length; i++) { slots[i] = default(Rect); } try { for (int j = 0; j < slots.Length; j++) { if (!TrySlotScreenRect(grid, j, row, out slots[j])) { return false; } } return true; } catch (Exception) { } return false; } internal static ItemData CraftingCommitItem(InventoryGui gui) { try { return (ItemData)(((Object)(object)gui == (Object)null) ? null : /*isinst with value type is only supported in some contexts*/); } catch (Exception) { return null; } } internal static InventoryItemCategory Category(ItemData item) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected I4, but got Unknown //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Invalid comparison between Unknown and I4 //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected I4, but got Unknown if (item?.m_shared == null) { return InventoryItemCategory.Unknown; } ItemType itemType = item.m_shared.m_itemType; switch (itemType - 1) { default: if ((int)itemType != 11) { switch (itemType - 17) { case 0: return InventoryItemCategory.Cape; case 1: return InventoryItemCategory.Utility; case 2: return InventoryItemCategory.Tool; } break; } return InventoryItemCategory.Legs; case 0: return InventoryItemCategory.Material; case 1: return InventoryItemCategory.Consumable; case 5: return InventoryItemCategory.Helmet; case 6: return InventoryItemCategory.Chest; case 2: case 3: case 4: break; } return InventoryItemCategory.Other; } internal static bool TrySlotScreenRect(InventoryGrid grid, int column, int row, out Rect slot) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0136: 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_014b: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) slot = default(Rect); if (!_ready || !Object.op_Implicit((Object)(object)grid) || column < 0 || row < 0) { return false; } try { Inventory inventory = grid.GetInventory(); if (inventory == null || column >= inventory.GetWidth() || row >= inventory.GetHeight()) { return false; } object obj = _getElement.Invoke(grid, new object[3] { column, row, inventory.GetWidth() }); GameObject val = (GameObject)((obj == null) ? null : /*isinst with value type is only supported in some contexts*/); RectTransform val2 = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); if (!Object.op_Implicit((Object)(object)val2)) { return false; } Canvas componentInParent = ((Component)grid).GetComponentInParent(); Camera obj2 = (((Object)(object)componentInParent != (Object)null && (int)componentInParent.renderMode != 0) ? componentInParent.worldCamera : null); Vector3[] array = (Vector3[])(object)new Vector3[4]; val2.GetWorldCorners(array); Vector2 val3 = RectTransformUtility.WorldToScreenPoint(obj2, array[0]); Vector2 val4 = RectTransformUtility.WorldToScreenPoint(obj2, array[2]); float num = Math.Min(val3.x, val4.x); float num2 = Math.Max(val3.x, val4.x); float num3 = Math.Min(val3.y, val4.y); float num4 = Math.Max(val3.y, val4.y); if (num2 - num < 8f || num4 - num3 < 8f) { return false; } slot = new Rect(num, (float)Screen.height - num4, num2 - num, num4 - num3); return true; } catch (Exception) { return false; } } internal static bool InventoryModalVisible() { InventoryGui instance = InventoryGui.instance; if ((Object)(object)instance == (Object)null) { return false; } if (!Active((Component)(object)instance.m_splitPanel) && !Active((Component)(object)instance.m_variantDialog) && !Active((Component)(object)instance.m_skillsDialog)) { return Active((Component)(object)instance.m_textsDialog); } return true; } private static bool Active(Component component) { if ((Object)(object)component != (Object)null) { return component.gameObject.activeInHierarchy; } return false; } internal static string PrefabId(ItemData item) { string text = (Object.op_Implicit((Object)(object)item?.m_dropPrefab) ? ((Object)item.m_dropPrefab).name : item?.m_shared?.m_name); text = text ?? string.Empty; if (text.EndsWith("(Clone)", StringComparison.Ordinal)) { text = text.Substring(0, text.Length - "(Clone)".Length); } text = text.Trim(); if (text.Length > 128) { return string.Empty; } return text; } private static MethodInfo Exact(Type type, string name, BindingFlags flags, params Type[] parameters) { return type.GetMethod(name, flags, null, parameters, null) ?? throw new MissingMethodException(type.FullName, name); } private static FieldInfo ExactField(Type type, string name, Type expected) { FieldInfo fieldInfo = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingFieldException(type.FullName, name); if (expected != null && fieldInfo.FieldType != expected) { throw new MissingFieldException(type.FullName, name + ":" + expected.FullName); } return fieldInfo; } } } namespace RunicInventory.Core { internal sealed class PositionChange where TItem : class { internal TItem Item { get; } internal TPosition Original { get; } internal TPosition Destination { get; } internal PositionChange(TItem item, TPosition original, TPosition destination) { Item = item ?? throw new ArgumentNullException("item"); Original = original; Destination = destination; } } internal static class AtomicPositionTransaction { private sealed class ReferenceComparer : IEqualityComparer where T : class { internal static readonly ReferenceComparer Instance = new ReferenceComparer(); public bool Equals(T left, T right) { return left == right; } public int GetHashCode(T value) { return RuntimeHelpers.GetHashCode(value); } } internal const int MaximumChanges = 128; internal static bool TryCommit(IReadOnlyList> changes, Action assign, Func verify, Action publish, out Exception failure, out Exception rollbackFailure) where TItem : class { failure = null; rollbackFailure = null; if (!Validate(changes, assign, verify, publish, out failure)) { return false; } try { for (int i = 0; i < changes.Count; i++) { assign(changes[i].Item, changes[i].Destination); } if (!verify()) { throw new InvalidOperationException("Position transaction verification failed."); } publish(); return true; } catch (Exception ex) { failure = ex; List list = new List(); for (int j = 0; j < changes.Count; j++) { PositionChange positionChange = changes[j]; try { assign(positionChange.Item, positionChange.Original); } catch (Exception item) { list.Add(item); } } try { publish(); } catch (Exception item2) { list.Add(item2); } if (list.Count == 1) { rollbackFailure = list[0]; } else if (list.Count > 1) { rollbackFailure = new AggregateException(list); } return false; } } private static bool Validate(IReadOnlyList> changes, Action assign, Func verify, Action publish, out Exception failure) where TItem : class { failure = null; if (changes == null || assign == null || verify == null || publish == null) { failure = new ArgumentNullException("A position transaction input was null."); return false; } if (changes.Count == 0 || changes.Count > 128) { failure = new ArgumentOutOfRangeException("changes"); return false; } HashSet hashSet = new HashSet(ReferenceComparer.Instance); for (int i = 0; i < changes.Count; i++) { PositionChange positionChange = changes[i]; if (positionChange == null || positionChange.Item == null || !hashSet.Add(positionChange.Item)) { failure = new ArgumentException("Position changes must contain unique non-null item references.", "changes"); return false; } } return true; } } internal sealed class CharacterProfileReadbackEvidence { internal long PlayerId { get; } internal int OuterPackageBytes { get; } internal int PlayerDataBytes { get; } internal string OuterSha512 { get; } internal string PlayerDataSha256 { get; } internal CharacterProfileReadbackEvidence(long playerId, int outerPackageBytes, int playerDataBytes, string outerSha512, string playerDataSha256) { PlayerId = playerId; OuterPackageBytes = outerPackageBytes; PlayerDataBytes = playerDataBytes; OuterSha512 = outerSha512 ?? string.Empty; PlayerDataSha256 = playerDataSha256 ?? string.Empty; } } internal static class CharacterProfileReadback { private sealed class ByteCursor { private readonly byte[] _data; private readonly int _end; internal int Position { get; private set; } internal int Remaining => _end - Position; internal ByteCursor(byte[] data, int offset, int count) { _data = data; Position = offset; _end = offset + count; } internal bool TrySkip(int count) { if (count < 0 || count > Remaining) { return false; } Position += count; return true; } internal bool TryReadInt32(out int value) { value = 0; if (Remaining < 4) { return false; } value = _data[Position] | (_data[Position + 1] << 8) | (_data[Position + 2] << 16) | (_data[Position + 3] << 24); Position += 4; return true; } internal bool TryReadInt64(out long value) { value = 0L; if (Remaining < 8) { return false; } ulong num = _data[Position] | ((ulong)_data[Position + 1] << 8) | ((ulong)_data[Position + 2] << 16) | ((ulong)_data[Position + 3] << 24) | ((ulong)_data[Position + 4] << 32) | ((ulong)_data[Position + 5] << 40) | ((ulong)_data[Position + 6] << 48) | ((ulong)_data[Position + 7] << 56); Position += 8; value = (long)num; return true; } internal bool TryReadStrictBoolean(out bool value) { value = false; if (Remaining < 1) { return false; } byte b = _data[Position++]; if (b > 1) { return false; } value = b != 0; return true; } internal bool TrySkipByteArray(int maximumBytes) { if (TryReadInt32(out var value) && value >= 0 && value <= maximumBytes) { return TrySkip(value); } return false; } internal bool TrySkipString(int maximumBytes) { if (!TryRead7BitEncodedInt(out var value) || value < 0 || value > maximumBytes) { return false; } return TrySkip(value); } private bool TryRead7BitEncodedInt(out int value) { value = 0; for (int i = 0; i < 5; i++) { if (Remaining < 1) { return false; } byte b = _data[Position++]; if (i == 4 && (b & 0xF0) != 0) { return false; } value |= (b & 0x7F) << i * 7; if ((b & 0x80) == 0) { return value >= 0; } } return false; } } internal const int CurrentProfileVersion = 43; internal const int CurrentStatCount = 105; internal const int OuterHashBytes = 64; internal const int MaximumFileBytes = 268435456; internal const int MaximumPlayerDataBytes = 67108864; internal const int MaximumWorldEntries = 4096; internal const int MaximumDictionaryEntries = 65536; internal const int MaximumTotalDictionaryEntries = 262144; internal const int MaximumStringBytes = 65536; internal static bool TryVerifyCurrentV43(byte[] fileBytes, byte[] expectedPlayerData, out CharacterProfileReadbackEvidence evidence, out string reasonCode) { evidence = null; if (fileBytes == null) { reasonCode = "readback.file-null"; return false; } if (expectedPlayerData == null) { reasonCode = "readback.expected-player-data-null"; return false; } if (fileBytes.Length < 72 || fileBytes.Length > 268435456) { reasonCode = "readback.file-size-invalid"; return false; } if (expectedPlayerData.Length > 67108864) { reasonCode = "readback.expected-player-data-too-large"; return false; } ByteCursor byteCursor = new ByteCursor(fileBytes, 0, fileBytes.Length); if (!byteCursor.TryReadInt32(out var value) || value < 0 || value > 268435384 || value > byteCursor.Remaining - 4 - 64) { reasonCode = "readback.outer-length-invalid"; return false; } int position = byteCursor.Position; int value2 = -1; if (!byteCursor.TrySkip(value) || !byteCursor.TryReadInt32(out value2) || value2 != 64 || byteCursor.Remaining != 64) { reasonCode = ((value2 == 64) ? "readback.envelope-trailing-or-truncated" : "readback.hash-length-invalid"); return false; } int position2 = byteCursor.Position; byte[] array; using (SHA512 sHA = SHA512.Create()) { array = sHA.ComputeHash(fileBytes, position, value); } if (!FixedEquals(fileBytes, position2, array, 0, array.Length)) { reasonCode = "readback.outer-hash-invalid"; return false; } ByteCursor byteCursor2 = new ByteCursor(fileBytes, position, value); if (!byteCursor2.TryReadInt32(out var value3) || value3 != 43) { reasonCode = "readback.profile-version-unsupported"; return false; } if (!byteCursor2.TryReadInt32(out var value4) || value4 != 105 || !byteCursor2.TrySkip(420) || !byteCursor2.TryReadStrictBoolean(out var value5)) { reasonCode = "readback.profile-header-invalid"; return false; } if (!byteCursor2.TryReadInt32(out var value6) || value6 < 0 || value6 > 4096) { reasonCode = "readback.world-count-invalid"; return false; } for (int i = 0; i < value6; i++) { if (!byteCursor2.TrySkip(8) || !byteCursor2.TryReadStrictBoolean(out value5) || !byteCursor2.TrySkip(12) || !byteCursor2.TryReadStrictBoolean(out value5) || !byteCursor2.TrySkip(12) || !byteCursor2.TryReadStrictBoolean(out value5) || !byteCursor2.TrySkip(12) || !byteCursor2.TrySkip(12) || !byteCursor2.TryReadStrictBoolean(out var value7) || (value7 && !byteCursor2.TrySkipByteArray(268435456))) { reasonCode = "readback.world-data-invalid"; return false; } } if (!byteCursor2.TrySkipString(65536) || !byteCursor2.TryReadInt64(out var value8) || !byteCursor2.TrySkipString(65536) || !byteCursor2.TryReadStrictBoolean(out value5) || !byteCursor2.TrySkip(8)) { reasonCode = "readback.identity-or-date-invalid"; return false; } int num = 0; for (int j = 0; j < 6; j++) { if (!byteCursor2.TryReadInt32(out var value9) || value9 < 0 || value9 > 65536 || num > 262144 - value9) { reasonCode = "readback.dictionary-count-invalid"; return false; } num += value9; for (int k = 0; k < value9; k++) { if (!byteCursor2.TrySkipString(65536) || !byteCursor2.TrySkip(4)) { reasonCode = "readback.dictionary-entry-invalid"; return false; } } } if (!byteCursor2.TryReadStrictBoolean(out var value10) || !value10) { reasonCode = "readback.player-data-missing"; return false; } if (!byteCursor2.TryReadInt32(out var value11) || value11 < 0 || value11 > 67108864 || value11 != byteCursor2.Remaining) { reasonCode = "readback.player-data-length-invalid"; return false; } int position3 = byteCursor2.Position; if (value11 != expectedPlayerData.Length || !FixedEquals(fileBytes, position3, expectedPlayerData, 0, expectedPlayerData.Length)) { reasonCode = "readback.player-data-mismatch"; return false; } byte[] value12; using (SHA256 sHA2 = SHA256.Create()) { value12 = sHA2.ComputeHash(fileBytes, position3, value11); } evidence = new CharacterProfileReadbackEvidence(value8, value, value11, ToHex(array), ToHex(value12)); reasonCode = "ok"; return true; } private static bool FixedEquals(byte[] left, int leftOffset, byte[] right, int rightOffset, int count) { if (left == null || right == null || leftOffset < 0 || rightOffset < 0 || count < 0 || leftOffset > left.Length - count || rightOffset > right.Length - count) { return false; } int num = 0; for (int i = 0; i < count; i++) { num |= left[leftOffset + i] ^ right[rightOffset + i]; } return num == 0; } private static string ToHex(byte[] value) { StringBuilder stringBuilder = new StringBuilder(value.Length * 2); for (int i = 0; i < value.Length; i++) { stringBuilder.Append(value[i].ToString("x2")); } return stringBuilder.ToString(); } } internal static class ControllerBindingPolicy { internal const string ModifierAction = "JoyAltKeys"; internal const string LegacyQuick1Action = "JoyLBumper"; internal const string Quick1Action = "JoyMap"; internal const string Quick2Action = "JoyButtonY"; internal const string Quick3Action = "JoyRBumper"; internal const string SortAction = "JoyButtonA"; internal const string ToggleLockAction = "JoyButtonB"; internal const int RouteCount = 5; internal const int AllRoutesMask = 31; internal static string EffectiveQuick1Action(string modifier, string quick1, string quick2, string quick3, string sort, string toggleLock, out bool legacyMapped) { legacyMapped = SameAction(modifier, "JoyAltKeys") && SameAction(quick1, "JoyLBumper") && SameAction(quick2, "JoyButtonY") && SameAction(quick3, "JoyRBumper") && SameAction(sort, "JoyButtonA") && SameAction(toggleLock, "JoyButtonB"); if (!legacyMapped) { return quick1; } return "JoyMap"; } internal static int ValidRouteMask(string modifierAction, string modifierPath, string[] primaryActions, string[] primaryPaths) { if (primaryActions == null || primaryPaths == null || primaryActions.Length != 5 || primaryPaths.Length != 5 || string.IsNullOrEmpty(modifierAction) || string.IsNullOrWhiteSpace(modifierPath)) { return 0; } int num = 31; for (int i = 0; i < 5; i++) { if (string.IsNullOrEmpty(primaryActions[i]) || string.IsNullOrWhiteSpace(primaryPaths[i]) || SameAction(primaryActions[i], modifierAction) || SamePath(primaryPaths[i], modifierPath)) { num &= ~(1 << i); } } for (int j = 0; j < 5; j++) { if ((num & (1 << j)) == 0) { continue; } for (int k = j + 1; k < 5; k++) { if ((num & (1 << k)) != 0 && (SameAction(primaryActions[j], primaryActions[k]) || SamePath(primaryPaths[j], primaryPaths[k]))) { num &= ~(1 << j); num &= ~(1 << k); } } } return num; } internal static bool RouteIsValid(int mask, int route) { if (route >= 0 && route < 5) { return (mask & (1 << route)) != 0; } return false; } private static bool SameAction(string left, string right) { return string.Equals(left, right, StringComparison.Ordinal); } private static bool SamePath(string left, string right) { return string.Equals(left?.Trim(), right?.Trim(), StringComparison.OrdinalIgnoreCase); } } internal enum ItemProtectionDomainEvidence : byte { NotApplicable, InDomainUnknown, ExactCurrentMember } internal static class ItemProtectionDomain { internal static ItemProtectionDomainEvidence Evaluate(IReadOnlyList governedItems, T candidate, int maximumItems) where T : class { if (candidate == null) { return ItemProtectionDomainEvidence.NotApplicable; } if (governedItems == null || maximumItems < 0 || governedItems.Count > maximumItems) { return ItemProtectionDomainEvidence.InDomainUnknown; } int num = 0; for (int i = 0; i < governedItems.Count; i++) { if (governedItems[i] == candidate) { num++; } } return num switch { 0 => ItemProtectionDomainEvidence.NotApplicable, 1 => ItemProtectionDomainEvidence.ExactCurrentMember, _ => ItemProtectionDomainEvidence.InDomainUnknown, }; } internal static ItemProtectionState ClassifyExactMember(bool occupiesSpecialRow, bool explicitlyLocked) { if (!(occupiesSpecialRow || explicitlyLocked)) { return ItemProtectionState.Unlocked; } return ItemProtectionState.Locked; } } internal sealed class PickupFilterSet { internal const int MaximumRules = 128; internal const int MaximumInputCharacters = 16384; private readonly HashSet _rules; internal int Count => _rules.Count; internal bool Truncated { get; } private PickupFilterSet(HashSet rules, bool truncated) { _rules = rules; Truncated = truncated; } internal bool Matches(string prefabId, string sharedName) { if (string.IsNullOrEmpty(prefabId) || !_rules.Contains(prefabId)) { if (!string.IsNullOrEmpty(sharedName)) { return _rules.Contains(sharedName); } return false; } return true; } internal static PickupFilterSet Parse(string text) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); bool truncated = false; if (text != null && text.Length > 16384) { return new PickupFilterSet(hashSet, truncated: true); } if (!string.IsNullOrEmpty(text)) { string[] array = text.Split(new char[4] { ',', ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length != 0 && text2.Length <= 128 && !ContainsUnsafe(text2)) { if (hashSet.Count >= 128 && !hashSet.Contains(text2)) { truncated = true; } else { hashSet.Add(text2); } } } } return new PickupFilterSet(hashSet, truncated); } private static bool ContainsUnsafe(string value) { for (int i = 0; i < value.Length; i++) { if (char.IsControl(value[i]) || value[i] == '<' || value[i] == '>') { return true; } } return false; } } internal readonly struct PickupDecision { internal bool Filtered { get; } internal bool Encumbered { get; } internal int OverflowItems { get; } internal int AcceptedItems { get; } internal float ResultingWeight { get; } internal bool Fits { get { if (!Filtered) { return OverflowItems == 0; } return false; } } internal PickupDecision(bool filtered, bool encumbered, int overflowItems, int acceptedItems, float resultingWeight) { Filtered = filtered; Encumbered = encumbered; OverflowItems = overflowItems; AcceptedItems = acceptedItems; ResultingWeight = resultingWeight; } } internal static class PickupPlanner { internal static PickupDecision Evaluate(int stack, int maximumStack, int compatibleStackCapacity, int emptyGeneralSlots, float unitWeight, float currentWeight, float maximumCarryWeight, bool filtered) { if (stack < 0) { throw new ArgumentOutOfRangeException("stack"); } if (maximumStack <= 0) { throw new ArgumentOutOfRangeException("maximumStack"); } if (compatibleStackCapacity < 0) { throw new ArgumentOutOfRangeException("compatibleStackCapacity"); } if (emptyGeneralSlots < 0 || emptyGeneralSlots > 128) { throw new ArgumentOutOfRangeException("emptyGeneralSlots"); } if (!FiniteNonNegative(unitWeight) || !FiniteNonNegative(currentWeight) || !FiniteNonNegative(maximumCarryWeight)) { throw new ArgumentOutOfRangeException("Weights must be finite and non-negative."); } long num = (long)emptyGeneralSlots * (long)maximumStack; long val = Math.Min(2147483647L, compatibleStackCapacity + num); int num2 = (int)((!filtered) ? Math.Min(stack, val) : 0); int overflowItems = (filtered ? stack : (stack - num2)); double num3 = (double)currentWeight + (double)num2 * (double)unitWeight; float num4 = ((num3 >= 3.4028234663852886E+38) ? float.MaxValue : ((float)num3)); return new PickupDecision(filtered, num4 > maximumCarryWeight, overflowItems, num2, num4); } private static bool FiniteNonNegative(float value) { if (!float.IsNaN(value) && !float.IsInfinity(value)) { return value >= 0f; } return false; } } internal sealed class SortItemDescriptor { internal InventorySlotCoordinate Coordinate { get; } internal int Category { get; } internal string StableName { get; } internal int Quality { get; } internal float Weight { get; } internal bool Equipped { get; } internal SortItemDescriptor(InventorySlotCoordinate coordinate, int category, string stableName, int quality, float weight, bool equipped) { if (category < 0 || category > 1024) { throw new ArgumentOutOfRangeException("category"); } if (string.IsNullOrEmpty(stableName) || stableName.Length > 160) { throw new ArgumentException("A bounded stable item name is required.", "stableName"); } if (quality < 0) { throw new ArgumentOutOfRangeException("quality"); } if (float.IsNaN(weight) || float.IsInfinity(weight) || weight < 0f) { throw new ArgumentOutOfRangeException("weight"); } Coordinate = coordinate; Category = category; StableName = stableName; Quality = quality; Weight = weight; Equipped = equipped; } } internal readonly struct SortMove { internal InventorySlotCoordinate Source { get; } internal InventorySlotCoordinate Destination { get; } internal SortMove(InventorySlotCoordinate source, InventorySlotCoordinate destination) { Source = source; Destination = destination; } } internal sealed class SafeSortPlan { private readonly ReadOnlyCollection _moves; internal IReadOnlyList Moves => _moves; internal int MovableCount { get; } internal bool ChangesAnything => _moves.Count != 0; internal SafeSortPlan(IEnumerable moves, int movableCount) { _moves = new List(moves ?? Array.Empty()).AsReadOnly(); MovableCount = movableCount; } } internal static class SafeSortPlanner { internal static bool TryPlan(TopologyLayout layout, IEnumerable items, IEnumerable locks, IEnumerable selectedRows, out SafeSortPlan plan, out string reasonCode) { plan = null; if (layout == null) { reasonCode = "sort.layout-null"; return false; } HashSet hashSet = new HashSet(); if (locks != null) { foreach (InventorySlotCoordinate @lock in locks) { if (!layout.InBounds(@lock) || !hashSet.Add(@lock)) { reasonCode = "sort.locks-invalid"; return false; } } } SortedSet sortedSet = new SortedSet(); if (selectedRows != null) { foreach (int selectedRow in selectedRows) { if (selectedRow <= 0 || selectedRow >= layout.SpecialRow) { reasonCode = "sort.region-unsafe"; return false; } sortedSet.Add(selectedRow); } } if (sortedSet.Count == 0) { reasonCode = "sort.region-empty"; return false; } Dictionary dictionary = new Dictionary(); if (items != null) { foreach (SortItemDescriptor item in items) { if (item == null || !layout.InBounds(item.Coordinate) || dictionary.ContainsKey(item.Coordinate)) { reasonCode = "sort.items-invalid"; return false; } dictionary.Add(item.Coordinate, item); if (dictionary.Count > 128) { reasonCode = "sort.item-bound-exceeded"; return false; } } } List list = new List(); foreach (SortItemDescriptor value2 in dictionary.Values) { if (sortedSet.Contains(value2.Coordinate.Y) && !value2.Equipped && !hashSet.Contains(value2.Coordinate)) { list.Add(value2); } } list.Sort(CompareItems); List list2 = new List(); foreach (int item2 in sortedSet) { for (int i = 0; i < layout.Width; i++) { InventorySlotCoordinate inventorySlotCoordinate = new InventorySlotCoordinate(i, item2); if (!hashSet.Contains(inventorySlotCoordinate) && (!dictionary.TryGetValue(inventorySlotCoordinate, out var value) || !value.Equipped)) { list2.Add(inventorySlotCoordinate); } } } if (list.Count > list2.Count) { reasonCode = "sort.capacity-proof-failed"; return false; } List list3 = new List(); HashSet hashSet2 = new HashSet(); HashSet hashSet3 = new HashSet(); for (int j = 0; j < list.Count; j++) { InventorySlotCoordinate coordinate = list[j].Coordinate; InventorySlotCoordinate inventorySlotCoordinate2 = list2[j]; if (!coordinate.Equals(inventorySlotCoordinate2)) { if (!hashSet2.Add(coordinate) || !hashSet3.Add(inventorySlotCoordinate2)) { reasonCode = "sort.permutation-not-unique"; return false; } list3.Add(new SortMove(coordinate, inventorySlotCoordinate2)); } } plan = new SafeSortPlan(list3, list.Count); reasonCode = "ok"; return true; } private static int CompareItems(SortItemDescriptor left, SortItemDescriptor right) { int num = left.Category.CompareTo(right.Category); if (num != 0) { return num; } int num2 = StringComparer.Ordinal.Compare(left.StableName, right.StableName); if (num2 != 0) { return num2; } int num3 = right.Quality.CompareTo(left.Quality); if (num3 != 0) { return num3; } int num4 = right.Weight.CompareTo(left.Weight); if (num4 != 0) { return num4; } return left.Coordinate.CompareTo(right.Coordinate); } } internal static class SelectedRowPolicy { internal const int MaximumTokens = 16; internal const int MaximumCharacters = 256; internal static bool TryParse(string text, TopologyLayout layout, out IReadOnlyList rows, out string reasonCode) { rows = Array.Empty(); if (layout == null) { reasonCode = "sort.layout-null"; return false; } if (text != null && text.Length > 256) { reasonCode = "sort.region-character-bound"; return false; } SortedSet sortedSet = new SortedSet(); string[] array = (text ?? string.Empty).Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length > 16) { reasonCode = "sort.region-token-bound"; return false; } string[] array2 = array; for (int i = 0; i < array2.Length; i++) { if (!int.TryParse(array2[i].Trim(), NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result <= 0 || result >= layout.SpecialRow) { reasonCode = "sort.region-unsafe"; return false; } sortedSet.Add(result); } if (sortedSet.Count == 0) { for (int j = 1; j < layout.SpecialRow; j++) { sortedSet.Add(j); } } List list = new List(sortedSet); rows = list.AsReadOnly(); reasonCode = "ok"; return true; } } internal enum InventoryItemCategory { Unknown, Material, Consumable, Helmet, Chest, Legs, Cape, Utility, Tool, Other } internal sealed class TopologyLayout { internal const int RequiredWidth = 8; internal const int MinimumHeight = 4; internal const int RoleCount = 8; private readonly InventorySlotCoordinate[] _coordinates; internal int Width { get; } internal int Height { get; } internal int SpecialRow { get; } internal int TotalSlots => Width * Height; private TopologyLayout(int width, int height) { Width = width; Height = height; SpecialRow = height - 1; _coordinates = new InventorySlotCoordinate[8]; for (int i = 0; i < 8; i++) { _coordinates[i] = new InventorySlotCoordinate(i, SpecialRow); } } internal bool MatchesNativeDimensions(int width, int height) { if (width == Width && height == Height && width == 8 && height >= 4) { return width * height <= 128; } return false; } internal InventorySlotCoordinate Coordinate(InventoryRoleKind role) { int num = (int)(role - 1); if (num < 0 || num >= _coordinates.Length) { throw new ArgumentOutOfRangeException("role"); } return _coordinates[num]; } internal bool TryRoleAt(int x, int y, out InventoryRoleKind role) { if (y == SpecialRow && x >= 0 && x < 8) { role = (InventoryRoleKind)(x + 1); return true; } role = (InventoryRoleKind)0; return false; } internal bool InBounds(InventorySlotCoordinate coordinate) { if (coordinate.X >= 0 && coordinate.X < Width && coordinate.Y >= 0) { return coordinate.Y < Height; } return false; } internal static bool TryCreate(int width, int height, out TopologyLayout layout, out string reasonCode) { layout = null; if (width != 8) { reasonCode = "topology.width-not-eight"; return false; } if (height < 4) { reasonCode = "topology.height-too-small"; return false; } if (height > 16) { reasonCode = "topology.slot-bound-exceeded"; return false; } TopologyLayout topologyLayout = new TopologyLayout(width, height); HashSet hashSet = new HashSet(); for (int i = 0; i < 8; i++) { InventorySlotCoordinate inventorySlotCoordinate = topologyLayout._coordinates[i]; if (!topologyLayout.InBounds(inventorySlotCoordinate) || !hashSet.Add(inventorySlotCoordinate)) { reasonCode = "topology.role-proof-failed"; return false; } } layout = topologyLayout; reasonCode = "ok"; return true; } internal static bool Accepts(InventoryRoleKind role, InventoryItemCategory category) { switch (role) { case InventoryRoleKind.Head: return category == InventoryItemCategory.Helmet; case InventoryRoleKind.Chest: return category == InventoryItemCategory.Chest; case InventoryRoleKind.Legs: return category == InventoryItemCategory.Legs; case InventoryRoleKind.Cape: return category == InventoryItemCategory.Cape; case InventoryRoleKind.Utility: return category == InventoryItemCategory.Utility; case InventoryRoleKind.Quick1: case InventoryRoleKind.Quick2: case InventoryRoleKind.Quick3: if (category != InventoryItemCategory.Consumable && category != InventoryItemCategory.Tool) { return category == InventoryItemCategory.Utility; } return true; default: return false; } } internal static bool TryEquipmentRole(InventoryItemCategory category, out InventoryRoleKind role) { switch (category) { case InventoryItemCategory.Helmet: role = InventoryRoleKind.Head; return true; case InventoryItemCategory.Chest: role = InventoryRoleKind.Chest; return true; case InventoryItemCategory.Legs: role = InventoryRoleKind.Legs; return true; case InventoryItemCategory.Cape: role = InventoryRoleKind.Cape; return true; case InventoryItemCategory.Utility: role = InventoryRoleKind.Utility; return true; default: role = (InventoryRoleKind)0; return false; } } } internal sealed class PersistedTopologyState { private readonly byte[] _lockBits; internal int Width { get; } internal int Height { get; } internal PersistedTopologyState(int width, int height, byte[] lockBits) { Width = width; Height = height; _lockBits = (byte[])(lockBits ?? throw new ArgumentNullException("lockBits")).Clone(); } internal bool IsLocked(int x, int y) { if (x < 0 || x >= Width || y < 0 || y >= Height) { return false; } int num = y * Width + x; return (_lockBits[num >> 3] & (1 << (num & 7))) != 0; } internal IReadOnlyList LockedSlots() { List list = new List(); for (int i = 0; i < Height; i++) { for (int j = 0; j < Width; j++) { if (IsLocked(j, i)) { list.Add(new InventorySlotCoordinate(j, i)); } } } return list.AsReadOnly(); } internal byte[] CopyBits() { return (byte[])_lockBits.Clone(); } } internal static class TopologyPersistenceCodec { internal const string MetadataKey = "runic.inventory.topology.v1"; internal const int MaximumPayloadCharacters = 256; private const string Version = "1"; internal static bool TryEncode(TopologyLayout layout, IEnumerable locks, out string payload, out string reasonCode) { payload = string.Empty; if (layout == null) { reasonCode = "persistence.layout-null"; return false; } byte[] array = new byte[(layout.TotalSlots + 7) / 8]; HashSet hashSet = new HashSet(); if (locks != null) { foreach (InventorySlotCoordinate @lock in locks) { if (!layout.InBounds(@lock)) { reasonCode = "persistence.lock-out-of-bounds"; return false; } if (!hashSet.Add(@lock)) { reasonCode = "persistence.lock-duplicate"; return false; } int num = @lock.Y * layout.Width + @lock.X; array[num >> 3] |= (byte)(1 << (num & 7)); } } string text = "1|" + layout.Width.ToString(CultureInfo.InvariantCulture) + "|" + layout.Height.ToString(CultureInfo.InvariantCulture) + "|" + ToHex(array); string text2 = Digest(text); payload = text + "|" + text2; if (payload.Length > 256) { payload = string.Empty; reasonCode = "persistence.payload-too-large"; return false; } reasonCode = "ok"; return true; } internal static bool TryDecode(string payload, out PersistedTopologyState state, out string reasonCode) { state = null; if (string.IsNullOrEmpty(payload)) { reasonCode = "persistence.missing"; return false; } if (payload.Length > 256) { reasonCode = "persistence.payload-too-large"; return false; } string[] array = payload.Split('|'); if (array.Length != 5 || !string.Equals(array[0], "1", StringComparison.Ordinal)) { reasonCode = "persistence.schema-invalid"; return false; } if (!int.TryParse(array[1], NumberStyles.None, CultureInfo.InvariantCulture, out var result) || !int.TryParse(array[2], NumberStyles.None, CultureInfo.InvariantCulture, out var result2) || !TopologyLayout.TryCreate(result, result2, out var layout, out var _)) { reasonCode = "persistence.dimensions-invalid"; return false; } int byteCount = (layout.TotalSlots + 7) / 8; if (!TryHex(array[3], byteCount, out var bytes)) { reasonCode = "persistence.lock-bits-invalid"; return false; } string text = Digest(array[0] + "|" + array[1] + "|" + array[2] + "|" + array[3]); if (array[4].Length != text.Length || !FixedEquals(array[4], text)) { reasonCode = "persistence.digest-invalid"; return false; } state = new PersistedTopologyState(result, result2, bytes); reasonCode = "ok"; return true; } private static string Digest(string value) { using SHA256 sHA = SHA256.Create(); return ToHex(sHA.ComputeHash(Encoding.UTF8.GetBytes(value))); } private static string ToHex(byte[] bytes) { StringBuilder stringBuilder = new StringBuilder(bytes.Length * 2); for (int i = 0; i < bytes.Length; i++) { stringBuilder.Append(bytes[i].ToString("x2", CultureInfo.InvariantCulture)); } return stringBuilder.ToString(); } private static bool TryHex(string text, int byteCount, out byte[] bytes) { bytes = null; if (text == null || text.Length != byteCount * 2) { return false; } byte[] array = new byte[byteCount]; for (int i = 0; i < byteCount; i++) { int num = Hex(text[i * 2]); int num2 = Hex(text[i * 2 + 1]); if (num < 0 || num2 < 0) { return false; } array[i] = (byte)((num << 4) | num2); } bytes = array; return true; } private static int Hex(char value) { if (value >= '0' && value <= '9') { return value - 48; } if (value >= 'a' && value <= 'f') { return value - 97 + 10; } return -1; } private static bool FixedEquals(string left, string right) { int num = left.Length ^ right.Length; int num2 = Math.Min(left.Length, right.Length); for (int i = 0; i < num2; i++) { num |= left[i] ^ right[i]; } return num == 0; } } } namespace RunicInventory.Capabilities { public static class InventoryCapabilityIds { public const string Topology = "inventory.topology"; public const string ItemLocks = "inventory.item-locks"; public const string QuickSlots = "inventory.quick-slots"; public const string PickupPreview = "inventory.pickup-preview"; public const string Status = "inventory.status"; private static readonly IReadOnlyList Values = Array.AsReadOnly(new string[5] { "inventory.topology", "inventory.item-locks", "inventory.quick-slots", "inventory.pickup-preview", "inventory.status" }); public static IReadOnlyList Published => Values; } } namespace RunicInventory.Api { public enum InventoryAuthorityMode { Unavailable, AuthoritativeLocal, RemoteDedicatedCompatibility, MigrationSafeCompatibility, Disabled, BatchInert } public enum InventoryRoleKind { Head = 1, Chest, Legs, Cape, Utility, Quick1, Quick2, Quick3 } public readonly struct InventorySlotCoordinate : IEquatable, IComparable { public int X { get; } public int Y { get; } public InventorySlotCoordinate(int x, int y) { if (x < 0 || x >= 128) { throw new ArgumentOutOfRangeException("x"); } if (y < 0 || y >= 128) { throw new ArgumentOutOfRangeException("y"); } X = x; Y = y; } public int CompareTo(InventorySlotCoordinate other) { int num = Y.CompareTo(other.Y); if (num == 0) { return X.CompareTo(other.X); } return num; } public bool Equals(InventorySlotCoordinate other) { if (X == other.X) { return Y == other.Y; } return false; } public override bool Equals(object obj) { if (obj is InventorySlotCoordinate other) { return Equals(other); } return false; } public override int GetHashCode() { return (Y * 397) ^ X; } public override string ToString() { return X + "," + Y; } } public sealed class InventoryRoleSnapshot { public InventoryRoleKind Role { get; } public InventorySlotCoordinate Coordinate { get; } public bool Occupied { get; } public bool Equipped { get; } public bool Locked { get; } public int Stack { get; } public string PrefabId { get; } public string ItemFingerprint { get; } public InventoryRoleSnapshot(InventoryRoleKind role, InventorySlotCoordinate coordinate, bool occupied, bool equipped, bool locked, int stack, string prefabId, string itemFingerprint) { if (!Enum.IsDefined(typeof(InventoryRoleKind), role)) { throw new ArgumentOutOfRangeException("role"); } if (stack < 0) { throw new ArgumentOutOfRangeException("stack"); } PrefabId = Bounded(prefabId, "prefabId", 128); ItemFingerprint = Bounded(itemFingerprint, "itemFingerprint", 64); if (!occupied && (stack != 0 || PrefabId.Length != 0 || ItemFingerprint.Length != 0 || equipped)) { throw new ArgumentException("An empty role cannot disclose item state."); } if (occupied && (stack <= 0 || ItemFingerprint.Length != 64)) { throw new ArgumentException("An occupied role requires a positive stack and SHA-256 fingerprint."); } if (ItemFingerprint.Length != 0 && !IsLowerHex(ItemFingerprint)) { throw new ArgumentException("Item fingerprint must be lowercase SHA-256 hex.", "itemFingerprint"); } Role = role; Coordinate = coordinate; Occupied = occupied; Equipped = equipped; Locked = locked; Stack = stack; } private static string Bounded(string value, string name, int maximum) { string text = value ?? string.Empty; if (text.Length > maximum) { throw new ArgumentOutOfRangeException(name); } for (int i = 0; i < text.Length; i++) { if (char.IsControl(text[i]) || text[i] == '<' || text[i] == '>') { throw new ArgumentException("Control and rich-text delimiter characters are not permitted.", name); } } return text; } private static bool IsLowerHex(string value) { for (int i = 0; i < value.Length; i++) { if ((value[i] < '0' || value[i] > '9') && (value[i] < 'a' || value[i] > 'f')) { return false; } } return true; } } public sealed class InventoryTopologySnapshot { public const int MaximumNativeSlots = 128; private readonly ReadOnlyCollection _roles; private readonly ReadOnlyCollection _lockedSlots; public string ProviderId { get; } public string ProtocolVersion { get; } public long Generation { get; } public InventoryAuthorityMode AuthorityMode { get; } public int Width { get; } public int Height { get; } public int TotalNativeSlots => Width * Height; public int OccupiedNativeSlots { get; } public bool SerializationVerified { get; } public IReadOnlyList Roles => _roles; public IReadOnlyList LockedSlots => _lockedSlots; public string TopologyHash { get; } public InventoryTopologySnapshot(string providerId, string protocolVersion, long generation, InventoryAuthorityMode authorityMode, int width, int height, int occupiedNativeSlots, bool serializationVerified, IEnumerable roles, IEnumerable lockedSlots, string topologyHash) { ProviderId = Bounded(providerId, "providerId", 64, required: true); ProtocolVersion = Bounded(protocolVersion, "protocolVersion", 16, required: true); if (generation < 0) { throw new ArgumentOutOfRangeException("generation"); } if (!Enum.IsDefined(typeof(InventoryAuthorityMode), authorityMode)) { throw new ArgumentOutOfRangeException("authorityMode"); } if (width != 8 || height < 4 || width * height > 128) { throw new ArgumentOutOfRangeException("width"); } if (occupiedNativeSlots < 0 || occupiedNativeSlots > width * height) { throw new ArgumentOutOfRangeException("occupiedNativeSlots"); } List list = new List(); HashSet hashSet = new HashSet(); HashSet hashSet2 = new HashSet(); if (roles != null) { foreach (InventoryRoleSnapshot role in roles) { if (role == null || !hashSet.Add(role.Role) || !hashSet2.Add(role.Coordinate)) { throw new ArgumentException("Topology roles must be non-null and unique.", "roles"); } if (role.Coordinate.X >= width || role.Coordinate.Y >= height) { throw new ArgumentOutOfRangeException("roles"); } list.Add(role); } } if (list.Count != 8) { throw new ArgumentException("Exactly eight topology roles are required.", "roles"); } list.Sort((InventoryRoleSnapshot left, InventoryRoleSnapshot right) => ((int)left.Role).CompareTo((int)right.Role)); for (int num = 0; num < list.Count; num++) { if (list[num].Role != (InventoryRoleKind)(num + 1) || list[num].Coordinate.X != num || list[num].Coordinate.Y != height - 1) { throw new ArgumentException("Every canonical topology role is required exactly once.", "roles"); } } List list2 = new List(); HashSet hashSet3 = new HashSet(); if (lockedSlots != null) { foreach (InventorySlotCoordinate lockedSlot in lockedSlots) { if (lockedSlot.X >= width || lockedSlot.Y >= height) { throw new ArgumentOutOfRangeException("lockedSlots"); } if (!hashSet3.Add(lockedSlot)) { throw new ArgumentException("Duplicate locked slot.", "lockedSlots"); } list2.Add(lockedSlot); } } if (list2.Count > 128) { throw new ArgumentOutOfRangeException("lockedSlots"); } list2.Sort(); int num2 = 0; foreach (InventoryRoleSnapshot item in list) { if (item.Occupied) { num2++; } if (item.Locked != hashSet3.Contains(item.Coordinate)) { throw new ArgumentException("Role lock facts must match the canonical locked-slot set.", "lockedSlots"); } } if (occupiedNativeSlots < num2) { throw new ArgumentOutOfRangeException("occupiedNativeSlots"); } TopologyHash = Bounded(topologyHash, "topologyHash", 64, required: true); if (TopologyHash.Length != 64) { throw new ArgumentException("Topology hash must be SHA-256 hex.", "topologyHash"); } for (int num3 = 0; num3 < TopologyHash.Length; num3++) { if ((TopologyHash[num3] < '0' || TopologyHash[num3] > '9') && (TopologyHash[num3] < 'a' || TopologyHash[num3] > 'f')) { throw new ArgumentException("Topology hash must be lowercase SHA-256 hex.", "topologyHash"); } } Generation = generation; AuthorityMode = authorityMode; Width = width; Height = height; OccupiedNativeSlots = occupiedNativeSlots; SerializationVerified = serializationVerified; _roles = list.AsReadOnly(); _lockedSlots = list2.AsReadOnly(); } private static string Bounded(string value, string name, int maximum, bool required) { string text = value ?? string.Empty; if (required && text.Length == 0) { throw new ArgumentException("A value is required.", name); } if (text.Length > maximum) { throw new ArgumentOutOfRangeException(name); } for (int i = 0; i < text.Length; i++) { if (char.IsControl(text[i]) || text[i] == '<' || text[i] == '>') { throw new ArgumentException("Control and rich-text delimiter characters are not permitted.", name); } } return text; } } public interface IInventoryTopologyService { string ProviderId { get; } bool TryCapture(long playerId, out InventoryTopologySnapshot snapshot, out string failureCode); } public interface IInventoryProtectionService { bool TryIsLocked(long playerId, InventorySlotCoordinate coordinate, out bool locked, out string failureCode); } public sealed class InventoryFeatureStatus { public InventoryAuthorityMode Mode { get; } public bool TopologyActive { get; } public string ReasonCode { get; } public InventoryFeatureStatus(InventoryAuthorityMode mode, bool topologyActive, string reasonCode) { if (!Enum.IsDefined(typeof(InventoryAuthorityMode), mode)) { throw new ArgumentOutOfRangeException("mode"); } if (string.IsNullOrWhiteSpace(reasonCode) || reasonCode.Length > 96) { throw new ArgumentException("A bounded reason code is required.", "reasonCode"); } for (int i = 0; i < reasonCode.Length; i++) { if (!char.IsLetterOrDigit(reasonCode[i]) && reasonCode[i] != '.' && reasonCode[i] != '-') { throw new ArgumentException("Reason code contains an unsafe character.", "reasonCode"); } } Mode = mode; TopologyActive = topologyActive; ReasonCode = reasonCode; } } public interface IInventoryStatusService { InventoryFeatureStatus Snapshot(); } public enum ItemProtectionState { Unknown, Unlocked, Locked } internal interface IItemProtectionQuery { bool TryGetProtection(object nativeItem, out ItemProtectionState state); } public static class InventoryIntegrationApi { private static readonly object Gate = new object(); private static InventoryRuntime _runtime; public static bool TryGetProtection(object nativeItem, out int state) { state = 0; InventoryRuntime runtime; lock (Gate) { runtime = _runtime; } if (!(nativeItem is ItemData)) { return false; } if (runtime == null) { return true; } try { ItemProtectionState state2; bool result = runtime.TryGetProtection(nativeItem, out state2); state = (int)state2; return result; } catch (Exception) { state = 0; return true; } } internal static void Attach(InventoryRuntime runtime) { lock (Gate) { _runtime = runtime; } } internal static void Detach(InventoryRuntime runtime) { lock (Gate) { if (_runtime == runtime) { _runtime = null; } } } } }