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 RepairRequiresMaterials v1.1.2
RepairRequiresMaterials.dll
Decompiled 14 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using ServerSync; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Core.ObjectPool; using YamlDotNet.Core.Tokens; using YamlDotNet.Helpers; using YamlDotNet.Serialization; using YamlDotNet.Serialization.BufferedDeserialization; using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators; using YamlDotNet.Serialization.Callbacks; using YamlDotNet.Serialization.Converters; using YamlDotNet.Serialization.EventEmitters; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; using YamlDotNet.Serialization.NodeTypeResolvers; using YamlDotNet.Serialization.ObjectFactories; using YamlDotNet.Serialization.ObjectGraphTraversalStrategies; using YamlDotNet.Serialization.ObjectGraphVisitors; using YamlDotNet.Serialization.Schemas; using YamlDotNet.Serialization.TypeInspectors; using YamlDotNet.Serialization.TypeResolvers; using YamlDotNet.Serialization.Utilities; using YamlDotNet.Serialization.ValueDeserializers; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("RepairRequiresMaterials")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("sighsorry")] [assembly: AssemblyProduct("RepairRequiresMaterials")] [assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("4358610B-F3F4-4843-B7AF-98B7BC60DCDE")] [assembly: AssemblyFileVersion("1.1.2")] [assembly: IgnoresAccessChecksTo("assembly_guiutils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.2.0")] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } } namespace RepairRequiresMaterials { internal static class AdminCommands { [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private static class ZNetRpcPeerInfoPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZRpc rpc) { RegisterPeerRpcs(__instance, rpc); } } private const string SetDurabilityCommand = "rrm_setdurability"; private const string SetDurabilityRequestRpc = "sighsorry.RepairRequiresMaterials.RequestSetDurability.v1"; private const string SetDurabilityResponseRpc = "sighsorry.RepairRequiresMaterials.SetDurabilityResponse.v1"; private const float AuthorizationTimeoutSeconds = 10f; private static bool _registered; private static int _nextRequestId; private static int _pendingRequestId; private static float _pendingPercentage; private static float _pendingRequestTime; private static ZRpc? _pendingServerRpc; private static Terminal? _pendingContext; internal static void Register() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown //IL_003e: Expected O, but got Unknown //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (!_registered) { _registered = true; new ConsoleCommand("rrm_setdurability", "<0-100> - set all durability-bearing equipment in your inventory to a percentage of its quality-adjusted maximum", new ConsoleEventFailable(SetInventoryEquipmentDurability), false, false, false, false, false, false, new ConsoleOptionsFetcher(GetDurabilityOptions), false, false, false); } } private static object SetInventoryEquipmentDurability(ConsoleEventArgs args) { float num = default(float); if (args.Length != 2 || !args.TryParameterFloat(1, ref num) || float.IsNaN(num) || float.IsInfinity(num) || num < 0f || num > 100f) { return "Usage: rrm_setdurability <0-100>"; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return "A local player is not available."; } ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { return "A network session is not available."; } if (!instance.IsServer()) { ZRpc serverRPC = instance.GetServerRPC(); if (serverRPC == null) { return "The server connection is not available."; } int num2 = (_pendingRequestId = NextRequestId()); _pendingPercentage = num; _pendingRequestTime = Time.realtimeSinceStartup; _pendingServerRpc = serverRPC; _pendingContext = args.Context; serverRPC.Invoke("sighsorry.RepairRequiresMaterials.RequestSetDurability.v1", new object[2] { num2, num }); ((MonoBehaviour)localPlayer).StartCoroutine(ExpirePendingRequest(num2)); Terminal context = args.Context; if (context != null) { context.AddString("RepairRequiresMaterials: requesting administrator authorization..."); } return true; } ApplyInventoryEquipmentDurability(localPlayer, num, args.Context); return true; } private static void ApplyInventoryEquipmentDurability(Player player, float percentage, Terminal? context) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) Inventory inventory = ((Humanoid)player).GetInventory(); int num = 0; int num2 = 0; float num3 = percentage / 100f; foreach (ItemData allItem in inventory.GetAllItems()) { if (allItem == null || !EquipmentTypeRules.IsEquipment(allItem.m_shared.m_itemType) || !allItem.m_shared.m_useDurability) { continue; } float maxDurability = allItem.GetMaxDurability(); if (maxDurability > 0f && !float.IsNaN(maxDurability) && !float.IsInfinity(maxDurability)) { num++; float num4 = Mathf.Clamp(maxDurability * num3, 0f, maxDurability); if (!allItem.m_durability.Equals(num4)) { allItem.m_durability = num4; num2++; } } } if (num2 > 0) { inventory.Changed(false, false); } string text = percentage.ToString("0.##", CultureInfo.InvariantCulture); AddCommandMessage(context, string.Format("{0}: set {1} of {2} eligible equipment items to {3}% durability.", "RepairRequiresMaterials", num2, num, text)); } private static int NextRequestId() { _nextRequestId++; if (_nextRequestId <= 0) { _nextRequestId = 1; } return _nextRequestId; } private static void RegisterPeerRpcs(ZNet zNet, ZRpc rpc) { if (zNet.IsServer()) { rpc.Register<int, float>("sighsorry.RepairRequiresMaterials.RequestSetDurability.v1", (Action<ZRpc, int, float>)delegate(ZRpc senderRpc, int requestId, float percentage) { HandleSetDurabilityRequest(zNet, senderRpc, requestId, percentage); }); return; } ZNetPeer peer = zNet.GetPeer(rpc); if (peer != null && peer.m_server) { ClearPendingRequest(); rpc.Register<int, bool, string>("sighsorry.RepairRequiresMaterials.SetDurabilityResponse.v1", (Action<ZRpc, int, bool, string>)delegate(ZRpc senderRpc, int requestId, bool approved, string message) { HandleSetDurabilityResponse(zNet, senderRpc, requestId, approved, message); }); } } private static void HandleSetDurabilityRequest(ZNet zNet, ZRpc rpc, int requestId, float percentage) { if (zNet.IsServer()) { string text = ValidateRemoteRequest(zNet, rpc, requestId, percentage); rpc.Invoke("sighsorry.RepairRequiresMaterials.SetDurabilityResponse.v1", new object[3] { requestId, text.Length == 0, text }); } } private static string ValidateRemoteRequest(ZNet zNet, ZRpc rpc, int requestId, float percentage) { if (requestId <= 0 || float.IsNaN(percentage) || float.IsInfinity(percentage) || percentage < 0f || percentage > 100f) { return "Invalid durability request."; } ZNetPeer peer = zNet.GetPeer(rpc); if (!rpc.IsConnected() || peer == null || !peer.IsReady()) { return "The requesting player is not available."; } string hostName = rpc.GetSocket().GetHostName(); if (string.IsNullOrWhiteSpace(hostName) || !zNet.IsAdmin(hostName)) { return "Administrator or host privileges are required."; } return string.Empty; } private static void HandleSetDurabilityResponse(ZNet zNet, ZRpc rpc, int requestId, bool approved, string message) { if (zNet.IsServer() || requestId != _pendingRequestId || rpc != _pendingServerRpc) { return; } float pendingPercentage = _pendingPercentage; bool num = Time.realtimeSinceStartup - _pendingRequestTime > 10f; Terminal pendingContext = _pendingContext; ClearPendingRequest(); if (num) { AddCommandMessage(pendingContext, "The administrator authorization request expired."); return; } if (!approved) { AddCommandMessage(pendingContext, string.IsNullOrWhiteSpace(message) ? "Administrator or host privileges are required." : message); return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { AddCommandMessage(pendingContext, "A local player is not available."); } else { ApplyInventoryEquipmentDurability(localPlayer, pendingPercentage, pendingContext); } } private static IEnumerator ExpirePendingRequest(int requestId) { yield return (object)new WaitForSecondsRealtime(10f); if (requestId == _pendingRequestId) { Terminal? pendingContext = _pendingContext; ClearPendingRequest(); AddCommandMessage(pendingContext, "The administrator authorization request expired."); } } private static void ClearPendingRequest() { _pendingRequestId = 0; _pendingPercentage = 0f; _pendingRequestTime = 0f; _pendingServerRpc = null; _pendingContext = null; } private static void AddCommandMessage(Terminal? context, string message) { if ((Object)(object)context != (Object)null) { context.AddString(message); return; } Console instance = Console.instance; if (instance != null) { ((Terminal)instance).AddString(message); } } private static List<string> GetDurabilityOptions() { return new List<string> { "0", "25", "50", "75", "100" }; } } internal static class ArtisanMasteryCompat { internal const string PluginGuid = "Dreanegade.Artisan_Mastery"; private const string AnvilPath = "Upgrades/Anvil/x_FurnitureAnvil"; private static MethodInfo? _playFx; private static bool _failureLogged; private static Component? _anvil; private static CraftingStation? _station; private static Player? _player; private static InventoryGui? _gui; private static string _successFx = string.Empty; private static bool _sessionOpen; private static readonly List<(GameObject Object, bool WasActive)> HiddenUi = new List<(GameObject, bool)>(); private static UIGroupHandler? _craftingGroup; private static GameObject? _defaultElement; private static GameObject? _rightStickElement; private static FieldRef<InventoryGui, float> _craftTimer = null; private static FieldRef<InventoryGui, Recipe> _craftRecipe = null; private static FieldRef<InventoryGui, Animator> _animator = null; private static FieldRef<UIGroupHandler, GameObject> _rightStickSelectable = null; private static Action<InventoryGui, List<Recipe>> _updateRecipeList = null; private static Action<InventoryGui, int, bool> _setRecipe = null; private static readonly List<Recipe> EmptyRecipes = new List<Recipe>(); internal static void Initialize(Harmony harmony) { //IL_03e9: Unknown result type (might be due to invalid IL or missing references) //IL_03ee: Unknown result type (might be due to invalid IL or missing references) //IL_0402: Expected O, but got Unknown //IL_0416: Unknown result type (might be due to invalid IL or missing references) //IL_0423: Expected O, but got Unknown if (!Chainloader.PluginInfos.TryGetValue("Dreanegade.Artisan_Mastery", out var value) || (Object)(object)value.Instance == (Object)null) { return; } Type type = ((object)value.Instance).GetType().Assembly.GetType("Artisan_Mastery.RepairAllTable"); MethodInfo methodInfo = ((type == null) ? null : AccessTools.DeclaredMethod(type, "Interact", new Type[3] { typeof(Humanoid), typeof(bool), typeof(bool) }, (Type[])null)); MethodInfo methodInfo2 = ((type == null) ? null : AccessTools.DeclaredMethod(type, "GetHoverText", Type.EmptyTypes, (Type[])null)); _playFx = ((type == null) ? null : AccessTools.DeclaredMethod(type, "PlayFxEverybody", new Type[1] { typeof(string) }, (Type[])null)); if (type == null || !typeof(Component).IsAssignableFrom(type) || methodInfo == null || methodInfo.IsStatic || methodInfo.ReturnType != typeof(bool) || methodInfo2 == null || methodInfo2.IsStatic || methodInfo2.ReturnType != typeof(string) || _playFx == null || _playFx.IsStatic || _playFx.ReturnType != typeof(void) || !HasField(type, "_nextUseTime", typeof(float)) || !HasField(type, "CooldownSeconds", typeof(float)) || !HasField(type, "RpcSuccessFx", typeof(string))) { RepairRequiresMaterialsPlugin.Log.LogWarning((object)"Artisan Mastery repair-table API was not recognized. Galleon repair compatibility could not be enabled."); return; } try { _craftTimer = AccessTools.FieldRefAccess<InventoryGui, float>("m_craftTimer"); _craftRecipe = AccessTools.FieldRefAccess<InventoryGui, Recipe>("m_craftRecipe"); _animator = AccessTools.FieldRefAccess<InventoryGui, Animator>("m_animator"); _rightStickSelectable = AccessTools.FieldRefAccess<UIGroupHandler, GameObject>("m_rightStickSelectable"); _updateRecipeList = AccessTools.MethodDelegate<Action<InventoryGui, List<Recipe>>>(AccessTools.DeclaredMethod(typeof(InventoryGui), "UpdateRecipeList", new Type[1] { typeof(List<Recipe>) }, (Type[])null), (object)null, true); _setRecipe = AccessTools.MethodDelegate<Action<InventoryGui, int, bool>>(AccessTools.DeclaredMethod(typeof(InventoryGui), "SetRecipe", new Type[2] { typeof(int), typeof(bool) }, (Type[])null), (object)null, true); PatchPrefix(harmony, typeof(InventoryGui), "Update", "ValidateSessionPrefix", Type.EmptyTypes); PatchPrefix(harmony, typeof(InventoryGui), "UpdateCraftingPanel", "RepairOnlyPanelPrefix", typeof(bool)); PatchPrefix(harmony, typeof(InventoryGui), "UpdateRecipe", "RepairOnlyPanelPrefix", typeof(Player), typeof(float)); PatchPrefix(harmony, typeof(InventoryGui), "OnCraftPressed", "BlockCraftingPrefix", Type.EmptyTypes); PatchPrefix(harmony, typeof(InventoryGui), "DoCrafting", "BlockCraftingPrefix", typeof(Player)); PatchPrefix(harmony, typeof(InventoryGui), "UpdateRecipeGamepadInput", "GamepadSelectionPrefix", Type.EmptyTypes); PatchPrefix(harmony, typeof(Player), "SetCraftingStation", "ChangeStationPrefix", typeof(CraftingStation)); PatchPrefix(harmony, typeof(Player), "AddKnownStation", "AddKnownStationPrefix", typeof(CraftingStation)); harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ArtisanMasteryCompat), "InteractPrefix", (Type[])null) { priority = 800 }, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(ArtisanMasteryCompat), "HoverPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); RepairRequiresMaterialsPlugin.Log.LogInfo((object)"Artisan Mastery galleon anvil now opens RRM's repair-only station."); } catch (Exception exception) { LogFailure("patch installation", exception); } } private static void PatchPrefix(Harmony harmony, Type type, string method, string prefix, params Type[] arguments) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.DeclaredMethod(type, method, arguments, (Type[])null) ?? throw new MissingMethodException(type.FullName, method); harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ArtisanMasteryCompat), prefix, (Type[])null) { priority = 800 }, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } internal static bool IsRepairStation(CraftingStation? station) { if (_station != null) { return station == _station; } return false; } internal static bool CanUseStation(Player player) { if (_sessionOpen && (Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)_player && (Object)(object)player == (Object)(object)Player.m_localPlayer && !((Character)player).IsDead() && !((Character)player).IsTeleporting() && !((Character)player).InCutscene() && (Object)(object)_gui != (Object)null && (Object)(object)_gui == (Object)(object)InventoryGui.instance && ((Component)_gui).gameObject.activeInHierarchy && _animator.Invoke(_gui).GetBool("visible") && (Object)(object)_anvil != (Object)null && _anvil.gameObject.activeInHierarchy && (Object)(object)_station != (Object)null && ((Component)_station).gameObject.activeInHierarchy && IsRepairStation(player.GetCurrentCraftingStation())) { return _station.InUseDistance((Humanoid)(object)player); } return false; } private static bool HasField(Type type, string name, Type fieldType) { FieldInfo fieldInfo = AccessTools.DeclaredField(type, name); if (fieldInfo != null && !fieldInfo.IsStatic) { return fieldInfo.FieldType == fieldType; } return false; } private static bool IsGalleonAnvil(Component table) { if ((Object)(object)table == (Object)null) { return false; } Ship componentInParent = table.GetComponentInParent<Ship>(true); if ((Object)(object)componentInParent != (Object)null && string.Equals(Utils.GetPrefabName(((Component)componentInParent).gameObject), "ShipGalleon_DO", StringComparison.Ordinal)) { return (Object)(object)((Component)componentInParent).transform.Find("Upgrades/Anvil/x_FurnitureAnvil") == (Object)(object)table.transform; } return false; } private static bool InteractPrefix(Component __instance, Humanoid user, bool hold, ref bool __result, ref float ____nextUseTime, float ___CooldownSeconds, string ___RpcSuccessFx) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) if (!IsGalleonAnvil(__instance)) { return true; } __result = false; if (!hold) { Player val = (Player)(object)((user is Player) ? user : null); if (val != null && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && !((Character)val).IsDead() && !((Character)val).IsTeleporting() && !((Character)val).InCutscene() && __instance.gameObject.activeInHierarchy && !(Time.time < ____nextUseTime) && !(Vector3.Distance(((Component)val).transform.position, __instance.transform.position) >= val.m_maxInteractDistance)) { ____nextUseTime = Time.time + Mathf.Max(0.1f, ___CooldownSeconds); try { OpenStation(__instance, val, ___RpcSuccessFx); } catch (Exception exception) { LogFailure("repair interaction", exception); CloseStation(hideGui: true); ((Character)val).Message((MessageType)2, RepairRequiresMaterialsLocalization.Localize("$rrm_galleon_failed"), 0, (Sprite)null, false); } return false; } } return false; } private static void OpenStation(Component anvil, Player player, string successFx) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown CloseStation(hideGui: true); InventoryGui instance = InventoryGui.instance; if (!((Object)(object)instance == (Object)null)) { instance.Hide(); _anvil = anvil; _player = player; _gui = instance; _successFx = successFx; _sessionOpen = true; GameObject val = new GameObject("RRM_GalleonRepairStation"); val.SetActive(false); val.transform.SetParent(anvil.transform, false); try { _station = val.AddComponent<CraftingStation>(); } catch { Object.Destroy((Object)(object)val); throw; } _station.m_name = "$rrm_galleon_station"; _station.m_craftRequireRoof = false; _station.m_craftRequireFire = false; _station.m_discoverRange = 0f; _station.m_rangeBuild = 0f; _station.m_useDistance = player.m_maxInteractDistance; _station.m_showBasicRecipies = false; _station.m_hasCraftTab = false; _station.m_canRepair = true; val.SetActive(true); CancelCrafting(instance); CaptureUi(instance); _updateRecipeList(instance, EmptyRecipes); _setRecipe(instance, -1, arg3: false); RepairSelectionState.Reset(); player.SetCraftingStation(_station); instance.Show((Container)null, 3); } } internal static void PlayRepairEffect(Player player) { if (!CanUseStation(player)) { return; } try { _playFx.Invoke(_anvil, new object[1] { _successFx }); } catch (Exception exception) { LogFailure("repair effect", exception); } } internal static void CloseStation(bool hideGui = false) { if (!_sessionOpen) { return; } _sessionOpen = false; InventoryGui gui = _gui; if ((Object)(object)_player != (Object)null && IsRepairStation(_player.GetCurrentCraftingStation())) { _player.SetCraftingStation((CraftingStation)null); } foreach (var (val, active) in HiddenUi) { if ((Object)(object)val != (Object)null) { val.SetActive(active); } } HiddenUi.Clear(); if ((Object)(object)_craftingGroup != (Object)null) { _craftingGroup.m_defaultElement = _defaultElement; _rightStickSelectable.Invoke(_craftingGroup) = _rightStickElement; } _craftingGroup = null; _defaultElement = null; _rightStickElement = null; if ((Object)(object)_station != (Object)null) { Object.Destroy((Object)(object)((Component)_station).gameObject); } _station = null; _anvil = null; _player = null; _gui = null; _successFx = string.Empty; RepairSelectionState.Reset(); RepairStripController.Hide(); if (hideGui && (Object)(object)gui != (Object)null) { gui.Hide(); } } private static void CaptureUi(InventoryGui gui) { Remember((Component?)(object)gui.m_tabCraft); Remember((Component?)(object)gui.m_tabUpgrade); Remember((Component?)(object)gui.m_recipeListRoot); Remember((Component?)(object)gui.m_recipeListScroll); Remember((Component?)(object)gui.m_recipeIcon); Remember((Component?)(object)gui.m_recipeName); Remember((Component?)(object)gui.m_recipeDecription); Remember((Component?)(object)gui.m_variantButton); Remember((Component?)(object)gui.m_craftButton); Remember((Component?)(object)gui.m_craftCancelButton); Remember((Component?)(object)gui.m_craftProgressPanel); Remember((Component?)(object)gui.m_qualityPanel); Remember((Component?)(object)gui.m_minStationLevelIcon); Remember((Component?)(object)gui.m_minStationLevelText); Remember((Component?)(object)gui.m_itemCraftType); Remember((Component?)(object)gui.m_upgradeItemIcon); Remember((Component?)(object)gui.m_upgradeItemDurability); Remember((Component?)(object)gui.m_upgradeItemName); Remember((Component?)(object)gui.m_upgradeItemQuality); GameObject upgradeItemQualityArrow = gui.m_upgradeItemQualityArrow; Remember((Component?)(object)((upgradeItemQualityArrow != null) ? upgradeItemQualityArrow.transform : null)); Remember((Component?)(object)gui.m_upgradeItemNextQuality); Remember((Component?)(object)gui.m_upgradeItemIndex); Remember((Component?)(object)gui.m_craftingStationIcon); Remember((Component?)(object)gui.m_craftingStationLevelRoot); GameObject[] recipeRequirementList = gui.m_recipeRequirementList; foreach (GameObject val in recipeRequirementList) { Remember((Component?)(object)(((Object)(object)val != (Object)null) ? val.transform : null)); } if (gui.m_uiGroups.Length > 3 && (Object)(object)gui.m_uiGroups[3] != (Object)null) { _craftingGroup = gui.m_uiGroups[3]; _defaultElement = _craftingGroup.m_defaultElement; _rightStickElement = _rightStickSelectable.Invoke(_craftingGroup); _craftingGroup.m_defaultElement = null; _rightStickSelectable.Invoke(_craftingGroup) = null; } } internal static void RefreshRepairFocus(InventoryGui gui) { if (_sessionOpen && (Object)(object)gui == (Object)(object)_gui && (Object)(object)_craftingGroup != (Object)null) { _craftingGroup.m_defaultElement = ((((Behaviour)gui.m_repairButton).isActiveAndEnabled && ((Selectable)gui.m_repairButton).interactable) ? ((Component)gui.m_repairButton).gameObject : null); } } private static void Remember(Component? component) { if ((Object)(object)component == (Object)null) { return; } GameObject gameObject = component.gameObject; foreach (var item in HiddenUi) { if ((Object)(object)item.Object == (Object)(object)gameObject) { return; } } HiddenUi.Add((gameObject, gameObject.activeSelf)); } private static void CancelCrafting(InventoryGui gui) { _craftTimer.Invoke(gui) = -1f; _craftRecipe.Invoke(gui) = null; gui.OnCraftPointerExit(); } private static void ValidateSessionPrefix() { if (_sessionOpen && ((Object)(object)_player == (Object)null || !CanUseStation(_player))) { CloseStation(hideGui: true); } } private static bool RepairOnlyPanelPrefix(InventoryGui __instance) { if (!_sessionOpen || (Object)(object)__instance != (Object)(object)_gui) { return true; } CancelCrafting(__instance); foreach (var item in HiddenUi) { if ((Object)(object)item.Object != (Object)null && item.Object.activeSelf) { item.Object.SetActive(false); } } __instance.m_craftingStationName.text = RepairRequiresMaterialsLocalization.Localize("$rrm_galleon_station"); return false; } private static bool BlockCraftingPrefix(InventoryGui __instance) { if (!_sessionOpen || (Object)(object)__instance != (Object)(object)_gui) { return true; } CancelCrafting(__instance); return false; } private static bool GamepadSelectionPrefix(InventoryGui __instance) { if (!_sessionOpen || (Object)(object)__instance != (Object)(object)_gui) { return true; } if ((Object)(object)_player != (Object)null && CanUseStation(_player)) { if (ZInput.GetButtonDown("JoyLStickDown") || ZInput.GetButtonDown("JoyDPadDown")) { RepairStripController.ScrollSelection(__instance, 1); } else if (ZInput.GetButtonDown("JoyLStickUp") || ZInput.GetButtonDown("JoyDPadUp")) { RepairStripController.ScrollSelection(__instance, -1); } } return false; } private static void ChangeStationPrefix(Player __instance, CraftingStation station) { if (_sessionOpen && (Object)(object)__instance == (Object)(object)_player && !IsRepairStation(station)) { CloseStation(); } } private static bool AddKnownStationPrefix(CraftingStation station) { return !IsRepairStation(station); } private static void HoverPostfix(Component __instance, ref string __result) { if (IsGalleonAnvil(__instance)) { Localization instance = Localization.instance; string text = ((instance != null) ? instance.Localize("$KEY_Use") : null) ?? "E"; if (text == "$KEY_Use") { text = "E"; } __result = "[<color=yellow><b>" + text + "</b></color>] " + RepairRequiresMaterialsLocalization.Localize("$rrm_galleon_action"); } } private static void LogFailure(string operation, Exception exception) { if (!_failureLogged) { _failureLogged = true; RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Artisan Mastery " + operation + " failed: " + exception.GetType().Name + ": " + exception.Message)); } } } internal static class ArtisanStorageCompat { private const string PendingKey = "rrm_galleon_recovery_pending"; private const string BackupKey = "rrm_galleon_recovery_original"; private static MethodInfo _cleanup = null; private static MethodInfo _follow = null; private static MethodInfo _backlink = null; private static MethodInfo _instantiate = null; private static MethodInfo _request = null; private static MethodInfo _findStorage = null; private static FieldInfo _slot = null; private static FieldInfo _link = null; private static FieldInfo _prefab = null; private static FieldInfo _visual = null; private static Action<Container> _save = null; private static readonly HashSet<ZDOID> Recovering = new HashSet<ZDOID>(); private static readonly HashSet<ZDOID> OwnershipWarnings = new HashSet<ZDOID>(); internal static void Initialize(Harmony harmony) { if (!Chainloader.PluginInfos.TryGetValue("Dreanegade.Artisan_Mastery", out var value) || (Object)(object)value.Instance == (Object)null) { return; } List<(MethodInfo Target, MethodInfo Patch)> installed = new List<(MethodInfo, MethodInfo)>(); try { ResolveContract(((object)value.Instance).GetType().Assembly); _save = AccessTools.MethodDelegate<Action<Container>>(AccessTools.DeclaredMethod(typeof(Container), "Save", Type.EmptyTypes, (Type[])null), (object)null, true); Install(_backlink, "BacklinkPrefix"); Install(_instantiate, "InstantiatePrefix"); Install(_follow, "FollowPrefix"); Install(_request, "RequestPrefix"); Install(_cleanup, "CleanupPrefix"); RepairRequiresMaterialsPlugin.Log.LogInfo((object)"Artisan Mastery hidden galleon storage protection enabled."); } catch (Exception ex) { foreach (var item in installed) { harmony.Unpatch((MethodBase)item.Target, item.Patch); } RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Artisan storage protection was not enabled: " + ex.GetBaseException().Message)); } void Install(MethodInfo target, string name) { //IL_002d: 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_0046: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ArtisanStorageCompat), name, (Type[])null, (Type[])null); installed.Add((target, methodInfo)); harmony.Patch((MethodBase)target, new HarmonyMethod(methodInfo) { priority = 800 }, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } internal static void ResolveContract(Assembly assembly) { Type type = assembly.GetType("Artisan_Mastery.ShipHiddenContainerRoot", throwOnError: true); Type type2 = assembly.GetType("Artisan_Mastery.ShipHiddenContainerSystem+StorageDef", throwOnError: true); if (!typeof(Component).IsAssignableFrom(type) || !type2.IsValueType) { throw new NotSupportedException("Unrecognized hidden storage types"); } _cleanup = Method(type, "CleanupStorage", typeof(void), typeof(ZDO), typeof(string), type2); _follow = Method(type, "UpdateLinkedStoragesPassiveFollow", typeof(void)); _backlink = Method(type, "EnsureStorageBackLink", typeof(void), type2, typeof(ZNetView), typeof(string)); _instantiate = Method(type, "InstantiateExistingStorage", typeof(GameObject), type2, typeof(ZDO), typeof(Transform), typeof(bool)); _request = Method(type, "RPC_RequestOpenHiddenContainer", typeof(void), typeof(long), typeof(int), typeof(long)); _findStorage = Method(type, "FindStorageZDOByStableLink", typeof(ZDO), typeof(string), typeof(int)); _slot = Field(type2, "Slot", typeof(int)); _link = Field(type2, "LinkKey", typeof(string)); _prefab = Field(type2, "HiddenPrefabName", typeof(string)); _visual = Field(type2, "VisualPath", typeof(string)); Field(type, "_shipNView", typeof(ZNetView)); Field(type, "_nextPassiveFollowTime", typeof(float)); Field(type, "_defs", type2.MakeArrayType()); Method(typeof(Container), "Save", typeof(void)); } private static MethodInfo Method(Type type, string name, Type result, params Type[] args) { MethodInfo methodInfo = AccessTools.DeclaredMethod(type, name, args, (Type[])null); if (methodInfo == null || methodInfo.IsStatic || methodInfo.ReturnType != result) { throw new MissingMethodException(type.FullName, name); } return methodInfo; } private static FieldInfo Field(Type type, string name, Type valueType) { FieldInfo fieldInfo = AccessTools.DeclaredField(type, name); if (fieldInfo == null || fieldInfo.IsStatic || fieldInfo.FieldType != valueType) { throw new MissingFieldException(type.FullName, name); } return fieldInfo; } private static bool IsHiddenStorage(ZDO zdo) { int prefab = zdo.GetPrefab(); if (prefab != StringExtensionMethods.GetStableHashCode("ShipContainerHiddenLarge_DO") && prefab != StringExtensionMethods.GetStableHashCode("ShipContainerHiddenMedium_DO")) { return prefab == StringExtensionMethods.GetStableHashCode("ShipContainerHiddenSmall_DO"); } return true; } private static Container? LoadedContainer(ZDO zdo) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) GameObject val = ZNetScene.instance.FindInstance(zdo.m_uid); if (!((Object)(object)val != (Object)null)) { return null; } return val.GetComponent<Container>(); } private static bool IsBusy(ZDO zdo) { Container val = LoadedContainer(zdo); if (zdo.GetInt(ZDOVars.s_inUse, 0) == 0) { if ((Object)(object)val != (Object)null) { return val.IsInUse(); } return false; } return true; } private static bool ProtectStorage(ZDO zdo) { if (IsHiddenStorage(zdo)) { if (!zdo.GetBool("rrm_galleon_recovery_pending", false)) { if (!zdo.IsOwner()) { return IsBusy(zdo); } return false; } return true; } return false; } private static bool BacklinkPrefix(ZNetView storageNView) { if ((Object)(object)storageNView == (Object)null || !storageNView.IsValid()) { return true; } return !ProtectStorage(storageNView.GetZDO()); } private static void InstantiatePrefix(ZDO storageZdo, ref bool writeZdoPosition) { if (storageZdo != null && ProtectStorage(storageZdo)) { writeZdoPosition = false; } } private static bool FollowPrefix(Component __instance, ZNetView ____shipNView, ref float ____nextPassiveFollowTime, Array ____defs) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: 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_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)____shipNView == (Object)null || !____shipNView.IsValid() || !____shipNView.IsOwner() || Time.time < ____nextPassiveFollowTime) { return false; } ____nextPassiveFollowTime = Time.time + 1f; ZDO zDO = ____shipNView.GetZDO(); foreach (object ____def in ____defs) { ZDO zDO2 = ZDOMan.instance.GetZDO(zDO.GetZDOID((string)_link.GetValue(____def))); if (zDO2 == null || !IsHiddenStorage(zDO2) || !HasExpectedLink(zDO2, zDO, zDO.GetString("galleon_container_ship_stable_id", ""), ____def)) { continue; } Transform val = __instance.transform.Find((string)_visual.GetValue(____def)); if ((Object)(object)val == (Object)null) { continue; } Vector3 val2 = val.position + Vector3.up * 0.15f; if (Vector3.Distance(zDO2.GetPosition(), val2) < 6f) { continue; } if (ProtectStorage(zDO2)) { if (OwnershipWarnings.Add(zDO2.m_uid)) { Warn(zDO2, "skipped passive following: in use by another peer or recovery pending"); } continue; } if (!zDO2.IsOwner()) { zDO2.SetOwner(ZDOMan.GetSessionID()); } zDO2.SetPosition(val2); } return false; } private static bool RequestPrefix(Component __instance, long requesterPeerId, int slot, ZNetView ____shipNView, Array ____defs) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)____shipNView == (Object)null || !____shipNView.IsValid() || !____shipNView.IsOwner()) { return true; } ZDO zDO = ____shipNView.GetZDO(); foreach (object ____def in ____defs) { if ((int)_slot.GetValue(____def) == slot) { ZDO val = ZDOMan.instance.GetZDO(zDO.GetZDOID((string)_link.GetValue(____def))); if (val == null || !val.GetBool("rrm_galleon_recovery_pending", false)) { string text = zDO.GetString("galleon_container_ship_stable_id", ""); val = (ZDO)(string.IsNullOrEmpty(text) ? null : /*isinst with value type is only supported in some contexts*/); } if (val == null || !val.GetBool("rrm_galleon_recovery_pending", false)) { return true; } Warn(val, "open refused: interrupted recovery requires administrator inspection"); ____shipNView.InvokeRPC(requesterPeerId, "HH_OpenHiddenContainerResponse", new object[3] { slot, ZDOID.None, false }); return false; } } return true; } private static bool CleanupPrefix(Component __instance, ZDO shipZdo, string stableShipId, object def) { //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004a: 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_009f: 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_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) int num = (int)_slot.GetValue(def); string text = (string)_link.GetValue(def); try { if (!shipZdo.IsOwner()) { return false; } HashSet<ZDOID> hashSet = new HashSet<ZDOID>(); ZDOID zDOID = shipZdo.GetZDOID(text); if (!((ZDOID)(ref zDOID)).IsNone()) { hashSet.Add(zDOID); } if (!string.IsNullOrEmpty(stableShipId)) { object? obj = _findStorage.Invoke(__instance, new object[2] { stableShipId, num }); ZDO val = (ZDO)((obj is ZDO) ? obj : null); if (val != null) { hashSet.Add(val.m_uid); } } bool flag = true; foreach (ZDOID item in hashSet) { ZDO zDO = ZDOMan.instance.GetZDO(item); if (zDO == null) { flag = false; RepairRequiresMaterialsPlugin.Log.LogWarning((object)$"Galleon slot={num}, storage={item}: missing storage; link retained."); } else if (!HasExpectedLink(zDO, shipZdo, stableShipId, def)) { Warn(zDO, "unexpected prefab/backlink; data preserved"); flag = false; } else { flag &= RecoverStorage(zDO, __instance.transform.position); } } if (flag) { shipZdo.RemoveZDOID(text); } } catch (Exception ex) { RepairRequiresMaterialsPlugin.Log.LogWarning((object)$"Galleon slot={num}, ship={shipZdo.m_uid}: cleanup stopped, link retained: {ex.GetBaseException().Message}"); } return false; } private static bool HasExpectedLink(ZDO storage, ZDO ship, string stableId, object def) { //IL_0069: 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) if (IsHiddenStorage(storage) && storage.GetPrefab() == StringExtensionMethods.GetStableHashCode((string)_prefab.GetValue(def)) && !string.IsNullOrEmpty(stableId) && storage.GetString("galleon_parent_ship_stable_id", "") == stableId && storage.GetInt("galleon_container_slot", -1) == (int)_slot.GetValue(def)) { return storage.GetZDOID("galleon_parent_ship") == ship.m_uid; } return false; } private static bool RecoverStorage(ZDO source, Vector3 position) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0587: 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_00cc: Expected O, but got Unknown //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Expected O, but got Unknown //IL_043e: Unknown result type (might be due to invalid IL or missing references) //IL_0445: Expected O, but got Unknown //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_03b4: Unknown result type (might be due to invalid IL or missing references) if (!Recovering.Add(source.m_uid)) { return false; } List<Container> list = new List<Container>(); byte[] array = null; byte[] array2 = null; bool flag = false; bool flag2 = false; try { if (source.GetBool("rrm_galleon_recovery_pending", false)) { throw new InvalidOperationException("interrupted recovery; not replaying it"); } if (!source.IsOwner() || IsBusy(source)) { throw new InvalidOperationException("storage is in use or owned by another peer"); } Container val = LoadedContainer(source); Container component = ZNetScene.instance.GetPrefab(source.GetPrefab()).GetComponent<Container>(); if ((Object)(object)component == (Object)null) { throw new InvalidOperationException("hidden container prefab unavailable"); } array = source.GetByteArray(ZDOVars.s_items, (byte[])null)?.Clone() as byte[]; Inventory val2 = new Inventory(component.m_name, component.m_bkg, component.m_width, component.m_height); if (array != null) { val2.Load(new ZPackage(array)); } array2 = Serialize(val2); if (array != null && !IsSupportedInventoryRoundTrip(array, array2)) { throw new InvalidOperationException("saved inventory lost data or used an unsupported format transition"); } if ((Object)(object)val != (Object)null && val.GetInventory().NrOfItems() > 0 && !BytesEqual(array2, Serialize(val.GetInventory()))) { throw new InvalidOperationException("live inventory differs from saved inventory"); } if (val2.NrOfItems() == 0) { ZDOMan.instance.DestroyZDO(source); return true; } GameObject prefab = ZNetScene.instance.GetPrefab("CargoCrate_DO"); Container val3 = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<Container>() : null); if ((Object)(object)prefab == (Object)null || (Object)(object)val3 == (Object)null || val3.m_width <= 0 || val3.m_height <= 0) { throw new InvalidOperationException("CargoCrate_DO container unavailable"); } List<byte[]> list2 = StageCrates(val2, val3); source.Set("rrm_galleon_recovery_original", array); if (!BytesEqual(array, source.GetByteArray("rrm_galleon_recovery_original", (byte[])null))) { throw new InvalidOperationException("source backup readback mismatch"); } source.Set("rrm_galleon_recovery_pending", true); flag = true; foreach (byte[] item in list2) { Container component2 = Object.Instantiate<GameObject>(prefab, position + Vector3.up * 0.5f, Quaternion.identity).GetComponent<Container>(); if ((Object)(object)component2 == (Object)null) { throw new InvalidOperationException("spawned cargo crate has no container"); } list.Add(component2); ZNetView component3 = ((Component)component2).GetComponent<ZNetView>(); if ((Object)(object)component3 == (Object)null || !component3.IsValid() || !component3.IsOwner() || component2.IsInUse() || component2.GetInventory() == null || component2.GetInventory().NrOfItems() != 0) { throw new InvalidOperationException("cargo crate is not empty and locally owned"); } WriteInventory(component2, item); } if (!source.IsOwner() || IsBusy(source) || !BytesEqual(array, source.GetByteArray(ZDOVars.s_items, (byte[])null))) { throw new InvalidOperationException("source changed during recovery"); } for (int i = 0; i < list.Count; i++) { Container obj = list[i]; ZNetView component4 = ((Component)obj).GetComponent<ZNetView>(); RequireWritable(obj); if (!BytesEqual(list2[i], component4.GetZDO().GetByteArray(ZDOVars.s_items, (byte[])null))) { throw new InvalidOperationException("cargo contents changed during recovery"); } } val2.GetAllItems().Clear(); byte[] array3 = Serialize(val2); if ((Object)(object)val != (Object)null) { WriteInventory(val, array3); } else { source.Set(ZDOVars.s_items, array3); } if (!BytesEqual(source.GetByteArray(ZDOVars.s_items, (byte[])null), array3)) { throw new InvalidOperationException("source clear could not be verified"); } flag2 = true; ZDOMan.instance.DestroyZDO(source); RepairRequiresMaterialsPlugin.Log.LogInfo((object)$"Galleon storage={source.m_uid}: recovered into {list.Count} cargo crate(s)."); return true; } catch (Exception ex) { if (flag && !flag2) { try { foreach (Container item2 in list) { ZNetView component5 = ((Component)item2).GetComponent<ZNetView>(); if ((Object)(object)component5 == (Object)null || !component5.IsValid() || !component5.IsOwner() || item2.IsInUse()) { throw new InvalidOperationException("rollback cargo is unavailable/in use"); } Inventory inventory = new Inventory("", (Sprite)null, 1, 1); WriteInventory(item2, Serialize(inventory)); } if (!source.IsOwner() || IsBusy(source)) { throw new InvalidOperationException("rollback source ownership changed"); } if (array2 == null) { throw new InvalidOperationException("rollback snapshot unavailable"); } Container val4 = LoadedContainer(source); if ((Object)(object)val4 != (Object)null) { WriteInventory(val4, array2); } else { source.Set(ZDOVars.s_items, array2); } if (!BytesEqual(array2, source.GetByteArray(ZDOVars.s_items, (byte[])null))) { throw new InvalidOperationException("rollback source verification failed"); } foreach (Container item3 in list) { ((Component)item3).GetComponent<ZNetView>().Destroy(); } source.Set("rrm_galleon_recovery_pending", false); source.RemoveByteArray(StringExtensionMethods.GetStableHashCode("rrm_galleon_recovery_original")); } catch (Exception ex2) { Warn(source, "recovery quarantined: " + ex2.GetBaseException().Message); } } Warn(source, (flag2 ? "contents recovered; source deletion failed" : "data preserved/quarantined") + ": " + ex.GetBaseException().Message); return flag2; } finally { Recovering.Remove(source.m_uid); } } private static List<byte[]> StageCrates(Inventory source, Container template) { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown int num = checked(template.m_width * template.m_height); List<byte[]> list = new List<byte[]>(); Inventory val = null; int num2 = 0; foreach (ItemData allItem in source.GetAllItems()) { if (num2 % num == 0) { if (val != null) { list.Add(Serialize(val)); } if (list.Count >= 64) { throw new InvalidOperationException("too many cargo crates required"); } val = new Inventory(template.m_name, template.m_bkg, template.m_width, template.m_height); } ItemData val2 = allItem.Clone(); int num3 = num2++ % num; val2.m_gridPos = new Vector2i(num3 % template.m_width, num3 / template.m_width); val.GetAllItems().Add(val2); } if (val != null) { list.Add(Serialize(val)); } return list; } private static void WriteInventory(Container container, byte[] payload) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown RequireWritable(container); Inventory inventory = container.GetInventory(); Action onChanged = inventory.m_onChanged; inventory.m_onChanged = null; try { inventory.Load(new ZPackage(payload)); if (!BytesEqual(payload, Serialize(inventory))) { throw new InvalidOperationException("cargo/source inventory lost data on load"); } RequireWritable(container); _save(container); if (!BytesEqual(payload, ((Component)container).GetComponent<ZNetView>().GetZDO().GetByteArray(ZDOVars.s_items, (byte[])null))) { throw new InvalidOperationException("inventory save readback mismatch"); } } finally { inventory.m_onChanged = onChanged; } } private static void RequireWritable(Container container) { ZNetView component = ((Component)container).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid() || !component.IsOwner() || IsBusy(component.GetZDO())) { throw new InvalidOperationException("container is unavailable, in use, or owned by another peer"); } } private static byte[] Serialize(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(); inventory.Save(val); return val.GetArray(); } private static bool IsSupportedInventoryRoundTrip(byte[] original, byte[] serialized) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown //IL_002e: Unknown result type (might be due to invalid IL or missing references) if (BytesEqual(original, serialized)) { return true; } if (original.Length != serialized.Length || original.Length < 6) { return false; } try { ZPackage val = new ZPackage(original); if (val.ReadInt() != 109 || new ZPackage(serialized).ReadInt() != 110) { return false; } byte[] array = (byte[])original.Clone(); Array.Copy(serialized, array, 4); int num = val.ReadUShort(); for (int i = 0; i < num; i++) { ItemData.Load(val, (Item)109); int num2 = val.GetPos() - 1; if ((original[num2] & -2) != 0) { return false; } array[num2] = 0; } return val.GetPos() == val.Size() && BytesEqual(array, serialized); } catch (Exception ex) when (ex is IOException || ex is ArgumentException || ex is OverflowException) { return false; } } private static bool BytesEqual(byte[]? first, byte[]? second) { if (first == second) { return true; } if (first == null || second == null || first.Length != second.Length) { return false; } for (int i = 0; i < first.Length; i++) { if (first[i] != second[i]) { return false; } } return true; } private static void Warn(ZDO storage, string message) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) RepairRequiresMaterialsPlugin.Log.LogWarning((object)string.Format("Galleon slot={0}, storage={1}, owner={2}: {3}", storage.GetInt("galleon_container_slot", -1), storage.m_uid, storage.GetOwner(), message)); } internal static void Shutdown() { Recovering.Clear(); OwnershipWarnings.Clear(); } } internal static class AzuCraftyBoxesCompat { private sealed class ContainerConsumption { internal object Container { get; } internal MethodInfo GetPrefabNameMethod { get; } internal MethodInfo ItemCountMethod { get; } internal MethodInfo ProcessInventoryMethod { get; } internal int Amount { get; } internal ContainerConsumption(object container, MethodInfo getPrefabNameMethod, MethodInfo itemCountMethod, MethodInfo processInventoryMethod, int amount) { Container = container; GetPrefabNameMethod = getPrefabNameMethod; ItemCountMethod = itemCountMethod; ProcessInventoryMethod = processInventoryMethod; Amount = amount; } } private sealed class RequirementConsumption { internal string ItemName { get; } internal string PrefabName { get; } internal int InventoryAmount { get; } internal List<ContainerConsumption> Containers { get; } = new List<ContainerConsumption>(); internal RequirementConsumption(string itemName, string prefabName, int inventoryAmount) { ItemName = itemName; PrefabName = prefabName; InventoryAmount = inventoryAmount; } } private const string DeferredKgDrawerTypeName = "AzuCraftyBoxes.IContainers.kgDrawer"; internal const string PluginGuid = "Azumatt.AzuCraftyBoxes"; private static bool _initialized; private static bool _available; private static bool _failureLogged; private static FieldInfo? _rangeField; private static MethodInfo? _queryFrameGetMethod; private static MethodInfo? _shouldPreventMethod; private static MethodInfo? _canItemBePulledMethod; private static MethodInfo? _checkAndDecrementMethod; internal static bool ShouldUseNearbyContainers() { try { return EnsureInitialized() && !ShouldPrevent(); } catch (Exception exception) { DisableAfterFailure("availability check", exception); return false; } } internal static bool TryCountAvailable(Player player, RepairMaterialCost cost, int currentAmount, out int totalAmount) { totalAmount = currentAmount; if (!_available) { return false; } try { Requirement sourceRequirement = cost.SourceRequirement; if ((Object)(object)sourceRequirement?.m_resItem == (Object)null) { return false; } IList nearbyContainers = GetNearbyContainers(player); if (nearbyContainers == null) { return false; } string name = sourceRequirement.m_resItem.m_itemData.m_shared.m_name; string resourcePrefabName = cost.ResourcePrefabName; foreach (object item in nearbyContainers) { if (item != null && TryGetContainerMethods(item, out MethodInfo getPrefabNameMethod, out MethodInfo itemCountMethod, out MethodInfo _) && CanPull(item, getPrefabNameMethod, resourcePrefabName)) { totalAmount += GetPullableAmount(item, itemCountMethod, name); } } return true; } catch (Exception exception) { DisableAfterFailure("container count", exception); totalAmount = currentAmount; return false; } } internal static bool TryConsume(Player player, IReadOnlyList<RepairMaterialCost> costs, out bool shouldCompleteRepair) { shouldCompleteRepair = false; if (!ShouldUseNearbyContainers()) { return false; } try { IList nearbyContainers = GetNearbyContainers(player); if (nearbyContainers == null) { return false; } Inventory inventory = ((Humanoid)player).GetInventory(); List<RequirementConsumption> list = new List<RequirementConsumption>(costs.Count); foreach (RepairMaterialCost cost in costs) { Requirement sourceRequirement = cost.SourceRequirement; if ((Object)(object)sourceRequirement?.m_resItem == (Object)null) { return false; } int num = Math.Max(0, cost.RequiredAmount); if (num <= 0) { continue; } string name = sourceRequirement.m_resItem.m_itemData.m_shared.m_name; string resourcePrefabName = cost.ResourcePrefabName; int num2 = Math.Min(num, inventory.CountItems(name, -1, true)); int num3 = num - num2; RequirementConsumption requirementConsumption = new RequirementConsumption(name, resourcePrefabName, num2); foreach (object item in nearbyContainers) { if (num3 <= 0) { break; } if (item != null && TryGetContainerMethods(item, out MethodInfo getPrefabNameMethod, out MethodInfo itemCountMethod, out MethodInfo processInventoryMethod) && CanPull(item, getPrefabNameMethod, resourcePrefabName)) { int num4 = Math.Min(num3, GetPullableAmount(item, itemCountMethod, name)); if (num4 > 0) { requirementConsumption.Containers.Add(new ContainerConsumption(item, getPrefabNameMethod, itemCountMethod, processInventoryMethod, num4)); num3 -= num4; } } } if (num3 > 0) { return false; } list.Add(requirementConsumption); } if (!ValidateConsumptionPlan(inventory, list)) { return false; } return ExecuteConsumptionPlan(inventory, list, out shouldCompleteRepair); } catch (Exception exception) { DisableAfterFailure("material consumption", exception, shouldCompleteRepair); return false; } } private static bool ValidateConsumptionPlan(Inventory inventory, IEnumerable<RequirementConsumption> plan) { foreach (RequirementConsumption item in plan) { if (inventory.CountItems(item.ItemName, -1, true) < item.InventoryAmount) { return false; } foreach (ContainerConsumption container in item.Containers) { if (!CanPull(container.Container, container.GetPrefabNameMethod, item.PrefabName) || GetPullableAmount(container.Container, container.ItemCountMethod, item.ItemName) < container.Amount) { return false; } } } return true; } private static bool ExecuteConsumptionPlan(Inventory inventory, IEnumerable<RequirementConsumption> plan, out bool shouldCompleteRepair) { shouldCompleteRepair = false; foreach (RequirementConsumption item in plan) { if (item.InventoryAmount > 0) { int num = inventory.CountItems(item.ItemName, -1, true); int num2; try { inventory.RemoveItem(item.ItemName, item.InventoryAmount, -1, true); num2 = num - inventory.CountItems(item.ItemName, -1, true); } catch (Exception exception) { try { shouldCompleteRepair |= num - inventory.CountItems(item.ItemName, -1, true) > 0; } catch { shouldCompleteRepair = true; } DisableAfterFailure("player inventory material removal", exception, shouldCompleteRepair); return false; } shouldCompleteRepair |= num2 > 0; if (num2 != item.InventoryAmount) { return DisableAfterConsumptionMismatch("player inventory changed during material removal", shouldCompleteRepair); } } foreach (ContainerConsumption container in item.Containers) { int rawContainerAmount = GetRawContainerAmount(container.Container, container.ItemCountMethod, item.ItemName); int num3; int rawContainerAmount2; try { num3 = Convert.ToInt32(container.ProcessInventoryMethod.Invoke(container.Container, new object[3] { item.ItemName, 0, container.Amount }) ?? ((object)0)); rawContainerAmount2 = GetRawContainerAmount(container.Container, container.ItemCountMethod, item.ItemName); } catch (Exception exception2) { try { shouldCompleteRepair |= rawContainerAmount - GetRawContainerAmount(container.Container, container.ItemCountMethod, item.ItemName) > 0; } catch { shouldCompleteRepair = true; } DisableAfterFailure("container material removal", exception2, shouldCompleteRepair); return false; } int num4 = rawContainerAmount - rawContainerAmount2; shouldCompleteRepair |= num4 > 0; if (num3 != container.Amount || num4 != container.Amount) { return DisableAfterConsumptionMismatch("container '" + GetContainerName(container) + "' did not remove the planned amount of " + item.ItemName, shouldCompleteRepair); } } } return true; } private static bool EnsureInitialized() { if (_initialized) { return _available; } _initialized = true; if (!Chainloader.PluginInfos.TryGetValue("Azumatt.AzuCraftyBoxes", out var value) || (Object)(object)value.Instance == (Object)null) { return false; } Assembly assembly = ((object)value.Instance).GetType().Assembly; Type type = assembly.GetType("AzuCraftyBoxes.AzuCraftyBoxesPlugin"); Type type2 = assembly.GetType("AzuCraftyBoxes.Util.Functions.Boxes"); Type type3 = assembly.GetType("AzuCraftyBoxes.Util.Functions.Boxes+QueryFrame"); Type type4 = assembly.GetType("AzuCraftyBoxes.Util.Functions.MiscFunctions"); _rangeField = type?.GetField("mRange", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _queryFrameGetMethod = type3?.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo method) => method.Name == "Get" && method.IsGenericMethodDefinition); _shouldPreventMethod = type4?.GetMethod("ShouldPrevent", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _canItemBePulledMethod = type2?.GetMethod("CanItemBePulled", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _checkAndDecrementMethod = type2?.GetMethod("CheckAndDecrement", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _available = type != null && type2 != null && type3 != null && type4 != null && _rangeField != null && _queryFrameGetMethod != null && _shouldPreventMethod != null && _canItemBePulledMethod != null && _checkAndDecrementMethod != null; if (!_available && !_failureLogged) { _failureLogged = true; RepairRequiresMaterialsPlugin.Log.LogWarning((object)"AzuCraftyBoxes was found, but its compatible container API was not available. Nearby-container repair support is disabled."); } return _available; } private static bool TryGetContainerMethods(object container, out MethodInfo getPrefabNameMethod, out MethodInfo itemCountMethod, out MethodInfo processInventoryMethod) { Type type = container.GetType(); getPrefabNameMethod = type.GetMethod("GetPrefabName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); itemCountMethod = type.GetMethod("ItemCount", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); processInventoryMethod = type.GetMethod("ProcessContainerInventory", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (getPrefabNameMethod != null && itemCountMethod != null && processInventoryMethod != null) { return SupportsVerifiedRemoval(container); } return false; } private static bool CanPull(object container, MethodInfo getPrefabNameMethod, string prefabName) { string text = Convert.ToString(getPrefabNameMethod.Invoke(container, null)) ?? string.Empty; return Convert.ToBoolean(_canItemBePulledMethod.Invoke(null, new object[3] { text, prefabName, string.Empty }) ?? ((object)false)); } private static bool SupportsVerifiedRemoval(object container) { return !string.Equals(container.GetType().FullName, "AzuCraftyBoxes.IContainers.kgDrawer", StringComparison.Ordinal); } private static int GetPullableAmount(object container, MethodInfo itemCountMethod, string itemName) { int rawContainerAmount = GetRawContainerAmount(container, itemCountMethod, itemName); return Math.Max(0, Convert.ToInt32(_checkAndDecrementMethod.Invoke(null, new object[1] { rawContainerAmount }) ?? ((object)0))); } private static int GetRawContainerAmount(object container, MethodInfo itemCountMethod, string itemName) { return Math.Max(0, Convert.ToInt32(itemCountMethod.Invoke(container, new object[1] { itemName }) ?? ((object)0))); } private static string GetContainerName(ContainerConsumption container) { return Convert.ToString(container.GetPrefabNameMethod.Invoke(container.Container, null)) ?? "unknown"; } private static bool ShouldPrevent() { return Convert.ToBoolean(_shouldPreventMethod.Invoke(null, null) ?? ((object)true)); } private static IList? GetNearbyContainers(Player player) { object value = _rangeField.GetValue(null); float num = Convert.ToSingle((value?.GetType().GetProperty("Value"))?.GetValue(value) ?? ((object)0f)); return _queryFrameGetMethod.MakeGenericMethod(typeof(Player)).Invoke(null, new object[2] { player, num }) as IList; } private static bool DisableAfterConsumptionMismatch(string reason, bool repairWillComplete) { _available = false; if (!_failureLogged) { _failureLogged = true; string text = (repairWillComplete ? "The selected item will still be repaired because materials were already consumed." : "The repair was cancelled because no material removal was confirmed."); RepairRequiresMaterialsPlugin.Log.LogWarning((object)("AzuCraftyBoxes consumption verification failed; nearby-container repair support is disabled for this session. " + text + " Reason: " + reason)); } return false; } private static void DisableAfterFailure(string operation, Exception exception, bool repairWillComplete = false) { _available = false; if (!_failureLogged) { _failureLogged = true; Exception ex = ((exception is TargetInvocationException && exception.InnerException != null) ? exception.InnerException : exception); string text = (repairWillComplete ? " The selected item will still be repaired because consumption may already have started." : string.Empty); RepairRequiresMaterialsPlugin.Log.LogWarning((object)("AzuCraftyBoxes " + operation + " failed; nearby-container repair support is disabled for this session: " + ex.Message + "." + text)); } } } [HarmonyPatch] internal static class PlayerCraftingEquipSpeedPatch { private readonly struct QueueState { internal readonly MinorActionData? ExistingAction; internal readonly ActionType ActionType; internal QueueState(MinorActionData? existingAction, ActionType actionType) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) ExistingAction = existingAction; ActionType = actionType; } } private static bool _missingTargetWarningLogged; private static IEnumerable<MethodBase> TargetMethods() { MethodBase methodBase = AccessTools.Method(typeof(Player), "QueueEquipAction", new Type[1] { typeof(ItemData) }, (Type[])null); if (methodBase != null) { yield return methodBase; } else { LogMissingTarget("QueueEquipAction"); } MethodBase methodBase2 = AccessTools.Method(typeof(Player), "QueueUnequipAction", new Type[1] { typeof(ItemData) }, (Type[])null); if (methodBase2 != null) { yield return methodBase2; } else { LogMissingTarget("QueueUnequipAction"); } } private static void Prefix(Player __instance, ItemData item, MethodBase __originalMethod, out QueueState __state) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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) ActionType actionType = (ActionType)(__originalMethod.Name == "QueueUnequipAction"); __state = new QueueState(FindAction(__instance, item, actionType), actionType); } [HarmonyPriority(0)] private static void Postfix(Player __instance, ItemData item, QueueState __state) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_0039: Unknown result type (might be due to invalid IL or missing references) if ((Object)__instance == (Object)null || __instance != Player.m_localPlayer || item == null) { return; } float value = RepairRequiresMaterialsPlugin.CraftingEquipTimeReductionAtLevel100.Value; if (value <= 0f || float.IsNaN(value)) { return; } MinorActionData val = FindAction(__instance, item, __state.ActionType); if (val != null && val != __state.ExistingAction) { val.m_duration = CalculateAdjustedDuration(val.m_duration, ((Character)__instance).GetSkillFactor((SkillType)107), value); if (val.m_duration < 1f) { val.m_startEffect = null; } } } private static MinorActionData? FindAction(Player player, ItemData? item, ActionType actionType) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (item == null) { return null; } for (int num = player.m_actionQueue.Count - 1; num >= 0; num--) { MinorActionData val = player.m_actionQueue[num]; if (val.m_type == actionType && val.m_item == item) { return val; } } return null; } private static float CalculateAdjustedDuration(float baseDuration, float skillFactor, float maximumReductionPercent) { if (float.IsNaN(baseDuration) || float.IsInfinity(baseDuration) || baseDuration <= 0f) { return 0f; } double num = ClampUnit(skillFactor); double num2 = ClampPercent(maximumReductionPercent) / 100.0; if (num <= 0.0 || num2 <= 0.0) { return baseDuration; } double num3 = Math.Max(0.0, 1.0 - num * num2); return (float)((double)baseDuration * num3); } private static double ClampUnit(float value) { if (float.IsNaN(value) || value <= 0f) { return 0.0; } if (!float.IsPositiveInfinity(value) && !(value >= 1f)) { return value; } return 1.0; } private static double ClampPercent(float value) { if (float.IsNaN(value) || value <= 0f) { return 0.0; } if (!float.IsPositiveInfinity(value) && !(value >= 100f)) { return value; } return 100.0; } private static void LogMissingTarget(string methodName) { if (!_missingTargetWarningLogged) { _missingTargetWarningLogged = true; RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Crafting equip-time reduction was not fully applied because Player." + methodName + " was not found.")); } } } [HarmonyPatch] internal static class PlayerEquipmentActionSprintPatch { private static bool _missingTargetWarningLogged; private static IEnumerable<MethodBase> TargetMethods() { MethodBase methodBase = AccessTools.DeclaredMethod(typeof(Player), "CheckRun", new Type[2] { typeof(Vector3), typeof(float) }, (Type[])null); if (methodBase != null) { yield return methodBase; } else if (!_missingTargetWarningLogged) { _missingTargetWarningLogged = true; RepairRequiresMaterialsPlugin.Log.LogWarning((object)"Equipment changes while running were not enabled because Player.CheckRun was not found."); } } [HarmonyPrefix] [HarmonyPriority(0)] private static bool Prefix(Player __instance, ref bool __result) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_0039: 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_0047: Invalid comparison between Unknown and I4 if ((Object)__instance == (Object)null || __instance != Player.m_localPlayer || !RepairRequiresMaterialsPlugin.AllowEquipmentChangesWhileRunning.Value.IsOn()) { return true; } string text = default(string); float num = default(float); MinorActionData val = default(MinorActionData); __instance.GetActionProgress(ref text, ref num, ref val); if (val == null || ((int)val.m_type != 0 && (int)val.m_type != 1)) { return true; } __result = false; return false; } } internal static class CraftingFreeRepairSystem { private enum TicketOutcome { None, Free, AnvilFree, Paid } private sealed class TicketState { internal string ItemId { get; set; } internal ulong Cycle { get; set; } internal TicketOutcome Outcome { get; set; } internal string PlanFingerprint { get; set; } internal TicketState(string itemId, ulong cycle, TicketOutcome outcome, string planFingerprint) { ItemId = itemId; Cycle = cycle; Outcome = outcome; PlanFingerprint = planFingerprint; } internal string Serialize() { char c = Outcome switch { TicketOutcome.Free => 'F', TicketOutcome.AnvilFree => 'A', TicketOutcome.Paid => 'P', _ => 'N', }; return string.Join("|", "v1", ItemId, Cycle.ToString(CultureInfo.InvariantCulture), c.ToString(), PlanFingerprint); } } private const string TicketKey = "sighsorry.RepairRequiresMaterials.SkillFreeRepairTicket"; private const string TicketSchema = "v1"; private const string RollDomain = "sighsorry.RepairRequiresMaterials.SkillFreeRepairRoll.v1"; private static readonly Dictionary<string, ulong> SessionMinimumCycles = new Dictionary<string, ulong>(StringComparer.Ordinal); internal static RepairPreview ResolvePreview(Player player, RepairPreview stationPreview) { if (stationPreview.PaymentKind != RepairPaymentKind.StationMaterials) { return stationPreview; } ItemData item = stationPreview.Item; Inventory inventory = ((Humanoid)player).GetInventory(); bool flag = HasMaterialCost(stationPreview); TicketState ticketState = ReadState(item, inventory, out var corruptState); if (corruptState) { if (!flag) { return stationPreview; } ticketState = new TicketState(Guid.NewGuid().ToString("N"), 0uL, TicketOutcome.Paid, BuildPlanFingerprint(stationPreview)); if (!WriteState(item, inventory, ticketState)) { return stationPreview; } return stationPreview.WithPayment(RepairPaymentKind.StationMaterials, ticketState.Serialize()); } if (ticketState != null) { NormalizeSessionCycle(item, inventory, ticketState); } if (ticketState != null && ticketState.Outcome == TicketOutcome.Paid) { return stationPreview.WithPayment(RepairPaymentKind.StationMaterials, ticketState.Serialize()); } bool flag2; if (ticketState != null) { TicketOutcome outcome = ticketState.Outcome; if ((uint)(outcome - 1) <= 1u) { flag2 = true; goto IL_00ae; } } flag2 = false; goto IL_00ae; IL_00ae: if (flag2) { string b = BuildPlanFingerprint(stationPreview); if (!string.Equals(ticketState.PlanFingerprint, b, StringComparison.Ordinal)) { ticketState.Outcome = TicketOutcome.Paid; if (!WriteState(item, inventory, ticketState)) { return stationPreview; } return stationPreview.WithPayment(RepairPaymentKind.StationMaterials, ticketState.Serialize()); } return stationPreview.WithPayment(GetPaymentKind(player, ticketState), ticketState.Serialize()); } if (!flag || !IsFeatureEnabled()) { return stationPreview; } if (ticketState == null) { ticketState = new TicketState(Guid.NewGuid().ToString("N"), 0uL, TicketOutcome.None, string.Empty); } double num = CalculateFreeRepairChance(((Character)player).GetSkillFactor((SkillType)107), RepairRequiresMaterialsPlugin.CraftingSkillFreeRepairChanceAtLevel0.Value, RepairRequiresMaterialsPlugin.CraftingSkillFreeRepairChanceAtLevel100.Value); double num2 = Math.Min(1.0, num + NormalizePercent(RepairRequiresMaterialsPlugin.GalleonAnvilFreeRepairBonus.Value) / 100.0); double deterministicRoll = GetDeterministicRoll(ticketState.ItemId, ticketState.Cycle); ticketState.Outcome = ((deterministicRoll < num) ? TicketOutcome.Free : ((deterministicRoll < num2) ? TicketOutcome.AnvilFree : TicketOutcome.Paid)); ticketState.PlanFingerprint = BuildPlanFingerprint(stationPreview); if (!WriteState(item, inventory, ticketState)) { return stationPreview; } return stationPreview.WithPayment(GetPaymentKind(player, ticketState), ticketState.Serialize()); } private static RepairPaymentKind GetPaymentKind(Player player, TicketState state) { if (!IsFeatureEnabled() || (state.Outcome != TicketOutcome.Free && (state.Outcome != TicketOutcome.AnvilFree || !ArtisanMasteryCompat.CanUseStation(player)))) { return RepairPaymentKind.StationMaterials; } return RepairPaymentKind.CraftingSkillFree; } internal static void CompleteSuccessfulRepair(Player player, RepairPreview preview) { if (!preview.HasRawMaterialCost || string.IsNullOrEmpty(preview.SkillFreeTicketToken)) { return; } try { if (!TryParseState(preview.SkillFreeTicketToken, out TicketState state) || state == null) { return; } ulong num = ((state.Cycle == ulong.MaxValue) ? 0 : (state.Cycle + 1)); string itemId = ((state.Cycle == ulong.MaxValue) ? Guid.NewGuid().ToString("N") : state.ItemId); if (!SessionMinimumCycles.TryGetValue(state.ItemId, out var value) || num > value) { SessionMinimumCycles[state.ItemId] = num; } ItemData item = preview.Item; Inventory inventory = ((Humanoid)player).GetInventory(); bool corruptState; TicketState ticketState = ReadState(item, inventory, out corruptState); if (ticketState != null && (!string.Equals(ticketState.ItemId, state.ItemId, StringComparison.Ordinal) || ticketState.Cycle != state.Cycle)) { RepairRequiresMaterialsPlugin.Log.LogWarning((object)"A skill-free repair ticket changed during a completed repair; the newer item state was preserved."); return; } TicketState state2 = new TicketState(itemId, num, TicketOutcome.None, string.Empty); if (!WriteState(item, inventory, state2)) { RepairRequiresMaterialsPlugin.Log.LogWarning((object)"A completed skill-free repair ticket could not be persisted; its cycle remains advanced for this session."); } } catch (Exception ex) { RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Could not complete the skill-free repair ticket: " + ex.GetType().Name + ": " + ex.Message)); } } private static bool IsFeatureEnabled() { return RepairRequiresMaterialsPlugin.EnableCraftingSkillFreeRepairs.Value.IsOn(); } internal static double CalculateFreeRepairChance(float skillFactor, float chanceAtLevel0Percent, float chanceAtLevel100Percent) { double num = NormalizePercent(chanceAtLevel100Percent) / 100.0; double num2 = Math.Min(NormalizePercent(chanceAtLevel0Percent) / 100.0, num); double num3 = NormalizeSkillFactor(skillFactor); return num2 + (num - num2) * num3; } private static double NormalizeSkillFactor(float value) { if (float.IsNaN(value) || value <= 0f) { return 0.0; } if (!float.IsPositiveInfinity(value) && !(value >= 1f)) { return value; } return 1.0; } private static double NormalizePercent(float value) { if (float.IsNaN(value) || value <= 0f) { return 0.0; } if (!float.IsPositiveInfinity(value) && !(value >= 100f)) { return value; } return 100.0; } private static bool HasMaterialCost(RepairPreview preview) { return preview.Costs.Any((RepairMaterialCost cost) => cost.RequiredAmount > 0); } private static TicketState? ReadState(ItemData item, Inventory inventory, out bool corruptState) { corruptState = false; if (item.m_customData == null || !item.m_customData.TryGetValue("sighsorry.RepairRequiresMaterials.SkillFreeRepairTicket", out var value)) { return null; } if (TryParseState(value, out TicketState state)) { return state; } corruptState = true; try { item.m_customData.Remove("sighsorry.RepairRequiresMaterials.SkillFreeRepairTicket"); RepairService.MarkInventoryDirty(inventory); } catch { } return null; } private static bool TryParseState(string serialized, out TicketState? state) { state = null; string[] array = serialized.Split(new char[1] { '|' }, 5); if (array.Length != 5 || !string.Equals(array[0], "v1", StringComparison.Ordinal) || !Guid.TryParseExact(array[1], "N", out var _) || !ulong.TryParse(array[2], NumberStyles.None, CultureInfo.InvariantCulture, out var result2) || array[3].Length != 1) { return false; } TicketOutcome ticketOutcome = array[3][0] switch { 'F' => TicketOutcome.Free, 'A' => TicketOutcome.AnvilFree, 'P' => TicketOutcome.Paid, 'N' => TicketOutcome.None, _ => (TicketOutcome)(-1), }; if (ticketOutcome < TicketOutcome.None || (ticketOutcome != TicketOutcome.None && array[4].Length == 0) || (ticketOutcome == TicketOutcome.None && array[4].Length != 0)) { return false; } state = new TicketState(array[1], result2, ticketOutcome, array[4]); return true; } private static void NormalizeSessionCycle(ItemData item, Inventory inventory, TicketState state) { if (SessionMinimumCycles.TryGetValue(state.ItemId, out var value) && state.Cycle < value) { state.Cycle = value; state.Outcome = TicketOutcome.None; state.PlanFingerprint = string.Empty; WriteState(item, inventory, state); } } private static bool WriteState(ItemData item, Inventory inventory, TicketState state) { try { if (item.m_customData == null) { item.m_customData = new Dictionary<string, string>(); } string text = state.Serialize(); if (item.m_customData.TryGetValue("sighsorry.RepairRequiresMaterials.SkillFreeRepairTicket", out var value) && string.Equals(value, text, StringComparison.Ordinal)) { return true; } item.m_customData["sighsorry.RepairRequiresMaterials.SkillFreeRepairTicket"] = text; RepairService.MarkInventoryDirty(inventory); return true; } catch (Exception ex) { RepairRequiresMaterialsPlugin.Log.LogWarning((object)("Could not persist a skill-free repair ticket: " + ex.GetType().Name + ": " + ex.Message)); return false; } } private static string BuildPlanFingerprint(RepairPreview preview) { StringBuilder stringBuilder = new StringBuilder(); AppendPart(stringBuilder, "plan-v1"); AppendPart(stringBuilder, ResolveItemPrefabName(preview.Item)); AppendPart(stringBuilder, preview.Item.m_quality.ToString(CultureInfo.InvariantCulture)); AppendPart(stringBuilder, preview.DurabilityBucketPercent.ToString(CultureInfo.InvariantCulture)); foreach (RepairMaterialCost item in preview.Costs.Where((RepairMaterialCost cost) => cost.RequiredAmount > 0).OrderBy<RepairMaterialCost, string>((RepairMaterialCost cost) => cost.ResourcePrefabName, StringComparer.Ordinal).ThenBy((RepairMaterialCost cost) => cost.RequiredAmount)) { AppendPart(stringBuilder, item.ResourcePrefabName); AppendPart(stringBuilder, item.RequiredAmount.ToString(CultureInfo.InvariantCulture)); } return Sha256Hex(stringBuilder.ToString()); } private static void AppendPart(StringBuilder builder, string value) { builder.Append(value.Length.ToString(CultureInfo.InvariantCulture)); builder.Append(':'); builder.Append(value); builder.Append('|'); } private static string ResolveItemPrefabName(ItemData item) { string text = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : item.m_shared.m_name)?.Trim() ?? string.Empty; if (!text.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase)) { return text; } return text.Substring(0, text.Length - "(Clone)".Length).Trim(); } private static double GetDeterministicRoll(string itemId, ulong cycle) { string s = string.Join("|", "sighsorry.RepairRequiresMaterials.SkillFreeRepairRoll.v1", itemId, cycle.ToString(CultureInfo.InvariantCulture)); byte[] array; using (SHA256 sHA = SHA256.Create()) { array = sHA.ComputeHash(Encoding.UTF8.GetBytes(s)); } ulong num = 0uL; for (int i = 0; i < 8; i++) { num = (num << 8) | array[i]; } return (double)(num >> 11) / 9007199254740992.0; } private static string Sha256Hex(string value) { byte[] array; using (SHA256 sHA = SHA256.Create()) { array = sHA.ComputeHash(Encoding.UTF8.GetBytes(value)); } StringBuilder stringBuilder = new StringBuilder(array.Length * 2); byte[] array2 = array; foreach (byte b in array2) { stringBuilder.Append(b.ToString("x2", CultureInfo.InvariantCulture)); } return stringBuilder.ToString(); } } internal static class CraftingProductionBonusSystem { internal const int UseVanillaBonus = -1; internal const string DefaultExcludedOutputPrefabPatterns = "Simple_*_Socket, Advanced_*_Socket, Perfect_*_Socket"; private const int MaximumIndependentRolls = 10000; private static bool _largeOutputWarningLogged; private static volatile PrefabPatternMatcher _excludedOutputPrefabs = PrefabPatternMatcher.Empty; internal static void SetExcludedOutputPrefabPatterns(string? patterns) { _excludedOutputPrefabs = PrefabPatternMatcher.Parse(patterns); } internal static float CalculatePerItemChance(float skillFactor, float bonusOutputChanceAtLevel100Percent) { double num = ClampProbability(skillFactor); double num2 = ClampLevel100ChancePercent(bonusOutputChanceAtLevel100Percent) / 100.0; if (num <= 0.0 || num2 <= 0.0) { return 0f; } return (float)ClampProbability(num * num2); } internal static int RollBonusItems(int baseItemCount, float itemChance, Func<float> nextRandomValue) { if (baseItemCount <= 0) { return 0; } int num = int.MaxValue - baseItemCount; if (num <= 0) { return 0; } double num2 = ClampProbability(itemChance); if (num2 <= 0.0) { return 0; } if (num2 >= 1.0) { return Math.Min(baseItemCount, num); } if (nextRandomValue == null) { throw new ArgumentNullException("nextRandomValue"); } int num3 = 0; for (int i = 0; i < baseItemCount; i++) { if (num3 >= num) { break; } if ((double)nextRandomValue() < num2) { num3++; } } return num3; } internal static int CalculateCraftingSkillBonusOrUseVanilla(InventoryGui gui, Player player, CraftingStation station, int baseItemCount) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown if ((Object)station == (Object)null || (int)station.m_craftingSkill != 107) { return -1; } if ((Object)player == (Object)null || baseItemCount <= 0) { return 0; } Recipe craftRecipe = gui.m_craftRecipe; if ((Object)craftRecipe == (Object)null || (Object)craftRecipe.m_item == (Object)null) { return 0; } string name = ((Object)((Component)craftRecipe.m_item).gameObject).name; if (_excludedOutputPrefabs.IsMatch(name) || craftRecipe.m_item.m_itemData.m_shared.m_maxStackSize <= 1) { return 0; } float value = RepairRequiresMaterialsPlugin.CraftingBonusOutputChanceAtLevel100.Value; float num = CalculatePerItemChance(((Character)player).GetSkillFactor((SkillType)107), value); if (num <= 0f) { return 0; } if (baseItemCount > 10000 && num < 1f) { if (!_largeOutputWarningLogged) { _largeOutputWarningLogged = true; RepairRequiresMaterialsPlugin.Log.LogWarning((object)($"A crafting result exceeded {10000} base items; " + "using Valheim's production-bonus calculation to avoid a long main-thread roll loop.")); } return -1; } return RollBonusItems(baseItemCount, num, NextRandomValue); } private static double ClampProbability(double value) { if (double.IsNaN(value) || value <= 0.0) { return 0.0; } if (!double.IsPositiveInfinity(value) && !(value >= 1.0)) { return value; } return 1.0; } private static double ClampLevel100ChancePercent(float value) { if (float.IsNaN(value) || value <= 0f) { return 0.0; } if (!float.IsPositiveInfinity(value) && !(value >= 25f)) { return value; } return 25.0; } private static float NextRandomValue() { return Random.value; } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] [HarmonyPriority(0)] internal static class InventoryGuiCraftingProductionBonusPatch { private static readonly MethodInfo GetAmountMethod = AccessTools.Method(typeof(Recipe), "GetAmount", new Type[4] { typeof(int), typeof(int).MakeByRefType(), typeof(ItemData).MakeByRefType(), typeof(int) }, (Type[])null); private static readonly MethodInfo GetCurrentCraftingStationMethod = AccessTools.Method(typeof(Player), "GetCurrentCraftingStation", (Type[])null, (Type[])null); private static readonly MethodInfo BonusHelperMethod = AccessTools.Method(typeof(CraftingProductionBonusSystem), "CalculateCraftingSkillBonusOrUseVanilla", (Type[])null, (Type[])null); private static readonly MethodInfo RandomValueGetter = AccessTools.PropertyGetter(typeof(Random), "value"); private static readonly FieldInfo CraftUpgradeItemField = AccessTools.Field(typeof(InventoryGui), "m_craftUpgradeItem"); private static readonly FieldInfo CraftBonusChanceField = AccessTools.Field(typeof(InventoryGui), "m_craftBonusChance"); private static readonly FieldInfo CraftBonusAmountField = AccessTools.Field(typeof(InventoryGui), "m_craftBonusAmount"); private static bool _patternWarningLogged; [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); try { if (!TryInject(list, generator, out string failure)) { LogPatternFailure(failure); } } catch (Exception arg) { LogPatternFailure($"unexpected transpiler error: {arg}"); } return list; } private static bool TryInject(List<CodeInstruction> codes, ILGenerator generator, out string failure) { //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02cc: Expected O, but got Unknown //IL_02e6: Unknown result type (might be due to invalid IL or missing references) //IL_02f0: Expected O, but got Unknown //IL_02f7: Unknown result type (might be due to invalid IL or missing references) //IL_0301: Expected O, but got Unknown //IL_0332: Unknown result type (might be due to invalid IL or missing references) //IL_033c: Expected O, but got Unknown //IL_0369: Unknown result type (might be due to invalid IL or missing references) //IL_0373: Expected O, but got Unknown //IL_0380: Unknown result type (might be due to invalid IL or missing references) //IL_038a: Expected O, but got Unknown //IL_03b7: Unknown result type (might be due to invalid IL or missing references) //IL_03c1: Expected O, but got Unknown //IL_03e2: Unknown result type (might be due to invalid IL or missing references) //IL_03ec: Expected O, but got Unknown failure = string.Empty; int num = FindCall(codes, GetAmountMethod, 0); int num2 = FindCall(codes, GetCurrentCraftingStationMethod, 0); if (num < 0 || num2 < 0) { failure = "could not locate Recipe.GetAmount and GetCurrentCraftingStation anchors"; return false; } if (num + 1 >= codes.Count || !TryGetStoredLocal(codes[num + 1], out var localIndex)) { failure = "could not resolve the pre-bonus result amount local"; return false; } int num3 = num2 + 1; if (num3 >= codes.Count || !TryGetStoredLocal(codes[num3], out var localIndex2)) { failure = "could not resolve the current crafting station local"; return false; } int startIndex = Math.Max(num + 2, num3 + 1); int num4 = FindCall(codes, RandomValueGetter, startIndex); if (num4 < 0 || !TryFindZeroInitializedLocal(codes, startIndex, num4, out var storeIndex, out var localIndex3)) { failure = "could not resolve the vanilla crafting-bonus accumulator"; return false; } int num5 = FindLocalLoad(codes, localIndex2, storeIndex + 1, num4); if (num5 < 0) { failure = "the vanilla crafting-bonus entry pattern changed"; return false; } int num6 = FindFieldLoad(codes, CraftBonusChanceField, num5); int num7 = FindFieldLoad(codes, CraftBonusAmountField, num5); if (num4 <= num5 || num6 <= num4 || num7 <= num6) { failure = "could not locate the ordered vanilla crafting-bonus roll"; return false; } int num8 = num7 - 2; int index = num7 + 2; int index2 = num7 + 3; int index3 = num7 + 4; int num9 = num7 + 6; if (num8 < num5 || num9 >= codes.Count || !LoadsLocal(codes[num8], localIndex3) || codes[num7 - 1].opcode != OpCodes.Ldarg_0 || codes[num7 + 1].opcode != OpCodes.Add || !StoresLocal(codes[index], localIndex3) || !LoadsLocal(codes[index2], localIndex) || !LoadsLocal(codes[index3], localIndex3) || codes[num7 + 5].opcode != OpCodes.Add || !StoresLocal(codes[num9], localIndex)) { failure = "the vanilla crafting-bonus accumulation pattern changed"; return false; } int num10 = FindFieldLoad(codes, CraftUpgradeItemField, num9 + 1); int num11 = num10 - 1; if (num10 <= num5 || num11 < 0 || num10 + 1 >= codes.Count || codes[num11].opcode != OpCodes.Ldarg_0 || !IsBranchTrue(codes[num10 + 1])) { failure = "could not locate the post-bonus inventory-capacity check"; return false; } if (num4 >= num11 || num6 >= num11 || num9 >= num11) { failure = "the vanilla crafting-bonus anchors crossed the capacity-check boundary"; return false; } Label label = generator.DefineLabel(); Label label2; if (codes[num11].labels.Count > 0) { label2 = codes[num11].labels[0]; } else { label2 = generator.DefineLabel(); codes[num11].labels.Add(label2); } CodeInstruction val = new CodeInstruction(OpCodes.Ldc_I4_0, (object)null); val.labels.Add(label); List<CodeInstruction> list = new List<CodeInstruction> { new CodeInstruction(OpCodes.Ldarg_0, (object)null), new CodeInstruction(OpCodes.Ldarg_1, (object)null), CloneWithoutMetadata(codes[num5]), CloneWithoutMetadata(codes[index2]), new CodeInstruction(OpCodes.Call, (object)BonusHelperMethod), CloneWithoutMetadata(codes[storeIndex]), CloneWithoutMetadata(codes[index3]), new CodeInstruction(OpCodes.Ldc_I4_0, (object)null), new CodeInstruction(OpCodes.Blt, (object)label), CloneWithoutMetadata(codes[index2]), CloneWithoutMetadata(codes[index3]), new CodeInstruction(OpCodes.Add, (object)null), CloneWithoutMetadata(codes[num + 1]), new CodeInstruction(OpCodes.Br, (object)label2), val, CloneWithoutMetadata(codes[storeIndex]) }; list[0].labels.AddRange(codes[num5].labels); codes[num5].labels.Clear(); list[0].blocks.AddRange(codes[num5].blocks); codes[num5].blocks.Clear(); codes.InsertRange(num5, list); return true; } private static int FindCall(List<CodeInstruction> codes, MethodInfo method, int startIndex) { for (int i = Math.Max(0, startIndex); i < codes.Count; i++) { if ((codes[i].opcode == OpCodes.Call || codes[i].opcode == OpCodes.Callvirt) && object.Equals(codes[i].operand, method)) { return i; } } return -1; } private static int FindFieldLoad(List<CodeInstruction> codes, FieldInfo field, int startIndex) { for (int i = Math.Max(0, startIndex); i < codes.Count; i++) { if ((codes[i].opcode == OpCodes.Ldfld || codes[i].opcode == OpCodes.Ldsfld) && object.Equals(codes[i].operand, field)) { return i; } } return -1; } private static bool TryFindZeroInitializedLocal(List<CodeInstruction> codes, int startIndex, int endIndex, out int storeIndex, out int localIndex) { storeIndex = -1; localIndex = -1; for (int i = Math.Max(0, startIndex); i + 1 < Math.Min(endIndex, codes.Count); i++) { if (IsLoadConstantZero(codes[i]) && TryGetStoredLocal(codes[i + 1], out var localIndex2)) { storeIndex = i + 1; localIndex = localIndex2; return true; } } return false; } private static int FindLocalLoad(List<CodeInstruction> codes, int localIndex, int startIndex, int endIndex) { for (int i = Math.Max(0, startIndex); i < Math.Min(endIndex, codes.Count); i++) { if (LoadsLocal(codes[i], localIndex)) { return i; } } return -1; } private static bool LoadsLocal(CodeInstruction instruction, int localIndex) { if (TryGetLocalIndex(instruction, load: true, out var localIndex2)) { return localIndex2 == localIndex; } return false; } private static bool StoresLocal(CodeInstruction instruction, int localIndex) { if (TryGetLocalIndex(instruction, load: false, out var localIndex2)) { return localIndex2 == localIndex; } return false; } private static bool TryGetStoredLocal(CodeInstruction instruction, out int localIndex) { return TryGetLocalIndex(instruction, load: false, out localIndex); } private static bool TryGetLocalIndex(CodeInstruction instruction, bool load, out int localIndex) { localIndex = -1; OpCode opcode = instruction.opcode; if (load) { if (opcode == OpCodes.Ldloc_0) { localIndex = 0; return true; } if (opcode == OpCodes.Ldloc_1) { localIndex = 1; return true; } if (opcode == OpCodes.Ldloc_2) { localIndex = 2; return true; } if (opcode == OpCodes.Ldloc_3) { localIndex = 3; return true; } if (opcode != OpCodes.Ldloc && opcode != OpCodes.Ldloc_S) { return false; } } else { if (opcode == OpCodes.Stloc_0) { localIndex = 0; return true;