Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of RunicProduction v2.3.4
BepInEx/plugins/RunicProduction/RunicProduction.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Runtime.Serialization.Json; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Runic.Compatibility; using Runic.Localization; using RunicAutomation; using RunicAutomation.Operations; using RunicProduction.Contracts; using RunicProduction.Core; using RunicProduction.Integration; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("RunicMultiAccess.NativeRegression")] [assembly: AssemblyTitle("Runic Production")] [assembly: AssemblyDescription("Standalone native-owner production with persistent links and bounded replenishment")] [assembly: AssemblyCompany("ChazmanMods")] [assembly: AssemblyProduct("Runic Production")] [assembly: AssemblyFileVersion("2.3.4.0")] [assembly: AssemblyInformationalVersion("2.3.4")] [assembly: InternalsVisibleTo("RunicProduction.Tests")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("2.3.4.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 RunicAutomation { public static class ContainerAuthority { private sealed class Policy { public Func<Container, ZDOID, long, long, bool> Allows; public Func<Container, bool> Refresh; } private static readonly Dictionary<string, Policy> Policies = new Dictionary<string, Policy>(); private static readonly FieldInfo ViewField = AccessTools.Field(typeof(Container), "m_nview"); private static Dictionary<string, Delegate> Routes => SharedState.Get("authority", () => new Dictionary<string, Delegate> { ["RegisterPolicy"] = new Action<string, Func<Container, ZDOID, long, long, bool>, Func<Container, bool>>(RegisterPolicyCore), ["UnregisterPolicy"] = (Action<string>)delegate(string name) { Policies.Remove(name); }, ["TryAcquire"] = new Func<Container, string, ZDOID, long, bool, bool>(TryAcquireCore) }); public static void RegisterPolicy(string name, Func<Container, ZDOID, long, long, bool> allows, Func<Container, bool> refresh) { ((Action<string, Func<Container, ZDOID, long, long, bool>, Func<Container, bool>>)Routes["RegisterPolicy"])(name, allows, refresh); } public static void UnregisterPolicy(string name) { ((Action<string>)Routes["UnregisterPolicy"])(name); } public static void Register(Container chest) { } public static void Clear() { } public static bool TryAcquire(Container chest, string policy, ZDOID context, long principal, bool allowInUse = false) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) return ((Func<Container, string, ZDOID, long, bool, bool>)Routes["TryAcquire"])(chest, policy, context, principal, allowInUse); } private static void RegisterPolicyCore(string name, Func<Container, ZDOID, long, long, bool> allows, Func<Container, bool> refresh) { Policies[name] = new Policy { Allows = allows, Refresh = refresh }; } public static ZNetView View(Container chest) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown if (!((Object)(object)chest == (Object)null)) { if (!((Object)(object)chest.m_rootObjectOverride != (Object)null)) { return (ZNetView)ViewField.GetValue(chest); } return chest.m_rootObjectOverride; } return null; } public unsafe static bool Blocked(Container chest) { //IL_002d: 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_0049: Unknown result type (might be due to invalid IL or missing references) ZNetView obj = View(chest); ZDO val = ((obj != null) ? obj.GetZDO() : null); if (!((Object)(object)chest == (Object)null) && !MutationGate.IsBlocked(chest.GetInventory())) { if (val != null) { if (!MutationGate.IsBlocked(val.m_uid)) { ZDOID uid = val.m_uid; return MutationGate.IsBlocked("valheim.zdo:" + ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString()); } return true; } return false; } return true; } public static bool Writable(Container chest, bool allowInUse = false) { if ((Object)(object)chest != (Object)null && ((Behaviour)chest).isActiveAndEnabled && !Blocked(chest)) { if (allowInUse) { goto IL_006f; } if (!chest.IsInUse()) { ZNetView obj = View(chest); if (obj != null) { ZDO zDO = obj.GetZDO(); if (((zDO != null) ? new int?(zDO.GetInt(ZDOVars.s_inUse, 0)) : ((int?)null)) == 0) { goto IL_006f; } } } } return false; IL_006f: if (!((Object)(object)chest.m_wagon == (Object)null)) { return !chest.m_wagon.InUse(); } return true; } public static string Describe(Container chest) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)chest == (Object)null)) { string[] obj = new string[8] { "endpoint=", null, null, null, null, null, null, null }; ZNetView obj2 = View(chest); obj[1] = ((obj2 == null) ? ((ZDOID?)null) : obj2.GetZDO()?.m_uid).ToString(); obj[2] = "; nativeOwner="; ZNetView obj3 = View(chest); obj[3] = ((obj3 != null) ? new bool?(obj3.IsOwner()) : ((bool?)null)).ToString(); obj[4] = "; blocked="; obj[5] = Blocked(chest).ToString(); obj[6] = "; inUse="; obj[7] = chest.IsInUse().ToString(); return string.Concat(obj); } return "container=missing"; } private static bool TryAcquireCore(Container chest, string policy, ZDOID context, long principal, bool allowInUse = false) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || !Writable(chest, allowInUse) || !Policies.TryGetValue(policy, out var value)) { return false; } ZNetView val = View(chest); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); if (val2 == null || !value.Allows(chest, context, principal, ZNet.GetUID())) { return false; } if (!val.IsOwner()) { val.ClaimOwnership(); if (!val.IsOwner() || !value.Refresh(chest)) { return false; } } if (val.IsValid() && val.IsOwner() && val.GetZDO() == val2 && Writable(chest, allowInUse)) { return value.Allows(chest, context, principal, ZNet.GetUID()); } return false; } public static Player FindPlayer(ZDOID context, long principal, long sender) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) ZNetScene instance = ZNetScene.instance; GameObject val = ((instance != null) ? instance.FindInstance(context) : null); Player val2 = (((Object)(object)val == (Object)null) ? null : val.GetComponent<Player>()); ZNetView val3 = (((Object)(object)val == (Object)null) ? null : val.GetComponent<ZNetView>()); if (!((Object)(object)val2 != (Object)null) || !((Object)(object)val3 != (Object)null) || !val3.IsValid() || val3.GetZDO().GetOwner() != sender || val2.GetPlayerID() != principal) { return null; } return val2; } public static ZDOID PlayerContext(Player player) { //IL_0054: 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_004e: 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) if (!((Object)(object)player == (Object)null)) { ZNetView component = ((Component)player).GetComponent<ZNetView>(); return (ZDOID)(((??)((component == null) ? ((ZDOID?)null) : component.GetZDO()?.m_uid)) ?? ZDOID.None); } return ZDOID.None; } public static bool PlayerAccess(Container chest, ZDOID context, long principal, long sender, float range, bool requireWard) { //IL_0000: 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_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) Player val = FindPlayer(context, principal, sender); if (!((Object)(object)val == (Object)null) && !((Object)(object)chest == (Object)null) && !(range <= 0f) && !float.IsNaN(range)) { Vector3 val2 = ((Component)val).transform.position - ((Component)chest).transform.position; if (!(((Vector3)(ref val2)).sqrMagnitude > range * range)) { MethodInfo methodInfo = AccessTools.Method(typeof(Container), "CheckAccess", (Type[])null, (Type[])null); if (methodInfo == null || !(bool)methodInfo.Invoke(chest, new object[1] { principal })) { return false; } if (!requireWard && !chest.m_checkGuardStone) { return true; } if (!(AccessTools.Field(typeof(PrivateArea), "m_allAreas").GetValue(null) is List<PrivateArea> list)) { return false; } foreach (PrivateArea item in list) { if (!((Object)(object)item == (Object)null) && (bool)AccessTools.Method(typeof(PrivateArea), "IsEnabled", (Type[])null, (Type[])null).Invoke(item, null) && (bool)AccessTools.Method(typeof(PrivateArea), "IsInside", (Type[])null, (Type[])null).Invoke(item, new object[2] { ((Component)chest).transform.position, 0f })) { Piece component = ((Component)item).GetComponent<Piece>(); if ((Object)(object)component == (Object)null || component.GetCreator() == 0L) { return false; } if (component.GetCreator() != principal && !(bool)AccessTools.Method(typeof(PrivateArea), "IsPermitted", (Type[])null, (Type[])null).Invoke(item, new object[1] { principal })) { return false; } } } return true; } } return false; } } [HarmonyPatch(typeof(ZNet), "Start")] internal static class RegisterSnapshotProtocol { private static void Postfix() { ManagedDrawerInteraction.Register(); } } [HarmonyPatch(typeof(ZNet), "OnDestroy")] internal static class EndWorld { private static void Postfix() { InventoryOperationNetwork.Clear(); ManagedChestLedger.Clear(); CharacterOperationEscrow.Clear(); NativeChestInterop.Clear(); InventoryWorldLifecycle.Clear(); WorldInventoryOrigin.Clear(); ManagedWorldDrops.Clear(); ManagedDrawerRetrieval.Clear(); ManagedAnimalConsumption.Clear(); ContainerAuthority.Clear(); MutationGate.EndSession(); } } [HarmonyPatch(typeof(ZNet), "Update")] internal static class InventoryOperationLoop { private static string lastFailure; private static void Postfix() { try { InventoryOperationNetwork.Tick(); if (InventoryOperationNetwork.Ready) { ManagedChestLedger.Tick(); NativeChestInterop.Tick(); CharacterOperationEscrow.Tick(); ManagedWorldDrops.Tick(); WorldInventoryOrigin.Tick(); InventoryWorldLifecycle.Tick(); } } catch (Exception ex) { string text = ex.ToString(); if (lastFailure != text) { lastFailure = text; Debug.LogError((object)("Runic inventory operation update failed: " + text)); } MutationGate.Report("inventory operation update: " + ex); } } } [HarmonyPatch(typeof(PlayerProfile), "LoadPlayerData")] internal static class ReloadCharacterOperation { private static void Postfix() { CharacterOperationEscrow.Clear(); } } [HarmonyPatch(typeof(ZDO), "Deserialize")] internal static class PreserveManagedChestProjection { private static void Postfix(ZDO __instance) { if (!InventoryOperationNetwork.Ready) { return; } try { ManagedChestLedger.Project(__instance); ManagedWorldDrops.Project(__instance); } catch (Exception ex) { MutationGate.Report("inventory projection reconciliation: " + ex); } } } public enum MutationOutcome { RejectedWithoutChanges, Committed, RolledBack, Indeterminate } public sealed class MutationLease : IDisposable { internal readonly object[] State; private HashSet<object> _endpoints => (HashSet<object>)State[4]; public string Id => (string)State[0]; public string Purpose => (string)State[1]; public MutationOutcome Outcome { get { return (MutationOutcome)(int)State[2]; } private set { State[2] = (int)value; } } public bool Active { get { return (bool)State[3]; } private set { State[3] = value; } } internal MutationLease(string purpose) { State = new object[5] { Guid.NewGuid().ToString("N"), purpose, 0, true, new HashSet<object>() }; } internal MutationLease(object[] state) { State = state; } public void Track(object endpoint) { if (!Active || MutationGate.Current?.State != State) { throw new InvalidOperationException("Inactive mutation lease."); } if (endpoint != null) { _endpoints.Add(endpoint); } } public void Complete(MutationOutcome outcome) { if (!Active || Outcome == MutationOutcome.Indeterminate) { return; } Outcome = outcome; if (outcome == MutationOutcome.Indeterminate) { foreach (object endpoint in _endpoints) { MutationGate.Block(endpoint); } } List<string> list = new List<string>(); foreach (object endpoint2 in _endpoints) { if (list.Count < 128) { list.Add(endpoint2.ToString()); } } MutationGate.Report(Id + " " + Purpose + " " + outcome.ToString() + " endpoints=[" + string.Join(",", list) + "]"); } public void Dispose() { lock (MutationGate.Sync) { if (Active) { Active = false; if (MutationGate.Current?.State == State) { MutationGate.Current = null; } } } } } public static class MutationGate { internal static object Sync => SharedState.Sync; private static object[] State => SharedState.Get("mutation", () => new object[4] { null, new HashSet<object>(), false, null }); private static HashSet<object> Blocked => (HashSet<object>)State[1]; private static bool _overflow { get { return (bool)State[2]; } set { State[2] = value; } } public static MutationLease Current { get { if (!(State[0] is object[] state)) { return null; } return new MutationLease(state); } internal set { State[0] = value?.State; } } public static Action<string> Diagnostic { get { return (Action<string>)State[3]; } set { State[3] = value; } } public static bool TryBegin(string purpose, out MutationLease lease) { lock (Sync) { lease = null; if (Current != null || _overflow) { return false; } Current = (lease = new MutationLease(purpose)); return true; } } public static bool IsBlocked(object endpoint) { lock (Sync) { return _overflow || (endpoint != null && Blocked.Contains(endpoint)); } } public static void Block(object endpoint) { lock (Sync) { if (endpoint != null) { if (Blocked.Count >= 16384) { _overflow = true; } else { Blocked.Add(endpoint); } } } } internal static void Report(string text) { try { Diagnostic?.Invoke(text); } catch { } } public static void EndSession() { lock (Sync) { Current?.Dispose(); Blocked.Clear(); _overflow = false; } } } public sealed class MutationIndeterminateException : InvalidOperationException { public MutationIndeterminateException(string detail, Exception inner = null) : base("Inventory recovery needs inspection: " + detail, inner) { } } internal static class ServerSnapshotBarrier { private sealed class Check { public string Nonce; public string Digest; public int OwnerRevision; public uint DataRevision; public float Retry; public float AcceptedUntil; } private const string RequestRpc = "RunicAutomation_SnapshotCheck_v2"; private const string ReplyRpc = "RunicAutomation_SnapshotAck_v2"; private static readonly Dictionary<ZDOID, Check> Checks = new Dictionary<ZDOID, Check>(); private static readonly Dictionary<string, float> Requests = new Dictionary<string, float>(); private static ZRoutedRpc Registered; private static int Frame = -1; private static int Sent; internal static void Register() { if (ZRoutedRpc.instance != null && Registered != ZRoutedRpc.instance) { Registered = ZRoutedRpc.instance; Registered.Register<ZPackage>("RunicAutomation_SnapshotCheck_v2", (Action<long, ZPackage>)ReceiveRequest); Registered.Register<ZPackage>("RunicAutomation_SnapshotAck_v2", (Action<long, ZPackage>)ReceiveReply); } } internal static void Clear() { Checks.Clear(); Requests.Clear(); Registered = null; Frame = -1; Sent = 0; } internal static void Remove(ZDOID id) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) Checks.Remove(id); } private static string Digest(ZDO zdo) { byte[] byteArray = zdo.GetByteArray(ZDOVars.s_items, (byte[])null); if (byteArray == null || byteArray.Length == 0) { return null; } using SHA256 sHA = SHA256.Create(); return Convert.ToBase64String(sHA.ComputeHash(byteArray)); } internal unsafe static bool Ready(Container chest, string nonce, Func<Container, bool> refresh) { //IL_0067: 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_0255: Unknown result type (might be due to invalid IL or missing references) //IL_028e: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_016e: 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) Register(); ZNet instance = ZNet.instance; ZNetView val = ContainerAuthority.View(chest); ZDO val2 = ((val != null) ? val.GetZDO() : null); if ((Object)(object)instance == (Object)null || instance.IsServer() || Registered == null || instance.GetServerPeer() == null || val2 == null || !val.IsOwner() || !ContainerAuthority.Writable(chest)) { return false; } string text = Digest(val2); if (text == null) { return false; } if (!Checks.TryGetValue(val2.m_uid, out var value)) { if (Checks.Count >= 16384) { return false; } value = (Checks[val2.m_uid] = new Check()); } float realtimeSinceStartup = Time.realtimeSinceStartup; ZDOID uid; if (value.Nonce == nonce && value.OwnerRevision == val2.OwnerRevision && value.DataRevision == val2.DataRevision && value.Digest == text && realtimeSinceStartup < value.AcceptedUntil) { if (!refresh(chest)) { return false; } if (!val.IsValid() || !val.IsOwner() || val2 != val.GetZDO() || !ContainerAuthority.Writable(chest) || value.OwnerRevision != val2.OwnerRevision || value.DataRevision != val2.DataRevision || value.Digest != Digest(val2)) { return false; } string[] obj = new string[6] { "authority server-snapshot-confirmed; endpoint=", null, null, null, null, null }; uid = val2.m_uid; obj[1] = ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(); obj[2] = "; ownerRevision="; obj[3] = val2.OwnerRevision.ToString(); obj[4] = "; dataRevision="; obj[5] = val2.DataRevision.ToString(); MutationGate.Report(string.Concat(obj)); Checks.Remove(val2.m_uid); return true; } if (realtimeSinceStartup < value.Retry) { return false; } if (Frame != Time.frameCount) { Frame = Time.frameCount; Sent = 0; } if (Sent >= 32) { return false; } Sent++; value.Nonce = nonce; value.OwnerRevision = val2.OwnerRevision; value.DataRevision = val2.DataRevision; value.Digest = text; value.AcceptedUntil = 0f; value.Retry = realtimeSinceStartup + 3f; ZPackage val3 = Pack(val2.m_uid, value); Registered.InvokeRoutedRPC(instance.GetServerPeer().m_uid, "RunicAutomation_SnapshotCheck_v2", new object[1] { val3 }); uid = val2.m_uid; MutationGate.Report("authority server-snapshot-requested; endpoint=" + ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString()); return false; } private static ZPackage Pack(ZDOID id, Check check) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0024: 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_003d: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(id); val.Write(check.Nonce); val.Write(check.OwnerRevision); val.Write(check.DataRevision); val.Write(check.Digest); return val; } private static bool Read(ZPackage package, out ZDOID id, out Check check) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) id = ZDOID.None; check = null; if (package == null || package.Size() > 256) { return false; } id = package.ReadZDOID(); string text = package.ReadString(); int num = package.ReadInt(); uint dataRevision = package.ReadUInt(); string text2 = package.ReadString(); if (((ZDOID)(ref id)).IsNone() || text.Length != 32 || !Guid.TryParseExact(text, "N", out var result) || result == Guid.Empty || num < 0 || num > 65535 || text2.Length != 44) { return false; } check = new Check { Nonce = text, OwnerRevision = num, DataRevision = dataRevision, Digest = text2 }; return true; } private unsafe static void ReceiveRequest(long sender, ZPackage package) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: 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) try { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || sender == 0L || ZNet.instance.GetPeer(sender) == null || MutationGate.Current != null || !Read(package, out var id, out var check)) { return; } string text = sender.ToString(); ZDOID val = id; string key = text + ":" + ((object)(*(ZDOID*)(&val))/*cast due to .constrained prefix*/).ToString(); float realtimeSinceStartup = Time.realtimeSinceStartup; if (Requests.TryGetValue(key, out var value) && realtimeSinceStartup < value) { return; } if (Requests.Count >= 16384) { List<string> list = new List<string>(); foreach (KeyValuePair<string, float> request in Requests) { if (realtimeSinceStartup >= request.Value) { list.Add(request.Key); } } foreach (string item in list) { Requests.Remove(item); } if (Requests.Count >= 16384) { return; } } Requests[key] = realtimeSinceStartup + 2f; ZDOMan instance = ZDOMan.instance; ZDO val2 = ((instance != null) ? instance.GetZDO(id) : null); if (val2 == null || val2.GetOwner() != sender || val2.OwnerRevision != check.OwnerRevision || val2.GetInt(ZDOVars.s_inUse, 0) != 0 || MutationGate.IsBlocked(id)) { return; } val = id; if (!MutationGate.IsBlocked("valheim.zdo:" + ((object)(*(ZDOID*)(&val))/*cast due to .constrained prefix*/).ToString())) { if (val2.DataRevision != check.DataRevision || Digest(val2) != check.Digest) { ZDOMan.instance.ForceSendZDO(sender, id); return; } Registered.InvokeRoutedRPC(sender, "RunicAutomation_SnapshotAck_v2", new object[1] { Pack(id, check) }); } } catch (Exception ex) { MutationGate.Report("snapshot check deferred: " + ex.Message); } } private static void ReceiveReply(long sender, ZPackage package) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) try { if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer()) { ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer != null && serverPeer.m_uid == sender && Read(package, out var id, out var check) && Checks.TryGetValue(id, out var value) && !(value.Nonce != check.Nonce) && value.OwnerRevision == check.OwnerRevision && value.DataRevision == check.DataRevision && !(value.Digest != check.Digest)) { value.AcceptedUntil = Time.realtimeSinceStartup + 3f; } } } catch (Exception ex) { MutationGate.Report("snapshot confirmation deferred: " + ex.Message); } } } internal static class SharedState { private const string Key = "Runic.InventorySafety.Embedded.v1"; internal static readonly object Sync = string.Intern("Runic.InventorySafety.Embedded.v1"); internal static bool InventoryOperationsActive { get { if (TryGet<Dictionary<string, Delegate>>("operations.network.v2", out var value) && value.TryGetValue("ready", out var value2)) { return ((Func<bool>)value2)(); } return false; } } internal static bool TryGet<T>(string name, out T value) where T : class { lock (Sync) { value = null; if (!(AppDomain.CurrentDomain.GetData("Runic.InventorySafety.Embedded.v1") is Dictionary<string, object> dictionary) || !dictionary.TryGetValue(name, out var value2)) { return false; } value = value2 as T; return value != null; } } internal static T Get<T>(string name, Func<T> create) where T : class { lock (Sync) { Dictionary<string, object> dictionary = AppDomain.CurrentDomain.GetData("Runic.InventorySafety.Embedded.v1") as Dictionary<string, object>; if (dictionary == null) { dictionary = new Dictionary<string, object>(); AppDomain.CurrentDomain.SetData("Runic.InventorySafety.Embedded.v1", dictionary); } if (!dictionary.TryGetValue(name, out var value)) { value = (dictionary[name] = create()); } return (T)value; } } } } namespace RunicAutomation.Operations { internal static class CharacterCheckpointPresentation { private sealed class Scope : IDisposable { private Action close; internal Scope(Action close) { this.close = close; } public void Dispose() { Action action = close; close = null; action?.Invoke(); } } private const string VaultHarmonyId = "chazman.RunicInventoryCheckpointPresentation.Vault"; private static FieldInfo vaultPending; private static MethodInfo vaultUpload; private static readonly FieldInfo PlayerData = AccessTools.Field(typeof(PlayerProfile), "m_playerData"); private static bool vaultAckValidated; private static readonly MethodInfo NativeSave = AccessTools.Method(typeof(Game), "SavePlayerProfile", new Type[2] { typeof(bool), typeof(bool) }, (Type[])null); private static Dictionary<string, Delegate> Routes => SharedState.Get("operations.checkpoint-presentation.v1", MakeRoutes); internal static bool Active => ((Func<bool>)Routes["active"])(); internal static IDisposable Enter() { EnsureVaultCompatibility(); return EnterScope(silent: true); } private static IDisposable EnterScope(bool silent) { return ((Func<bool, IDisposable>)Routes["enter"])(silent); } internal static void EnsureVaultCompatibility() { ((Action)Routes["vault-install"])(); } internal static void RecordVaultUpload(object service, string before, string after, bool silent) { ((Action<object, string, string, bool>)Routes["vault-record"])(service, before, after, silent); } internal static void ClearVaultOrigin(object service) { ((Action<object>)Routes["vault-clear"])(service); } internal static bool IsVaultFollowUpSilent(object service, string pending) { return ((Func<object, string, bool>)Routes["vault-silent"])(service, pending); } internal static void Verified(PlayerProfile profile, byte[] playerData) { try { ((Action<PlayerProfile, byte[]>)Routes["verified"])(profile, playerData); } catch (Exception ex) { Warn("verified profile proof unavailable: " + ex.GetType().Name); } } private static Dictionary<string, Delegate> MakeRoutes() { ThreadLocal<LinkedList<bool>> scopes = new ThreadLocal<LinkedList<bool>>(() => new LinkedList<bool>()); ConditionalWeakTable<object, StrongBox<string>> origins = new ConditionalWeakTable<object, StrongBox<string>>(); ConditionalWeakTable<PlayerProfile, Tuple<string, int, long, string, byte[]>> verified = new ConditionalWeakTable<PlayerProfile, Tuple<string, int, long, string, byte[]>>(); bool installed = false; bool installing = false; bool failed = false; StrongBox<string> value; return new Dictionary<string, Delegate> { ["active"] = (Func<bool>)(() => scopes.Value.Last?.Value ?? false), ["verified"] = (Action<PlayerProfile, byte[]>)delegate(PlayerProfile profile, byte[] data) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected I4, but got Unknown if (profile != null) { verified.Remove(profile); if (data != null) { verified.Add(profile, Tuple.Create(profile.GetFilename(), (int)profile.m_fileSource, profile.GetPlayerID(), profile.GetName(), (byte[])data.Clone())); } } }, ["verified-data"] = (Func<PlayerProfile, byte[]>)((PlayerProfile profile) => (profile == null || !verified.TryGetValue(profile, out value) || !(value.Item1 == profile.GetFilename()) || value.Item2 != (int)profile.m_fileSource || value.Item3 != profile.GetPlayerID() || !(value.Item4 == profile.GetName())) ? null : value.Item5), ["enter"] = (Func<bool, IDisposable>)delegate(bool silent) { LinkedList<bool> list = scopes.Value; LinkedListNode<bool> entry = list.AddLast(silent); return new Scope(delegate { if (entry.List != null) { list.Remove(entry); } }); }, ["vault-record"] = (Action<object, string, string, bool>)delegate(object service, string before, string after, bool silent) { if (service != null) { bool flag = before != null && origins.TryGetValue(service, out value) && value.Value == before; origins.Remove(service); if (silent && after != null && (before == null || (before == after && flag))) { origins.Add(service, new StrongBox<string>(after)); } } }, ["vault-clear"] = (Action<object>)delegate(object service) { if (service != null) { origins.Remove(service); } }, ["vault-silent"] = (Func<object, string, bool>)((object service, string pending) => service != null && pending != null && origins.TryGetValue(service, out value) && value.Value == pending), ["vault-install"] = (Action)delegate { //IL_0093: Unknown result type (might be due to invalid IL or missing references) lock (SharedState.Sync) { if (!(installed || installing || failed)) { Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetName().Name == "RunicCharacterVault"); if (!(assembly == null)) { installing = true; try { InstallVault(assembly.GetType("RunicCharacterVault.ProfileTransferService", throwOnError: true)); installed = true; return; } catch (Exception ex) { failed = true; try { new Harmony("chazman.RunicInventoryCheckpointPresentation.Vault").UnpatchSelf(); } catch { } Warn("optional Character Vault queued-save compatibility unavailable: " + ex.GetType().Name); return; } finally { installing = false; } } } } } }; } private static void InstallVault(Type service) { //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Expected O, but got Unknown vaultPending = AccessTools.Field(service, "_pendingRequest"); MethodInfo methodInfo = (vaultUpload = AccessTools.Method(service, "UploadSavedProfile", new Type[1] { typeof(PlayerProfile) }, (Type[])null)); MethodInfo methodInfo2 = AccessTools.Method(service, "ReceiveSaveAck", new Type[2] { typeof(ZRpc), typeof(string) }, (Type[])null); MethodInfo[] array = new string[4] { "BeginFinalDisconnectSave", "ReceiveSaveRequest", "ResetClientState", "SaveManualClientProfile" }.Select((string name) => AccessTools.Method(service, name, (Type[])null, (Type[])null)).ToArray(); if (vaultPending?.FieldType != typeof(string) || methodInfo == null || methodInfo2 == null || NativeSave == null || array.Any((MethodInfo method) => method == null)) { throw new MissingMemberException("Character Vault save boundaries changed"); } Harmony val = new Harmony("chazman.RunicInventoryCheckpointPresentation.Vault"); vaultAckValidated = false; val.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, Patch("VaultAckTranspiler"), (HarmonyMethod)null, (HarmonyMethod)null); if (!vaultAckValidated) { throw new MissingMethodException("Character Vault queued-save call changed"); } val.Patch((MethodBase)methodInfo, Patch("VaultUploadPrefix"), Patch("VaultUploadPostfix"), (HarmonyMethod)null, Patch("VaultUploadFinalizer"), (HarmonyMethod)null); MethodInfo[] array2 = array; foreach (MethodInfo methodInfo3 in array2) { val.Patch((MethodBase)methodInfo3, Patch("VaultVisiblePrefix"), (HarmonyMethod)null, (HarmonyMethod)null, Patch("VaultVisibleFinalizer"), (HarmonyMethod)null); } static HarmonyMethod Patch(string name) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown return new HarmonyMethod(AccessTools.Method(typeof(CharacterCheckpointPresentation), name, (Type[])null, (Type[])null)); } } private static void Warn(string message) { try { Debug.LogWarning((object)("Runic checkpoint presentation: " + message)); } catch { } } private static string Pending(object service) { return (string)vaultPending.GetValue(service); } private static void VaultUploadPrefix(object __instance, out Tuple<string, bool> __state) { __state = null; try { __state = Tuple.Create(Pending(__instance), Active); } catch (Exception ex) { ClearVaultOrigin(__instance); Warn("upload origin capture failed: " + ex.GetType().Name); } } private static void VaultUploadPostfix(object __instance, Tuple<string, bool> __state) { try { if (__state == null) { ClearVaultOrigin(__instance); } else { RecordVaultUpload(__instance, __state.Item1, Pending(__instance), __state.Item2); } } catch (Exception ex) { ClearVaultOrigin(__instance); Warn("upload origin update failed: " + ex.GetType().Name); } } private static Exception VaultUploadFinalizer(object __instance, Exception __exception) { if (__exception != null) { ClearVaultOrigin(__instance); } return __exception; } private static void VaultVisiblePrefix(object __instance, out IDisposable __state) { __state = null; try { ClearVaultOrigin(__instance); __state = EnterScope(silent: false); } catch (Exception ex) { Warn("visible save scope failed: " + ex.GetType().Name); } } private static Exception VaultVisibleFinalizer(IDisposable __state, Exception __exception) { __state?.Dispose(); return __exception; } private static IEnumerable<CodeInstruction> VaultAckTranspiler(IEnumerable<CodeInstruction> instructions) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown List<CodeInstruction> list = instructions.ToList(); if (list.Count((CodeInstruction instruction) => CodeInstructionExtensions.Calls(instruction, NativeSave)) != 1) { return list; } List<CodeInstruction> list2 = new List<CodeInstruction>(list.Count + 1); foreach (CodeInstruction item in list) { if (CodeInstructionExtensions.Calls(item, NativeSave)) { item.opcode = OpCodes.Ldarg_0; item.operand = null; list2.Add(item); list2.Add(new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(CharacterCheckpointPresentation), "SaveVaultFollowUp", (Type[])null, (Type[])null))); } else { list2.Add(item); } } vaultAckValidated = true; return list2; } private static void SaveVaultFollowUp(Game game, bool setLogoutPoint, bool saveEvent, object service) { bool flag = false; try { flag = IsVaultFollowUpSilent(service, Pending(service)); } catch (Exception ex) { ClearVaultOrigin(service); Warn("queued save origin lookup failed: " + ex.GetType().Name); } using (EnterScope(flag)) { PlayerProfile val = ((game != null) ? game.GetPlayerProfile() : null); if (flag && vaultUpload != null && ReusableVerifiedProfile(val)) { try { vaultUpload.Invoke(service, new object[1] { val }); return; } catch (TargetInvocationException ex2) { ExceptionDispatchInfo.Capture(ex2.InnerException ?? ex2).Throw(); return; } } game.SavePlayerProfile(setLogoutPoint, saveEvent); } } private static bool ReusableVerifiedProfile(PlayerProfile profile) { //IL_0041: 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: Expected O, but got Unknown try { byte[] array = ((Func<PlayerProfile, byte[]>)Routes["verified-data"])(profile); if (array == null || !OwnerOperationQueue.Same(array, (byte[])PlayerData.GetValue(profile))) { return false; } PlayerProfile val = new PlayerProfile(profile.GetFilename(), profile.m_fileSource); return val.Load() && val.GetPlayerID() == profile.GetPlayerID() && val.GetName() == profile.GetName() && OwnerOperationQueue.Same(array, (byte[])PlayerData.GetValue(val)); } catch { return false; } } } [HarmonyPatch(typeof(Hud), "ProfileSaveStarted")] internal static class SilentCharacterCheckpointHudPatch { private static bool Prefix() { return !CharacterCheckpointPresentation.Active; } } internal static class CharacterOperationCheckpoint { private static readonly FieldInfo PlayerData = AccessTools.Field(typeof(PlayerProfile), "m_playerData"); private const string ReceiptKey = "runic.operations.character.v2"; internal static string Receipt(Player player) { if (!player.m_customData.TryGetValue("runic.operations.character.v2", out var value)) { return ""; } return value; } internal static void SetReceipt(Player player, string value) { if (value == null || value.Length > 2097152) { throw new InvalidDataException("Invalid character operation receipt"); } if (value.Length == 0) { player.m_customData.Remove("runic.operations.character.v2"); } else { player.m_customData["runic.operations.character.v2"] = value; } } internal static bool SaveAndVerify(Player player, out string error) { //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Expected O, but got Unknown error = ""; try { if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || (Object)(object)Game.instance == (Object)null || SaveSystem.HasSessionFlag((SaveSystemSessionFlags)1)) { error = "character-save-unavailable"; return false; } PlayerProfile playerProfile = Game.instance.GetPlayerProfile(); if (playerProfile == null || string.IsNullOrEmpty(playerProfile.GetFilename())) { error = "character-profile-unavailable"; return false; } playerProfile.SavePlayerData(player); byte[] array = (byte[])PlayerData.GetValue(playerProfile); bool flag; using (CharacterCheckpointPresentation.Enter()) { flag = array != null && playerProfile.Save(); } if (!flag) { error = "character-save-failed"; return false; } PlayerProfile val = new PlayerProfile(playerProfile.GetFilename(), playerProfile.m_fileSource); if (!val.Load() || !OwnerOperationQueue.Same(array, (byte[])PlayerData.GetValue(val))) { error = "character-save-readback-mismatch"; return false; } CharacterCheckpointPresentation.Verified(playerProfile, array); return true; } catch (Exception ex) { error = "character-save-error:" + ex.GetType().Name; return false; } } } internal static class CharacterOperationEscrow { private enum Stage { Prepared, Returning, Applied } private sealed class Receipt { internal string World; internal string Id; internal string Kind; internal string Reason; internal byte[] Request; internal byte[] Escrow; internal byte[] Delivery; internal Vector2i[] Origins; internal Stage Stage; internal bool Accepted; internal bool Processed; internal string Encode() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_00c1: 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) //IL_00c8: 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) ZPackage val = new ZPackage(); val.Write(4); val.Write(World); val.Write(Id); val.Write(Kind); val.Write((int)Stage); val.Write(Accepted); val.Write(Reason ?? ""); val.Write(Processed); val.Write(Request); val.Write(Escrow); val.Write(Delivery ?? Array.Empty<byte>()); Vector2i[] origins = Origins; val.Write((origins != null) ? origins.Length : (-1)); if (Origins != null) { Vector2i[] origins2 = Origins; foreach (Vector2i val2 in origins2) { val.Write(val2.x); val.Write(val2.y); } } return Convert.ToBase64String(val.GetArray()); } internal static Receipt Decode(string value) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_013d: 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_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) if (value.Length > 2097152) { throw new InvalidDataException("Character receipt too large"); } ZPackage val = new ZPackage(Convert.FromBase64String(value)); int num = val.ReadInt(); if (num < 2 || num > 4) { throw new InvalidDataException("Unsupported character receipt"); } Receipt receipt = new Receipt { World = val.ReadString(), Id = val.ReadString(), Kind = val.ReadString(), Stage = (Stage)val.ReadInt(), Accepted = val.ReadBool(), Reason = val.ReadString(), Processed = (num >= 3 && val.ReadBool()), Request = OperationPacket.Bytes(val), Escrow = OperationPacket.Bytes(val), Delivery = OperationPacket.Bytes(val) }; if (num >= 4) { int num2 = val.ReadInt(); if (num2 < -1 || num2 > 65535) { throw new InvalidDataException("Invalid character origin count"); } if (num2 >= 0) { receipt.Origins = (Vector2i[])(object)new Vector2i[num2]; HashSet<Vector2i> hashSet = new HashSet<Vector2i>(); Vector2i val2 = default(Vector2i); for (int i = 0; i < num2; i++) { ((Vector2i)(ref val2))..ctor(val.ReadInt(), val.ReadInt()); if (val2.x < 0 || val2.y < 0 || val2.x > 255 || val2.y > 255 || !hashSet.Add(val2)) { throw new InvalidDataException("Invalid character origin slot"); } receipt.Origins[i] = val2; } } } OperationPacket.End(val); if (!Enum.IsDefined(typeof(Stage), receipt.Stage) || !Guid.TryParseExact(receipt.Id, "N", out var _) || receipt.World.Length > 128 || receipt.Kind.Length > 128 || receipt.Reason.Length > 4096 || receipt.Request.Length > 65536 || receipt.Escrow.Length > 65536 || receipt.Delivery.Length > 65536) { throw new InvalidDataException("Invalid character receipt"); } return receipt; } } private sealed class Session { private const string PendingKey = "runic.operations.deliveries.v1"; private Player player; private Receipt receipt; private Receipt lastCompleted; private readonly List<Receipt> pending = new List<Receipt>(); private readonly Dictionary<string, Action<bool, string>> pendingCallbacks = new Dictionary<string, Action<bool, string>>(); private string status = ""; private bool finished; private bool corrupt; private float retry; private Action<bool, string> completed; private readonly Dictionary<string, Func<Player, byte[], Inventory, bool>> processors = new Dictionary<string, Func<Player, byte[], Inventory, bool>>(StringComparer.Ordinal); internal Dictionary<string, Delegate> Routes() { return new Dictionary<string, Delegate> { ["busy"] = (Func<bool>)delegate { Load(); return corrupt || (receipt != null && !finished); }, ["status"] = (Func<string>)(() => status), ["completed-counts"] = new Func<Tuple<int, int>>(CompletedItemCounts), ["tick"] = new Action(Tick), ["wake-delivery"] = (Func<string, byte[], bool>)delegate(string kind, byte[] request) { Load(); if ((Object)(object)player == (Object)null || receipt == null || corrupt || finished || receipt.Stage != Stage.Returning || receipt.Processed || receipt.Kind != kind || request == null || !OwnerOperationQueue.Same(receipt.Request, request)) { return false; } retry = 0f; return true; }, ["clear"] = (Action)delegate { player = null; receipt = null; lastCompleted = null; pending.Clear(); pendingCallbacks.Clear(); completed = null; finished = false; corrupt = false; retry = 0f; status = ""; }, ["begin"] = new Func<string, Func<byte[], byte[]>, Tuple<ItemData, int>[], Action<bool, string>, bool>(Begin), ["processor"] = (Action<string, Func<Player, byte[], Inventory, bool>>)delegate(string kind, Func<Player, byte[], Inventory, bool> process) { processors[kind] = process; } }; } private Tuple<int, int> CompletedItemCounts() { try { Load(); Receipt receipt = lastCompleted ?? (finished ? this.receipt : null); if ((Object)(object)player == (Object)null || corrupt || receipt == null || receipt.Stage != Stage.Applied) { return null; } return Tuple.Create(NativeInventoryCodec.Load(receipt.Escrow, 32, 32).GetAllItems().Sum((ItemData x) => x.m_stack), NativeInventoryCodec.Load(receipt.Delivery, 32, 32).GetAllItems().Sum((ItemData x) => x.m_stack)); } catch { return null; } } private void Load() { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got Unknown if (player == Player.m_localPlayer) { return; } player = Player.m_localPlayer; receipt = null; lastCompleted = null; pending.Clear(); pendingCallbacks.Clear(); completed = null; finished = false; corrupt = false; retry = 0f; if ((Object)(object)player == (Object)null) { return; } string text = CharacterOperationCheckpoint.Receipt(player); try { if (player.m_customData.TryGetValue("runic.operations.deliveries.v1", out var value)) { ZPackage val = new ZPackage(Convert.FromBase64String(value)); if (val.ReadInt() != 1) { throw new InvalidDataException("Unsupported pending deliveries"); } int num = val.ReadInt(); if (num < 0 || num > 4096) { throw new InvalidDataException("Invalid pending delivery count"); } for (int i = 0; i < num; i++) { Receipt held = Receipt.Decode(val.ReadString()); if (held.Stage != Stage.Returning || pending.Any((Receipt p) => p.Id == held.Id)) { throw new InvalidDataException("Invalid pending delivery"); } pending.Add(held); } OperationPacket.End(val); } if (text.Length != 0) { receipt = Receipt.Decode(text); finished = receipt.Stage == Stage.Applied; status = (finished ? "" : "Recovering pending inventory operation"); } } catch (Exception ex) { corrupt = true; status = "Character operation receipt needs recovery"; MutationGate.Report(status + ": " + ex); } } private bool Begin(string kind, Func<byte[], byte[]> buildPayload, Tuple<ItemData, int>[] items, Action<bool, string> callback) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0166: 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_0196: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) Load(); if ((Object)(object)player == (Object)null || ((Character)player).IsDead() || corrupt || (this.receipt != null && !finished) || !InventoryOperationNetwork.Ready || buildPayload == null || items == null || items.Length > 65535) { return false; } Inventory inventory = ((Humanoid)player).GetInventory(); Inventory val = new Inventory("Runic pending transfer", (Sprite)null, 32, 32); HashSet<ItemData> hashSet = new HashSet<ItemData>(); Vector2i[] array = (Vector2i[])(object)new Vector2i[items.Length]; HashSet<Vector2i> hashSet2 = new HashSet<Vector2i>(); for (int i = 0; i < items.Length; i++) { ItemData val2 = items[i]?.Item1; int num = items[i]?.Item2 ?? 0; if (val2 == null || val2.m_equipped || num < 1 || num > val2.m_stack || !inventory.ContainsItem(val2) || !hashSet.Add(val2)) { return false; } if (val2.m_gridPos.x < 0 || val2.m_gridPos.y < 0 || val2.m_gridPos.x >= inventory.GetWidth() || val2.m_gridPos.y >= inventory.GetHeight() || val2.m_gridPos.x > 255 || val2.m_gridPos.y > 255 || !hashSet2.Add(val2.m_gridPos)) { return false; } array[i] = val2.m_gridPos; ItemData val3 = val2.Clone(); val3.m_stack = num; val3.m_equipped = false; val3.m_gridPos = new Vector2i(i % 32, i / 32); val.GetAllItems().Add(val3); } byte[] array2 = NativeInventoryCodec.Save(val); if (array2.Length > 65536) { return false; } byte[] array3 = buildPayload(array2); if (array3 == null || array3.Length > 65536) { return false; } Receipt receipt = new Receipt { World = ZNet.instance.GetWorldUID().ToString(CultureInfo.InvariantCulture), Id = Guid.NewGuid().ToString("N"), Kind = kind, Request = array3, Escrow = array2, Origins = array, Stage = Stage.Prepared, Reason = "" }; string value = receipt.Encode(); foreach (Tuple<ItemData, int> tuple in items) { ItemData item = tuple.Item1; item.m_stack -= tuple.Item2; if (tuple.Item1.m_stack == 0) { inventory.GetAllItems().Remove(tuple.Item1); } } this.receipt = receipt; completed = callback; finished = false; retry = 0f; CharacterOperationCheckpoint.SetReceipt(player, value); Notify(inventory); Tick(); return true; } private void Tick() { Load(); if ((Object)(object)player == (Object)null || corrupt || Time.realtimeSinceStartup < retry) { return; } if (((Character)player).IsDead()) { status = "Pending inventory delivery will resume after respawn"; return; } if (receipt == null || finished) { DrainPending(); return; } retry = Time.realtimeSinceStartup + 2f; if (receipt.Stage == Stage.Applied) { Finish(); } else if ((Object)(object)ZNet.instance == (Object)null || receipt.World != ZNet.instance.GetWorldUID().ToString(CultureInfo.InvariantCulture)) { status = "Return to the original world to reconcile the pending transfer"; } else if (receipt.Stage == Stage.Prepared) { status = "Waiting for inventory operation"; string expectedId = receipt.Id; InventoryOperationNetwork.Send(receipt.World, receipt.Id, receipt.Kind, receipt.Request, delegate(int code, string reason, byte[] response) { Receive(expectedId, code, reason, response); }); } else { Deliver(); } } private void Receive(string id, int code, string reason, byte[] payload) { if ((Object)(object)player == (Object)null || receipt == null || receipt.Id != id || receipt.Stage != Stage.Prepared) { return; } if (code != 2 && code != 3) { status = "Inventory operation requires reconciliation: " + reason; corrupt = true; return; } byte[] array = ((code == 2) ? payload : receipt.Escrow); try { NativeInventoryCodec.Load(array, 32, 32); } catch (Exception ex) { status = "Inventory delivery codec is incompatible"; MutationGate.Report(status + ": " + ex); return; } receipt.Accepted = code == 2; receipt.Reason = reason; receipt.Delivery = array; receipt.Stage = Stage.Returning; CharacterOperationCheckpoint.SetReceipt(player, receipt.Encode()); retry = 0f; } private void Deliver() { Inventory val = NativeInventoryCodec.Load(receipt.Delivery, 32, 32); Inventory inventory = ((Humanoid)player).GetInventory(); bool flag = false; if (receipt.Accepted && !receipt.Processed && processors.TryGetValue(receipt.Kind, out var value)) { NativeInventoryMemoryCheckpoint nativeInventoryMemoryCheckpoint = new NativeInventoryMemoryCheckpoint(inventory); Action onChanged = inventory.m_onChanged; inventory.m_onChanged = null; try { if (!value(player, receipt.Request, val)) { nativeInventoryMemoryCheckpoint.Restore(); status = "Waiting to complete the pending action"; return; } receipt.Delivery = NativeInventoryCodec.Save(val); receipt.Processed = true; flag = true; CharacterOperationCheckpoint.SetReceipt(player, receipt.Encode()); } catch (Exception ex) { try { nativeInventoryMemoryCheckpoint.Restore(); } catch (Exception ex2) { MutationGate.Report("Action rollback failed: " + ex2); } corrupt = true; status = "Pending action requires reconciliation"; MutationGate.Report(status + ": " + ex); return; } finally { inventory.m_onChanged = onChanged; } } if (!AddDelivery(NativeInventoryCodec.Clone(inventory), val, receipt)) { ParkPending(); if (flag) { Notify(inventory); } return; } NativeInventoryMemoryCheckpoint nativeInventoryMemoryCheckpoint2 = new NativeInventoryMemoryCheckpoint(inventory); Action onChanged2 = inventory.m_onChanged; inventory.m_onChanged = null; bool flag2 = false; try { if (!AddDelivery(inventory, val, receipt)) { nativeInventoryMemoryCheckpoint2.Restore(); flag2 = true; } else { receipt.Stage = Stage.Applied; CharacterOperationCheckpoint.SetReceipt(player, receipt.Encode()); } } catch (Exception ex3) { try { nativeInventoryMemoryCheckpoint2.Restore(); } catch (Exception ex4) { MutationGate.Report("Inventory delivery rollback failed: " + ex4); } corrupt = true; status = "Inventory delivery requires reconciliation"; MutationGate.Report(status + ": " + ex3); return; } finally { inventory.m_onChanged = onChanged2; } if (flag2) { ParkPending(); if (flag) { Notify(inventory); } } else { Notify(inventory); retry = 0f; Finish(); } } private void ParkPending() { pending.Add(receipt); if (completed != null) { Receipt held = receipt; byte[] originalDelivery = receipt.Delivery; Action<bool, string> callback = completed; pendingCallbacks[receipt.Id] = delegate(bool accepted, string reason) { held.Delivery = originalDelivery; callback(accepted, reason); }; } PersistPending(); CharacterOperationCheckpoint.SetReceipt(player, ""); receipt = null; completed = null; finished = false; retry = 0f; status = "Items are waiting for inventory space. You can deposit other items to make room."; ((Character)player).Message((MessageType)1, status, 0, (Sprite)null, false); } private static bool AddDelivery(Inventory destination, Inventory delivery, Receipt pending) { //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: 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_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) if (pending.Origins == null || pending.Origins.Length == 0) { return NativeInventoryCodec.AddAll(destination, delivery); } Inventory val = NativeInventoryCodec.Load(pending.Escrow, 32, 32); Inventory val2 = NativeInventoryCodec.Clone(delivery); ItemData[] array = val2.GetAllItems().ToArray(); foreach (ItemData val3 in array) { int num = val3.m_gridPos.y * 32 + val3.m_gridPos.x; if (val3.m_gridPos.x >= 32 || num < 0 || num >= pending.Origins.Length) { continue; } ItemData itemAt = val.GetItemAt(val3.m_gridPos.x, val3.m_gridPos.y); if (itemAt == null || NativeInventoryCodec.ItemIdentity(itemAt) != NativeInventoryCodec.ItemIdentity(val3)) { continue; } Vector2i val4 = pending.Origins[num]; if (val4.x < 0 || val4.y < 0 || val4.x >= destination.GetWidth() || val4.y >= destination.GetHeight()) { continue; } ItemData itemAt2 = destination.GetItemAt(val4.x, val4.y); int num2 = Math.Min(val3.m_stack, itemAt.m_stack); if (itemAt2 == null) { ItemData val5 = val3.Clone(); val5.m_stack = num2; val5.m_equipped = false; val5.m_gridPos = val4; destination.GetAllItems().Add(val5); } else { if (NativeInventoryCodec.ItemIdentity(itemAt2) != NativeInventoryCodec.ItemIdentity(val3)) { continue; } num2 = Math.Min(num2, Math.Max(0, itemAt2.m_shared.m_maxStackSize - itemAt2.m_stack)); if (num2 == 0) { continue; } itemAt2.m_stack += num2; } val3.m_stack -= num2; if (val3.m_stack == 0) { val2.GetAllItems().Remove(val3); } } return NativeInventoryCodec.AddAll(destination, val2); } private void Finish() { lastCompleted = receipt; finished = true; status = ""; Action<bool, string> action = completed; completed = null; action?.Invoke(receipt.Accepted, receipt.Reason); } private void PersistPending() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown if (pending.Count == 0) { player.m_customData.Remove("runic.operations.deliveries.v1"); return; } ZPackage val = new ZPackage(); val.Write(1); val.Write(pending.Count); foreach (Receipt item in pending) { val.Write(item.Encode()); } player.m_customData["runic.operations.deliveries.v1"] = Convert.ToBase64String(val.GetArray()); } private void DrainPending() { retry = Time.realtimeSinceStartup + 0.1f; if (pending.Count == 0 || (Object)(object)ZNet.instance == (Object)null) { return; } string world = ZNet.instance.GetWorldUID().ToString(CultureInfo.InvariantCulture); Inventory inventory = ((Humanoid)player).GetInventory(); bool flag = false; List<Action> list = new List<Action>(); Action onChanged = inventory.m_onChanged; inventory.m_onChanged = null; try { Receipt[] array = pending.Where((Receipt p) => p.World == world).ToArray(); foreach (Receipt held in array) { Inventory val = NativeInventoryCodec.Load(held.Delivery, 32, 32); bool flag2 = false; ItemData[] array2 = val.GetAllItems().ToArray(); foreach (ItemData val2 in array2) { int num3 = Math.Min(val2.m_stack, NativeInventoryCodec.CapacityFor(inventory, val2)); if (num3 == 0) { continue; } NativeInventoryMemoryCheckpoint nativeInventoryMemoryCheckpoint = new NativeInventoryMemoryCheckpoint(inventory); ItemData val3 = val2.Clone(); val3.m_stack = num3; val3.m_equipped = false; try { if (!NativeInventoryCodec.AddExact(inventory, val3)) { nativeInventoryMemoryCheckpoint.Restore(); continue; } } catch { nativeInventoryMemoryCheckpoint.Restore(); throw; } val2.m_stack -= num3; if (val2.m_stack == 0) { val.GetAllItems().Remove(val2); } held.Delivery = NativeInventoryCodec.Save(val); PersistPending(); flag2 = (flag = true); } if (!flag2 && val.NrOfItems() != 0) { continue; } if (val.NrOfItems() == 0) { pending.Remove(held); held.Stage = Stage.Applied; if (pendingCallbacks.TryGetValue(held.Id, out var callback)) { pendingCallbacks.Remove(held.Id); list.Add(delegate { lastCompleted = held; callback(held.Accepted, held.Reason); }); } } else { held.Delivery = NativeInventoryCodec.Save(val); } PersistPending(); } } finally { inventory.m_onChanged = onChanged; } if (flag) { Notify(inventory); } if (pending.Count == 0) { status = ""; } foreach (Action item in list) { item(); } } private static void Notify(Inventory inventory) { try { AccessTools.Method(typeof(Inventory), "Changed", new Type[2] { typeof(bool), typeof(bool) }, (Type[])null).Invoke(inventory, new object[2] { false, false }); } catch (Exception ex) { MutationGate.Report("inventory callback after operation: " + ex.GetType().Name); } } } private static Dictionary<string, Delegate> Routes => SharedState.Get("operations.character.v2", () => new Session().Routes()); internal static bool Busy => ((Func<bool>)Routes["busy"])(); internal static string Status => ((Func<string>)Routes["status"])(); internal static Tuple<int, int> CompletedItemCounts => ((Func<Tuple<int, int>>)Routes["completed-counts"])(); internal static void Tick() { ((Action)Routes["tick"])(); } internal static bool WakeDelivery(string kind, byte[] request) { return ((Func<string, byte[], bool>)Routes["wake-delivery"])(kind, request); } internal static void Clear() { ((Action)Routes["clear"])(); } internal static void RegisterDeliveryProcessor(string kind, Func<Player, byte[], Inventory, bool> process) { ((Action<string, Func<Player, byte[], Inventory, bool>>)Routes["processor"])(kind, process); } internal static bool Begin(string kind, Func<byte[], byte[]> payload, Tuple<ItemData, int>[] items, Action<bool, string> completed) { return ((Func<string, Func<byte[], byte[]>, Tuple<ItemData, int>[], Action<bool, string>, bool>)Routes["begin"])(kind, payload, items, completed); } } internal sealed class FileOperationEndpoints : IOperationEndpoints { private readonly string directory; private readonly string world; private readonly Dictionary<string, byte[]> cache = new Dictionary<string, byte[]>(); internal Action<string, byte[]> Changed = delegate { }; internal Func<string, bool> NativeEndpoint = (string _) => false; internal Func<string, byte[]> NativeRead = (string _) => (byte[])null; public FileOperationEndpoints(string directory, string world) { this.directory = Path.GetFullPath(directory); this.world = world ?? throw new ArgumentNullException("world"); Directory.CreateDirectory(this.directory); } public byte[] Import(string endpoint, byte[] inventory) { byte[] array = Read(endpoint); if (array != null) { return array; } byte[] array2 = Image(world, endpoint, 0L, inventory); FileOperationJournal.DurableReplace(PathFor(endpoint), array2); cache[endpoint] = array2; return (byte[])array2.Clone(); } public byte[] Read(string endpoint) { if (NativeEndpoint(endpoint)) { return NativeRead(endpoint); } return ReadStored(endpoint); } internal byte[] ReadStored(string endpoint) { if (cache.TryGetValue(endpoint, out var value)) { return (byte[])value.Clone(); } string text = PathFor(endpoint); if (!File.Exists(text)) { return null; } if (new FileInfo(text).Length > 4194304) { throw new InvalidDataException("Endpoint too large"); } byte[] array = File.ReadAllBytes(text); Parse(array, out var text2, out var endpoint2, out var _, out var _); if (world != text2 || endpoint != endpoint2) { throw new InvalidDataException("Endpoint identity mismatch"); } cache[endpoint] = array; return (byte[])array.Clone(); } public bool CompareExchange(string endpoint, byte[] expected, byte[] replacement) { if (!OwnerOperationQueue.Same(Read(endpoint), expected)) { return false; } Parse(expected, out var text, out var endpoint2, out var revision, out var inventory); Parse(replacement, out var text2, out var endpoint3, out var revision2, out inventory); if (text != world || text2 != world || endpoint2 != endpoint || endpoint3 != endpoint || revision == long.MaxValue || revision2 != revision + 1) { throw new InvalidDataException("Invalid endpoint revision or identity"); } FileOperationJournal.DurableReplace(PathFor(endpoint), replacement); cache[endpoint] = (byte[])replacement.Clone(); Changed(endpoint, (byte[])replacement.Clone()); return true; } internal Tuple<string, byte[]>[] Enumerate(string prefix) { List<Tuple<string, byte[]>> list = new List<Tuple<string, byte[]>>(); foreach (string item in Directory.EnumerateFiles(directory, "*.inventory")) { if (new FileInfo(item).Length > 4194304) { throw new InvalidDataException("Endpoint too large"); } byte[] array = File.ReadAllBytes(item); Parse(array, out var text, out var endpoint, out var _, out var _); if (text != world || !string.Equals(item, PathFor(endpoint), StringComparison.OrdinalIgnoreCase)) { throw new InvalidDataException("Endpoint identity mismatch"); } if (endpoint.StartsWith(prefix, StringComparison.Ordinal)) { cache[endpoint] = array; list.Add(Tuple.Create(endpoint, (byte[])array.Clone())); } } return list.ToArray(); } public static byte[] Next(byte[] image, byte[] inventory) { Parse(image, out var text, out var endpoint, out var revision, out var _); return Image(text, endpoint, checked(revision + 1), inventory); } internal static byte[] Transient(string world, string endpoint, long revision, byte[] inventory) { return Image(world, endpoint, revision, inventory); } public static byte[] Inventory(byte[] image) { Parse(image, out var _, out var _, out var _, out var inventory); return inventory; } public static long Revision(byte[] image) { Parse(image, out var _, out var _, out var revision, out var _); return revision; } private string PathFor(string endpoint) { if (string.IsNullOrWhiteSpace(endpoint) || endpoint.Length > 256) { throw new ArgumentException("Invalid endpoint"); } return Path.Combine(directory, OperationRequest.Hash(world + "\n" + endpoint) + ".inventory"); } private static byte[] Image(string world, string endpoint, long revision, byte[] inventory) { if (inventory == null || inventory.Length > 4190208) { throw new ArgumentException("Invalid inventory size"); } using MemoryStream memoryStream = new MemoryStream(); using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true)) { binaryWriter.Write(1); binaryWriter.Write(world); binaryWriter.Write(endpoint); binaryWriter.Write(revision); binaryWriter.Write(inventory.Length); binaryWriter.Write(inventory); } using (SHA256 sHA = SHA256.Create()) { byte[] array = sHA.ComputeHash(memoryStream.ToArray()); memoryStream.Write(array, 0, array.Length); } return memoryStream.ToArray(); } private static void Parse(byte[] image, out string world, out string endpoint, out long revision, out byte[] inventory) { if (image == null || image.Length < 48 || image.Length > 4194304) { throw new InvalidDataException("Invalid endpoint image"); } using (SHA256 sHA = SHA256.Create()) { if (!sHA.ComputeHash(image, 0, image.Length - 32).SequenceEqual(image.Skip(image.Length - 32))) { throw new InvalidDataException("Endpoint checksum mismatch"); } } using MemoryStream memoryStream = new MemoryStream(image, 0, image.Length - 32, writable: false); using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8); if (binaryReader.ReadInt32() != 1) { throw new InvalidDataException("Unsupported endpoint schema"); } world = binaryReader.ReadString(); endpoint = binaryReader.ReadString(); revision = binaryReader.ReadInt64(); if (world.Length > 256 || endpoint.Length > 256 || revision < 0) { throw new InvalidDataException("Invalid endpoint identity"); } int num = binaryReader.ReadInt32(); if (num < 0 || num != memoryStream.Length - memoryStream.Position) { throw new InvalidDataException("Invalid endpoint payload"); } inventory = binaryReader.ReadBytes(num); } } internal sealed class FileOperationJournal : IOperationJournal, ICompactOperationJournal, IDisposable { private const int Schema = 2; private const int MaximumFile = 67108864; private readonly string directory; private readonly string world; private readonly FileStream ownership; private readonly Dictionary<string, OperationRecord> records = new Dictionary<string, OperationRecord>(); public FileOperationJournal(string directory, string world) { this.directory = Path.GetFullPath(directory); this.world = world; Directory.CreateDirectory(this.directory); ownership = new FileStream(Path.Combine(this.directory, "authority.lock"), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); try { foreach (string item in Directory.EnumerateFiles(this.directory, "*.operation")) { OperationRecord operationRecord = Decode(ReadBounded(item)); if (operationRecord.Request.World != world || Path.GetFileNameWithoutExtension(item) != operationRecord.Request.Key) { throw new InvalidDataException("Operation journal identity mismatch"); } records.Add(operationRecord.Request.Key, operationRecord); } } catch { ownership.Dispose(); throw; } } public OperationRecord Find(string key) { if (!records.TryGetValue(key, out var value)) { return null; } return value; } public IEnumerable<OperationRecord> Pending() { return records.Values.Where((OperationRecord x) => x.Result.Status == OperationStatus.RecoveryPending).ToArray(); } public void ForgetTerminal(string key) { if (records.TryGetValue(key, out var value)) { if (value.Result.Status == OperationStatus.RecoveryPending) { throw new InvalidOperationException("Cannot discard a recovery record"); } File.Delete(Path.Combine(directory, key + ".operation")); records.Remove(key); } } public void Save(OperationRecord record) { if (record.Request.World != world) { throw new InvalidOperationException("Wrong world"); } if (records.TryGetValue(record.Request.Key, out var value)) { if (value.Request.Fingerprint != record.Request.Fingerprint) { throw new InvalidOperationException("Reused request id"); } if (value.Result.Status != OperationStatus.RecoveryPending) { if (!OwnerOperationQueue.Same(Encode(value), Encode(record))) { throw new InvalidOperationException("Terminal receipt is immutable"); } return; } } DurableReplace(Path.Combine(directory, record.Request.Key + ".operation"), Encode(record)); records[record.Request.Key] = record; } internal static void DurableReplace(string destination, byte[] data) { string text = destination + "." + Guid.NewGuid().ToString("N") + ".pending"; try { using (FileStream fileStream = new FileStream(text, FileMode.CreateNew, FileAccess.Write, FileShare.None, 4096, FileOptions.None)) { fileStream.Write(data, 0, data.Length); } if (File.Exists(destination)) { File.Replace(text, destination, null); } else { File.Move(text, destination); } } finally { if (File.Exists(text)) { File.Delete(text); } } } private static byte[] ReadBounded(string path) { if (new FileInfo(path).Length > 67108864) { throw new InvalidDataException("Journal record too large"); } return File.ReadAllBytes(path); } private static byte[] Encode(OperationRecord record) { using MemoryStream memoryStream = new MemoryStream(); using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true)) { binaryWriter.Write(2); binaryWriter.Write(record.Plan.Native); binaryWriter.Write(record.Request.World); binaryWriter.Write(record.Request.Actor); binaryWriter.Write(record.Request.Id); binaryWriter.Write(record.Request.Kind); Write(binaryWriter, record.Request.Payload); binaryWriter.Write((int)record.Result.Status); binaryWriter.Write(record.Result.Reason); Write(binaryWriter, record.Result.Payload); Write(binaryWriter, record.Plan.Response); binaryWriter.Write(record.Plan.Changes.Count); foreach (EndpointChange change in record.Plan.Changes) { binaryWriter.Write(change.Endpoint); Write(binaryWriter, change.Before); Write(binaryWriter, change.After); } } if (memoryStream.Length > 67108832) { throw new InvalidDataException("Journal record too large"); } using (SHA256 sHA = SHA256.Create()) { byte[] array = sHA.ComputeHash(memoryStream.ToArray()); memoryStream.Write(array, 0, array.Length); } return memoryStream.ToArray(); } private static OperationRecord Decode(byte[] data) { if (data.Length < 36) { throw new InvalidDataException("Truncated journal"); } using (SHA256 sHA = SHA256.Create()) { if (!sHA.ComputeHash(data, 0, data.Length - 32).SequenceEqual(data.Skip(data.Length - 32))) { throw new InvalidDataException("Journal checksum mismatch"); } } using MemoryStream memoryStream = new MemoryStream(data, 0, data.Length - 32, writable: false); using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8); bool native = binaryReader.ReadInt32() switch { 2 => binaryReader.ReadBoolean(), 1 => false, _ => throw new InvalidDataException("Unsupported journal schema"), }; OperationRequest request = new OperationRequest(binaryReader.ReadString(), binaryReader.ReadString(), binaryReader.ReadString(), binaryReader.ReadString(), Read(binaryReader, 65536)); OperationStatus operationStatus = (OperationStatus)binaryReader.ReadInt32(); if (operationStatus != OperationStatus.Committed && operationStatus != OperationStatus.Denied && operationStatus != OperationStatus.RecoveryPending) { throw new InvalidDataException("Invalid receipt status"); } string text = binaryReader.ReadString(); if (text.Length > 4096) { throw new InvalidDataException("Invalid receipt reason"); } OperationResult result = new OperationResult(operationStatus, text, Read(binaryReader, 65536)); byte[] response = Read(binaryReader, 65536); int num = binaryReader.ReadInt32(); if (num < 0 || num > 128) { throw new InvalidDataException("Invalid endpoint count"); } List<EndpointChange> list = new List<EndpointChange>(); for (int i = 0; i < num; i++) { list.Add(new EndpointChange(binaryReader.ReadString(), Read(binaryReader, 4194304), Read(binaryReader, 4194304))); } if (memoryStream.Position != memoryStream.Length) { throw new InvalidDataException("Trailing journal data"); } return new OperationRecord(request, new OperationPlan(list, response, native), result); } private static void Write(BinaryWriter writer, byte[] bytes) { writer.Write(bytes.Length); writer.Write(bytes); } private static byte[] Read(BinaryReader reader, int maximum) { int num = reader.ReadInt32(); if (num < 0 || num > maximum) { throw new InvalidDataException("Invalid payload length"); } byte[] array = reader.ReadBytes(num); if (array.Length != num) { throw new EndOfStreamException(); } return array; } public void Dispose() { ownership.Dispose(); } } internal static class InventoryMetadataComparison { internal static bool SameItem(ItemData left, ItemData right) { if (left != null && right != null) { return ((ReadOnlySpan<byte>)ForItem(left)).SequenceEqual((ReadOnlySpan<byte>)ForItem(right)); } return false; } internal static byte[] ForItem(ItemData item) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown ZPackage val = new ZPackage(); GameObject dropPrefab = item.m_dropPrefab; WriteItem(val, (dropPrefab != null) ? StringExtensionMethods.GetStableHashCode(((Object)dropPrefab).name) : 0, item, includeSlot: false); return val.GetArray(); } internal static byte[] ForInventory(Inventory inventory) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(109); val.Write(inventory.GetAllItems().Count); foreach (ItemData item in from i in inventory.GetAllItems() orderby i.m_gridPos.y, i.m_gridPos.x select i) { GameObject dropPrefab = item.m_dropPrefab; WriteItem(val, (dropPrefab != null) ? StringExtensionMethods.GetStableHashCode(((Object)dropPrefab).name) : 0, item, includeSlot: true); } val.Write(0); return val.GetArray(); } internal static byte[] ForPayload(byte[] bytes) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown ZPackage val = new ZPackage(bytes); int num = val.ReadInt(); if (num != 109) { return (byte[])bytes.Clone(); } int num2 = val.ReadUShort(); List<(int, ItemData)> list = new List<(int, ItemData)>(); for (int i = 0; i < num2; i++) { list.Add(ItemData.Load(val, (Item)num)); } ZPackage val2 = new ZPackage(); val2.Write(num); val2.Write(num2); foreach (var item in from tuple in list orderby tuple.itemData.m_gridPos.y, tuple.itemData.m_gridPos.x select tuple) { WriteItem(val2, item.Item1, item.Item2, includeSlot: true); } val2.Write(bytes.Length - val.GetPos()); for (int num3 = val.GetPos(); num3 < bytes.Length; num3++) { val2.Write(bytes[num3]); } return val2.GetArray(); } private static void WriteItem(ZPackage key, int prefab, ItemData item, bool includeSlot) { key.Write(prefab); if (includeSlot) { key.Write(item.m_gridPos.x); key.Write(item.m_gridPos.y); key.Write(item.m_stack); } key.Write(item.m_quality); key.Write(item.m_variant); key.Write(item.m_worldLevel); key.Write(item.m_crafterID); key.Write((item.m_crafterID == 0L) ? "" : (item.m_crafterName ?? "")); key.Write(item.m_cheated); key.Write(item.m_customData?.Count ?? 0); if (item.m_customData == null) { return; } foreach (KeyValuePair<string, string> item2 in item.m_customData.OrderBy<KeyValuePair<string, string>, string>((KeyValuePair<string, string> p) => p.Key, StringComparer.Ordinal)) { key.Write(item2.Key); key.Write(item2.Value); } } } internal static class InventoryOperationNetwork { private sealed class Pending { internal string World; internal string Id; internal string Kind; internal byte[] Payload; internal float Retry; internal float Created; internal float NextBusyWarning; internal Action<int, string, byte[]> Completed; } private sealed class Network { private readonly Dictionary<string, string> modules = new Dictionary<string, string>(); private readonly HashSet<long> compatible = new HashSet<long>(); private readonly Dictionary<string, Pending> pending = new Dictionary<string, Pending>(); private readonly Dictionary<long, Tuple<float, int>> rates = new Dictionary<long, Tuple<float, int>>(); private readonly Dictionary<long, Tuple<float, int>> queryRates = new Dictionary<long, Tuple<float, int>>(); private readonly Dictionary<long, float> actorWarnings = new Dictionary<long, float>(); private ZRoutedRpc registered; private bool ready; private float nextHello; private float nextCleanup; private int frame = -1; private string mismatch = ""; private bool Complete => modules.Count > 0; private string BuildStamp => OperationRequest.Hash(string.Join(";", from x in modules.OrderBy<KeyValuePair<string, string>, string>((KeyValuePair<string, string> x) => x.Key, StringComparer.Ordinal) select x.Key + "=" + x.Value)); private string World => ZNet.instance.GetWorldUID().ToString(CultureInfo.InvariantCulture); internal Dictionary<string, Delegate> Routes() { return new Dictionary<string, Delegate> { ["module"] = (Action<string, string>)delegate(string module, string build) { modules[module] = build; }, ["tick"] = new Action(Tick), ["clear"] = new Action(Clear), ["ready"] = (Func<bool>)(() => ready && Complete), ["installed"] = (Func<bool>)(() => modules.Count > 0), ["compatible"] = (Func<long, bool>)((long peer) => peer == ZDOMan.GetSessionID() || compatible.Contains(peer)), ["send"] = new Func<string, string, string, byte[], Action<int, string, byte[]>, bool>(Send), ["cancel-query"] = new Func<string, bool>(CancelQuery) }; } private void Clear() { ready = false; frame = -1; nextHello = 0f; nextCleanup = 0f; mismatch = ""; compatible.Clear(); rates.Clear(); queryRates.Clear(); actorWarnings.Clear(); pending.Clear(); SharedOperationHost.Stop(); } private void Tick() { //IL_02ec: Unknown result type (might be due to invalid IL or missing references) //IL_02f3: Expected O, but got Unknown //IL_0401: Unknown result type (might be due to invalid IL or missing references) //IL_0408: Expected O, but got Unknown if (!Complete || (Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null) { ready = false; return; } World world = ZNet.instance.GetWorld(); if (world == null || world.m_uid == 0L) { ready = false; } else { if (Time.frameCount == frame) { return; } frame = Time.frameCount; if (registered != ZRoutedRpc.instance) { registered = ZRoutedRpc.instance; ready = false; registered.Register<ZPackage>("RunicOperations_Hello_v2", (Action<long, ZPackage>)Hello); registered.Register<ZPackage>("RunicOperations_Ready_v2", (Action<long, ZPackage>)ReadyReply); registered.Register<ZPackage>("RunicOperations_Request_v2", (Action<long, ZPackage>)Request); registered.Register<ZPackage>("RunicOperations_Result_v2", (Action<long, ZPackage>)Result); } if (ZNet.instance.IsServer()) { if (!SharedOperationHost.Active) { SharedOperationHost.Start(World, Path.Combine(Utils.GetSaveDataPath((FileSource)2), "RunicOperations", World)); } ready = true; SharedOperationHost.Tick(Time.frameCount); if (Time.realtimeSinceStartup > nextCleanup) { nextCleanup = Time.realtimeSinceStartup + 10f; HashSet<long> live = new HashSet<long>(from x in ZNet.instance.GetPeers() select x.m_uid); compatible.RemoveWhere((long x) => !live.Contains(x)); long[] array = rates.Keys.Where((long x) => !live.Contains(x)).ToArray(); foreach (long key in array) { rates.Remove(key); } array = queryRates.Keys.Where((long x) => !live.Contains(x)).ToArray(); foreach (long key2 in array) { queryRates.Remove(key2); } array = actorWarnings.Keys.Where((long x) => !live.Contains(x)).ToArray(); foreach (long key3 in array) { actorWarnings.Remove(key3); } } } else if (!ready && Time.realtimeSinceStartup >= nextHello && ZNet.instance.GetServerPeer() != null) { nextHello = Time.realtimeSinceStartup + 2f; ZPackage val = new ZPackage(); val.Write(2); val.Write(World); val.Write(BuildStamp); registered.InvokeRoutedRPC(ZNet.instance.GetServerPeer().m_uid, "RunicOperations_Hello_v2", new object[1] { val }); } if (!ready) { return; } Pending[] array2 = this.pending.Values.OrderBy((Pending x) => Priority(x.Kind)).ToArray(); foreach (Pending pending in array2) { if (this.pending.TryGetValue(pending.Id, out var value) && value == pending && !(pending.World != World) && !(Time.realtimeSinceStartup < pending.Retry)) { pending.Retry = Time.realtimeSinceStartup + ((pending.Kind == "storage.query.v2") ? 0.25f : 2f); ZPackage val2 = new ZPackage(); val2.Write(2); val2.Write(pending.World); val2.Write(pending.Id); val2.Write(pending.Kind); val2.Write(pending.Payload); long num2 = (ZNet.instance.IsServer() ? ZDOMan.GetSessionID() : (ZNet.instance.GetServerPeer()?.m_uid ?? 0)); if (num2 != 0L) { registered.InvokeRoutedRPC(num2, "RunicOperations_Request_v2", new object[1] { val2 }); } } } } } private bool Send(string world, string id, string kind, byte[] payload, Action<int, string, byte[]> completed) { if (!Guid.TryParseExact(id, "N", out var _) || kind == null || kind.Length > 128 || payload == null || payload.Length > 65536) { return false; } if (pending.TryGetValue(id, out var value)) { if (value.World != world || value.Kind != kind || !OwnerOperationQueue.Same(value.Payload, payload)) { return false; } value.Completed = completed; return true; } if (kind == "storage.query.v2" || kind == "storage.viewer.v2") { if (pending.Values.Count((Pending x) => x.Kind == kind) >= ((kind == "storage.query.v2") ? 4 : 2)) { return false; } } else { int num = pending.Values.Count((Pending x) => !ReadOnlyKind(x.Kind)); if (num >= 16 || (BackgroundKind(kind) && num >= 12)) { return false; } } pending.Add(id, new Pending { World = world, Id = id, Kind = kind, Payload = (byte[])payload.Clone(), Completed = completed, Created = Time.realtimeSinceStartup }); return true; } private bool CancelQuery(string id) { if (id == null || !pending.TryGetValue(id, out var value) || !ReadOnlyKind(value.Kind)) { return false; } return pending.Remove(id); } private void Hello(long sender, ZPackage packet) { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got Unknown try { if (Complete && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && ZNet.instance.GetPeer(sender) != null && packet.Size() <= 256) { int num = packet.ReadInt(); string text = packet.ReadString(); string text2 = packet.ReadString(); OperationPacket.End(packet); bool flag = num == 2 && text == World && text2 == BuildStamp; if (flag) { compatible.Add(sender); } else { compatible.Remove(sender); } ZPackage val = new ZPackage(); val.Write(2); val.Write(World); val.Write(flag); val.Write(BuildStamp); registered.InvokeRoutedRPC(sender, "RunicOperations_Ready_v2", new object[1] { val }); if (!flag) { Debug.Log((object)"Runic peer uses native inventory handling; optional Runic server transactions were not negotiated."); } } } catch (Exception ex) { MutationGate.Report("operation hello rejected: " + ex.GetType().Name); } } private void ReadyReply(long sender, ZPackage packet) { try { ZNet instance = ZNet.instance; if (instance != null && instance.GetServerPeer()?.m_uid == sender && packet.Size() <= 256) { int num = packet.ReadInt(); string text = packet.ReadString(); bool flag = packet.ReadBool(); string text2 = packet.ReadString(); OperationPacket.End(packet); ready = num == 2 && text == World && flag && text2 == BuildStamp; if (!ready && mismatch != text2) { mismatch = text2; Debug.Log((object)"Runic uses native inventory handling for this server; optional Runic server transactions were not negotiated."); } } } catch { ready = false; } } private void Request(long sender, ZPackage packet) { try { if (!ready || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || (sender != ZDOMan.GetSessionID() && !compatible.Contains(sender)) || packet == null || packet.Size() > 66048 || packet.ReadInt() != 2) { return; } string world = packet.ReadString(); string id = packet.ReadString(); string text = packet.ReadString(); byte[] array = OperationPacket.Bytes(packet); OperationPacket.End(packet); if (world != World || !Guid.TryParseExact(id, "N", out var _) || text.Length > 128 || array.Length > 65536) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; Dictionary<long, Tuple<float, int>> dictionary = (ReadOnlyKind(text) ? queryRates : rates); if (!dictionary.TryGetValue(sender, out var value) || realtimeSinceStartup - value.Item1 >= 1f) { value = Tuple.Create(realtimeSinceStartup, 0); } if (value.Item2 >= 32) { return; } dictionary[sender] = Tuple.Create(value.Item1, value.Item2 + 1); if (!NetworkInventoryAccess.TryActor(sender, out var actor)) { Reply(sender, world, id, 1, "actor-unavailable", Array.Empty<byte>()); if (!actorWarnings.TryGetValue(sender, out var value2) || realtimeSinceStartup >= value2) { actorWarnings[sender] = realtimeSinceStartup + 30f; Debug.LogWarning((object)$"Runic inventory request deferred: character identity unavailable for peer {sender}; request={id}. The request will retry."); } } else { Tuple<int, string, byte[]> tuple = SharedOperationHost.Submit(world, actor, id, text, array, delegate(int status, string reason, byte[] response) { Reply(sender, world, id, status, reason, response); }); if (tuple.Item1 != 0) { Reply(sender, world, id, tuple.Item1, tuple.Item2, tuple.Item3); } } } catch (Exception ex) { MutationGate.Report("operation request rejected: " + ex.GetType().Name + ": " + ex.Message); } } private void Reply(long receiver, string world, string id, int status, string reason, byte[] payload) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown if (registered != null && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { ZPackage val = new ZPackage(); val.Write(2); val.Write(world); val.Write(id); val.Write(status); val.Write(reason); val.Write(payload); registered.InvokeRoutedRPC(receiver, "RunicOperations_Result_v2", new object[1] { val }); } } private void Result(long sender, ZPackage packet) { try { if ((Object)(object)ZNet.instance == (Object)null || packet == null || packet.Size() > 70500) { return; } long num = (ZNet.instance.IsServer() ? ZDOMan.GetSessionID() : (ZNet.instance.GetServerPeer()?.m_uid ?? 0)); if (num == 0L || sender != num || packet.ReadInt() != 2) { return; } string text = packet.ReadString(); string key = packet.ReadString(); int num2 = packet.ReadInt(); string text2 = packet.ReadString(); byte[] array = OperationPacket.Bytes(packet); OperationPacket.End(packet); if (text != World || !pending.TryGetValue(key, out var value) || value.World != text || array.Length > 65536 || text2.Length > 4096 || !Enum.IsDefined(typeof(OperationStatus), num2)) { return; } switch (num2) { case 0: break; case 1: case 4: if (num2 == 1 && text2 == "container-live-state-requested" && !BackgroundKind(value.Kind)) { value.Retry = Math.Min(value.Retry, Time.realtimeSinceStartup + 0.05f); } else if (num2 == 1 && !ReadOnlyKind(value.Kind) && !BackgroundKind(value.Kind)) { value.Retry = Math.Min(value.Retry, Time.realtimeSinceStartup + 0.25f); } WarnBusy(value, text2); break; default: pending.Remove(key); value.Completed?.Invoke(num2, text2, array); break; } } catch (Exception ex) { MutationGate.Report("operation reply deferred: " + ex.GetType().Name + ": " + ex.Message); } } private void WarnBusy(Pending request, string reason) { float realtimeSinceStartup = Time.realtimeSinceStartup; float num = realtimeSinceStartup - request.Created; if (!(num < 3f) && !(realtimeSinceStartup < request.NextBusyWarning)) { request.NextBusyWarning = realtimeSinceStartup + 30f; int num2 = pending.Values.Count((Pending x) => x.Kind == "storage.query.v2"); int num3 = pending.Values.Count((Pending x) => x.Kind == "storage.viewer.v2"); Debug.LogWarning((object)("Runic inventory request is still waiting: kind=" + request.Kind + "; request=" + request.Id + "; age=" + num.ToString("F1", CultureInfo.InvariantCulture) + "s; reason=" + reason + "; " + $"pending mutations={pending.Count - num2 - num3}/16, chest queries={num2}/4, viewers={num3}/2. The same request will retry.")); } } } private const string HelloRpc = "RunicOperations_Hello_v2"; private const string ReadyRpc = "RunicOperations_Ready_v2"; private const string RequestRpc = "RunicOperations_Request_v2"; private const string ResultRpc = "RunicOperations_Result_v2"; private const string QueryKind = "storage.query.v2"; private const string ViewerKind = "storage.viewer.v2"; private static Dictionary<string, Delegate> Routes => SharedState.Get("operations.network.v2", () => new Network().Routes()); internal static bool Ready => ((Func<bool>)Routes["ready"])(); internal static bool Installed => ((Func<bool>)Routes["installed"])(); internal static void RegisterModule(string module) { ((Action<string, string>)Routes["module"])(module, Assembly.GetExecutingAssembly().ManifestModule.ModuleVersionId.ToString("N")); } internal static void Tick() { ((Action)Routes["tick"])(); } internal static void Clear() { ((Action)Routes["clear"])(); } internal static bool Compatible(long peer) { return ((Func<long, bool>)Routes["compatible"])(peer); } internal static bool Send(string world, string id, string kind, byte[] payload, Action<int, string, byte[]> completed) { return ((Func<string, string, string, byte[], Action<int, string, byte[]>, bool>)Routes["send"])(world, id, kind, payload, completed); } internal static bool CancelQuery(string id) { return ((Func<string, bool>)Routes["cancel-query"])(id); } private static bool ReadOnlyKind(string kind) { if (!(kind == "storage.query.v2")) { return kind == "storage.viewer.v2"; } return true; }