Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of SmartRestock v1.3.2
SmartRestock.dll
Decompiled 2 days agousing System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.Json; using HarmonyLib; using Il2CppFishNet; using Il2CppInterop.Runtime.Injection; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne.Core.Items.Framework; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.EntityFramework; using Il2CppScheduleOne.GameTime; using Il2CppScheduleOne.ItemFramework; using Il2CppScheduleOne.Messaging; using Il2CppScheduleOne.Money; using Il2CppScheduleOne.NPCs; using Il2CppScheduleOne.Persistence; using Il2CppScheduleOne.Property; using Il2CppScheduleOne.Storage; using Il2CppScheduleOne.Tiles; using Il2CppScheduleOne.UI; using Il2CppScheduleOne.UI.Items; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using SmartRestock; using SmartRestock.Logic; using SmartRestock.State; using SmartRestock.UI; using SmartRestock.Utils; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(global::SmartRestock.SmartRestock), "SmartRestock", "1.3.2", "uplusion23", null)] [assembly: MelonColor(255, 171, 33, 241)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("SmartRestock")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+26cf1a1ea481edbb8fe1acff85ee53e4f929ac34")] [assembly: AssemblyProduct("SmartRestock")] [assembly: AssemblyTitle("SmartRestock")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NativeIntegerAttribute : Attribute { public readonly bool[] TransformFlags; public NativeIntegerAttribute() { TransformFlags = new bool[1] { true }; } public NativeIntegerAttribute(bool[] P_0) { TransformFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SmartRestock { public sealed class SmartRestock : MelonMod { private const string PrefixGradientName = "\u001b[38;2;171;33;241mS\u001b[38;2;160;45;239mm\u001b[38;2;150;58;237ma\u001b[38;2;139;70;235mr\u001b[38;2;128;82;233mt\u001b[38;2;117;95;231mR\u001b[38;2;107;107;229me\u001b[38;2;96;119;227ms\u001b[38;2;85;132;225mt\u001b[38;2;74;144;223mo\u001b[38;2;64;156;221mc\u001b[38;2;84;218;244mk\u001b[0m"; internal const string DisplayName = "SmartRestock"; internal static SmartRestock Instance { get; private set; } internal static Harmony HarmonyPatcher { get; private set; } internal static Instance Log { get; private set; } public override void OnInitializeMelon() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown Instance = this; Preferences.EnsureUserDataDirectory(); HarmonyPatcher = new Harmony("SmartRestock"); HarmonyPatcher.PatchAll(); Log = new Instance("\u001b[38;2;171;33;241mS\u001b[38;2;160;45;239mm\u001b[38;2;150;58;237ma\u001b[38;2;139;70;235mr\u001b[38;2;128;82;233mt\u001b[38;2;117;95;231mR\u001b[38;2;107;107;229me\u001b[38;2;96;119;227ms\u001b[38;2;85;132;225mt\u001b[38;2;74;144;223mo\u001b[38;2;64;156;221mc\u001b[38;2;84;218;244mk\u001b[0m"); Log.Msg("SmartRestock initialized."); } public override void OnUpdate() { RestockManager.Tick(); } } } namespace SmartRestock.Utils { internal static class Preferences { internal static MelonPreferences_Category basePrefs; internal static MelonPreferences_Category dangerPrefs; internal static bool AdditionalLogging => basePrefs.GetEntry<bool>("99_enableAdditionalLogging").Value; static Preferences() { basePrefs = null; dangerPrefs = null; EnsureUserDataDirectory(); CreateMelonPreferences(); CreateDangerCategory(); } public static void EnsureUserDataDirectory() { if (!Directory.Exists("UserData/SmartRestock")) { Directory.CreateDirectory("UserData/SmartRestock"); } } public static void CreateMelonPreferences() { basePrefs = MelonPreferences.CreateCategory("SmartRestock", "General Settings"); if (basePrefs == null) { SmartRestock.Log.Error("Failed to create MelonPreferences category."); throw new Exception("Failed to create MelonPreferences category."); } basePrefs.SetFilePath("UserData/SmartRestock/SmartRestock.cfg", true, false); basePrefs.CreateEntry<int>("restockAmount", 0, "Amount to restock", "How much to restock (enter 0 for max stack)", false, false, (ValueValidator)null, (string)null); basePrefs.CreateEntry<bool>("restockSeeds", false, "Restock seeds & pseudo", "Should you restock seeds and pseudo? (includes cocaseed, granddaddypurpleseed, greencrackseed, ogkushseed, sourdieselseed, spore syringe, and pseudo)", false, false, (ValueValidator)null, (string)null); basePrefs.CreateEntry<bool>("enableNotifications", true, "Enable notifications", "Show notifications when restock actions are performed", false, false, (ValueValidator)null, (string)null); basePrefs.CreateEntry<bool>("enableTransactionText", true, "Enable end-of-day transactions text", "Show a summary of restock transactions in the end-of-day report", false, false, (ValueValidator)null, (string)null); basePrefs.CreateEntry<bool>("notifyFailedRestock", true, "Notify on failed restock", "Show a notification when a restock fails due to insufficient funds", false, false, (ValueValidator)null, (string)null); basePrefs.CreateEntry<bool>("99_enableAdditionalLogging", false, "Enable additional logging", "Log verbose debug information (skip reasons, slot evictions, toggle events). Useful for troubleshooting.", false, false, (ValueValidator)null, (string)null); basePrefs.SaveToFile(false); } private static void CreateDangerCategory() { dangerPrefs = MelonPreferences.CreateCategory("SmartRestock_Dangerous", "Dangerous Settings"); if (dangerPrefs == null) { SmartRestock.Log.Error("Failed to create danger MelonPreferences category."); throw new Exception("Failed to create danger MelonPreferences category."); } dangerPrefs.SetFilePath("UserData/SmartRestock/SmartRestock.cfg", true, false); dangerPrefs.CreateEntry<bool>("useCashForRestock", false, "Use cash for restocking", "If enabled, the mod will use on-hand cash for restocking instead of bank funds. Use with caution as this may lead to running out of cash.", false, false, (ValueValidator)null, (string)null); dangerPrefs.CreateEntry<bool>("restockAnyItem", false, "Restock any item", "If enabled, the mod will attempt to restock any item, not just those on the predefined list. This may cause unintended consequences and should be used with caution.", false, false, (ValueValidator)null, (string)null); } } internal static class SlotIdentity { private static readonly string[] EligibleStationSlotNames = new string[4] { "Ingredient", "InputSlot", "Liquid", "Packaging" }; private static readonly ConcurrentDictionary<Type, (FieldInfo? Field, PropertyInfo? Prop)> AssignedSlotMemberCache = new ConcurrentDictionary<Type, (FieldInfo, PropertyInfo)>(); private static readonly ConcurrentDictionary<Type, FieldInfo?> SlotIndexFieldCache = new ConcurrentDictionary<Type, FieldInfo>(); private static PropertyInfo? _originCoordProp; private static FieldInfo? _originCoordField; private static bool _originCoordLookupDone; public static bool IsEligibleSlot(ItemSlotUI slotUi) { try { ItemSlot val = TryResolveAssignedSlot(slotUi); if (val == null) { return false; } IItemSlotOwner slotOwner = val.SlotOwner; Il2CppObjectBase val2 = (Il2CppObjectBase)(object)slotOwner; if (val2 == null) { return false; } if ((Object)(object)val2.TryCast<StorageEntity>() != (Object)null) { return true; } if ((Object)(object)val2.TryCast<GridItem>() != (Object)null) { return IsEligibleStationSlot(slotUi); } Component val3 = val2.TryCast<Component>(); if ((Object)(object)((val3 != null) ? val3.gameObject : null) != (Object)null) { GameObject gameObject = val3.gameObject; if ((Object)(object)gameObject.GetComponent<StorageEntity>() != (Object)null) { return true; } if ((Object)(object)gameObject.GetComponent<GridItem>() != (Object)null) { return IsEligibleStationSlot(slotUi); } return false; } return false; } catch { return false; } } private static bool IsEligibleStationSlot(ItemSlotUI slotUi) { GameObject gameObject = ((Component)slotUi).gameObject; string text = ((gameObject != null) ? ((Object)gameObject).name : null); if (string.IsNullOrEmpty(text)) { return false; } string[] eligibleStationSlotNames = EligibleStationSlotNames; foreach (string value in eligibleStationSlotNames) { if (text.Contains(value, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } public static string? TryGetPropertyName(ItemSlot slot) { try { IItemSlotOwner slotOwner = slot.SlotOwner; Il2CppObjectBase val = (Il2CppObjectBase)(object)slotOwner; if (val == null) { return null; } GridItem val2 = TryResolveGridItem(val); object result; if (val2 == null) { result = null; } else { Property parentProperty = ((BuildableItem)val2).ParentProperty; result = ((parentProperty != null) ? ((Object)parentProperty).name : null); } return (string?)result; } catch { return null; } } public static SlotKey? TryResolve(ItemSlotUI slotUi) { try { ItemSlot val = TryResolveAssignedSlot(slotUi); if (val == null) { return null; } int? num = TryGetSlotIndex(val); if (!num.HasValue || num.Value < 0) { return null; } string text = TryBuildContainerId(val); if (string.IsNullOrEmpty(text)) { return null; } return new SlotKey(text, num.Value); } catch (Exception ex) { SmartRestock.Log.Warning("Slot identity resolve failed: " + ex.Message); return null; } } private static string? TryBuildContainerId(ItemSlot slot) { //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_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) try { IItemSlotOwner slotOwner = slot.SlotOwner; if (slotOwner == null) { return null; } Il2CppObjectBase val = (Il2CppObjectBase)(object)slotOwner; if (val == null) { return null; } GridItem val2 = TryResolveGridItem(val); if ((Object)(object)val2 == (Object)null) { return null; } Property parentProperty = ((BuildableItem)val2).ParentProperty; string value = ((parentProperty != null) ? ((Object)parentProperty).name : null); if (string.IsNullOrWhiteSpace(value)) { return null; } Grid ownerGrid = val2.OwnerGrid; string value2 = ((ownerGrid != null) ? ((Object)ownerGrid).name : null); if (string.IsNullOrWhiteSpace(value2)) { return null; } Vector2 val3 = TryGetOriginCoordinate(val2); ItemInstance itemInstance = ((BuildableItem)val2).ItemInstance; object obj; if (itemInstance == null) { obj = null; } else { ItemDefinition definition = itemInstance.Definition; obj = ((definition != null) ? ((BaseItemDefinition)definition).ID : null); } string value3 = (string)obj; if (string.IsNullOrWhiteSpace(value3)) { return null; } return $"{value}:{value2}:{val3.x:0.##},{val3.y:0.##}:{value3}"; } catch { return null; } } private static GridItem? TryResolveGridItem(Il2CppObjectBase owner) { GridItem val = owner.TryCast<GridItem>(); if ((Object)(object)val != (Object)null) { return val; } StorageEntity val2 = owner.TryCast<StorageEntity>(); if ((Object)(object)((val2 != null) ? ((Component)val2).gameObject : null) != (Object)null) { GridItem component = ((Component)val2).gameObject.GetComponent<GridItem>(); if ((Object)(object)component != (Object)null) { return component; } } return null; } private static Vector2 TryGetOriginCoordinate(GridItem gridItem) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) if (!_originCoordLookupDone) { Type type = ((object)gridItem).GetType(); _originCoordProp = type.GetProperty("_originCoordinate", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (_originCoordProp == null) { _originCoordField = type.GetField("_originCoordinate", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } _originCoordLookupDone = true; } if (_originCoordProp?.GetValue(gridItem) is Vector2 result) { return result; } if (_originCoordField?.GetValue(gridItem) is Vector2 result2) { return result2; } return Vector2.zero; } internal static ItemSlot? TryResolveAssignedSlot(ItemSlotUI slotUi) { Type type = ((object)slotUi).GetType(); if (!AssignedSlotMemberCache.TryGetValue(type, out (FieldInfo, PropertyInfo) value)) { FieldInfo fieldInfo = type.GetField("assignedSlot", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetField("AssignedSlot", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetField("slot", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetField("Slot", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); PropertyInfo item = ((fieldInfo == null) ? (type.GetProperty("assignedSlot", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetProperty("AssignedSlot", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetProperty("slot", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetProperty("Slot", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) : null); value = (fieldInfo, item); AssignedSlotMemberCache[type] = value; } object? obj = value.Item1?.GetValue(slotUi); ItemSlot val = (ItemSlot)((obj is ItemSlot) ? obj : null); if (val != null) { return val; } object? obj2 = value.Item2?.GetValue(slotUi, null); ItemSlot val2 = (ItemSlot)((obj2 is ItemSlot) ? obj2 : null); if (val2 != null) { return val2; } return null; } private static int? TryGetSlotIndex(ItemSlot slot) { try { return slot.SlotIndex; } catch { } Type type = ((object)slot).GetType(); if (!SlotIndexFieldCache.TryGetValue(type, out FieldInfo value)) { value = type.GetField("_SlotIndex_k__BackingField", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetField("slotIndex", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetField("SlotIndex", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type.GetField("index", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); SlotIndexFieldCache[type] = value; } if (value != null && value.FieldType == typeof(int) && value.GetValue(slot) is int value2) { return value2; } return null; } } } namespace SmartRestock.UI { internal static class SmartRestockButtonInjector { private const string ButtonName = "SmartRestockButton"; private const string IconName = "SmartRestockIcon"; private static Transform? FindTemplateButton(Transform root) { Transform val = root.Find("Filter"); if ((Object)(object)val != (Object)null) { return val; } Il2CppArrayBase<Transform> componentsInChildren = ((Component)root).GetComponentsInChildren<Transform>(true); foreach (Transform item in componentsInChildren) { if ((Object)(object)item == (Object)null || !string.Equals(((Object)item).name, "Filter", StringComparison.OrdinalIgnoreCase) || !((Object)(object)((Component)item).GetComponent<Button>() != (Object)null)) { continue; } return item; } return null; } private static Image EnsureControlledIcon(GameObject buttonObject, Image backgroundImage) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) Transform val = buttonObject.transform.Find("SmartRestockIcon"); Image val3; if ((Object)(object)val == (Object)null) { GameObject val2 = new GameObject("SmartRestockIcon"); val2.transform.SetParent(buttonObject.transform, false); val2.AddComponent<RectTransform>(); val2.AddComponent<Image>(); val3 = val2.GetComponent<Image>(); } else { val3 = ((Component)val).GetComponent<Image>(); if ((Object)(object)val3 == (Object)null) { val3 = ((Component)val).gameObject.AddComponent<Image>(); } } RectTransform rectTransform = ((Graphic)val3).rectTransform; rectTransform.anchorMin = new Vector2(0.18f, 0.18f); rectTransform.anchorMax = new Vector2(0.82f, 0.82f); rectTransform.offsetMin = Vector2.zero; rectTransform.offsetMax = Vector2.zero; rectTransform.anchoredPosition = Vector2.zero; ((Transform)rectTransform).localScale = Vector3.one; ((Graphic)val3).raycastTarget = false; Il2CppArrayBase<Image> componentsInChildren = buttonObject.GetComponentsInChildren<Image>(true); foreach (Image item in componentsInChildren) { if (!((Object)(object)item == (Object)null) && !((Object)(object)item == (Object)(object)backgroundImage) && !((Object)(object)item == (Object)(object)val3)) { ((Behaviour)item).enabled = false; } } return val3; } private static void DisableFilterComponents(GameObject buttonObject) { ItemSlotFilterButton component = buttonObject.GetComponent<ItemSlotFilterButton>(); if ((Object)(object)component != (Object)null) { ((Behaviour)component).enabled = false; Object.Destroy((Object)(object)component); } } private static void PlaceClone(RectTransform cloneRt, RectTransform templateRt) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) cloneRt.anchorMin = templateRt.anchorMin; cloneRt.anchorMax = templateRt.anchorMax; cloneRt.pivot = templateRt.pivot; cloneRt.sizeDelta = templateRt.sizeDelta; cloneRt.anchoredPosition = templateRt.anchoredPosition + new Vector2(-28f, 0f); } public static void Attach(ItemSlotUI slotUi) { //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Expected O, but got Unknown try { Transform transform = ((Component)slotUi).transform; if ((Object)(object)transform == (Object)null) { return; } if (!SlotIdentity.IsEligibleSlot(slotUi)) { Transform val = transform.Find("SmartRestockButton"); if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)((Component)val).gameObject); } return; } Transform val2 = FindTemplateButton(transform); if ((Object)(object)val2 == (Object)null) { Transform val3 = transform.Find("SmartRestockButton"); if ((Object)(object)val3 != (Object)null) { Object.Destroy((Object)(object)((Component)val3).gameObject); } return; } Transform val4 = transform.Find("SmartRestockButton"); if ((Object)(object)val4 != (Object)null) { DisableFilterComponents(((Component)val4).gameObject); Button component = ((Component)val4).GetComponent<Button>(); Image component2 = ((Component)val4).GetComponent<Image>(); SmartRestockButtonBehaviour component3 = ((Component)val4).GetComponent<SmartRestockButtonBehaviour>(); if ((Object)(object)component3 != (Object)null && (Object)(object)component != (Object)null && (Object)(object)component2 != (Object)null) { Image iconImage = EnsureControlledIcon(((Component)val4).gameObject, component2); component3.Initialize(slotUi, component, iconImage); } component3?.RefreshVisual(); return; } GameObject val5 = Object.Instantiate<GameObject>(((Component)val2).gameObject, val2.parent); ((Object)val5).name = "SmartRestockButton"; val5.SetActive(true); RectTransform component4 = val5.GetComponent<RectTransform>(); RectTransform component5 = ((Component)val2).GetComponent<RectTransform>(); if ((Object)(object)component4 != (Object)null && (Object)(object)component5 != (Object)null) { PlaceClone(component4, component5); } DisableFilterComponents(val5); Button component6 = val5.GetComponent<Button>(); Image component7 = val5.GetComponent<Image>(); if ((Object)(object)component6 == (Object)null || (Object)(object)component7 == (Object)null) { SmartRestock.Log.Warning("SmartRestock clone missing Button/Image; skipping."); return; } Image iconImage2 = EnsureControlledIcon(val5, component7); component6.onClick = new ButtonClickedEvent(); SmartRestockButtonBehaviour smartRestockButtonBehaviour = val5.GetComponent<SmartRestockButtonBehaviour>(); if ((Object)(object)smartRestockButtonBehaviour == (Object)null) { smartRestockButtonBehaviour = val5.AddComponent<SmartRestockButtonBehaviour>(); } smartRestockButtonBehaviour.Initialize(slotUi, component6, iconImage2); smartRestockButtonBehaviour.RefreshVisual(); } catch (Exception value) { SmartRestock.Log.Error($"Attach auto-restock button failed: {value}"); } } } [RegisterTypeInIl2Cpp] public sealed class SmartRestockButtonBehaviour : MonoBehaviour { private ItemSlotUI? _slotUi; private Button? _button; private Image? _iconImage; private SlotKey? _slotKey; private float _nextRefreshAt; private float _resolveRetryAfter; public SmartRestockButtonBehaviour(IntPtr ptr) : base(ptr) { } public SmartRestockButtonBehaviour() : base(ClassInjector.DerivedConstructorPointer<SmartRestockButtonBehaviour>()) { ClassInjector.DerivedConstructorBody((Il2CppObjectBase)(object)this); } public void Initialize(ItemSlotUI slotUi, Button button, Image iconImage) { _slotUi = slotUi; _button = button; _iconImage = iconImage; _slotKey = SlotIdentity.TryResolve(slotUi); _nextRefreshAt = 0f; if (_slotKey.HasValue) { RestockManager.SyncSlotBinding(slotUi, _slotKey.Value); } ((UnityEventBase)button.onClick).RemoveAllListeners(); ((UnityEvent)button.onClick).AddListener(UnityAction.op_Implicit((Action)OnClick)); } private void OnEnable() { _nextRefreshAt = 0f; RefreshVisual(); } private void Update() { if (!(Time.unscaledTime < _nextRefreshAt)) { _nextRefreshAt = Time.unscaledTime + 0.15f; RefreshVisual(); } } public void RefreshVisual() { //IL_00d2: 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) if ((Object)(object)_iconImage == (Object)null) { return; } if (!_slotKey.HasValue && (Object)(object)_slotUi != (Object)null && Time.unscaledTime >= _resolveRetryAfter) { _slotKey = SlotIdentity.TryResolve(_slotUi); if (_slotKey.HasValue) { RestockManager.SyncSlotBinding(_slotUi, _slotKey.Value); } else { _resolveRetryAfter = Time.unscaledTime + 2f; } } bool flag = _slotKey.HasValue && SmartRestockStateStore.Get(_slotKey.Value); SmartRestockButtonIconAssets.ApplyStateSprite(_iconImage, flag); ((Graphic)_iconImage).color = (flag ? SmartRestockButtonInjectorReflection.EnabledColor : SmartRestockButtonInjectorReflection.DisabledColor); } private void OnClick() { if ((Object)(object)_slotUi == (Object)null) { return; } SlotKey? slotKey = _slotKey; if (!slotKey.HasValue) { _slotKey = SlotIdentity.TryResolve(_slotUi); } if (!_slotKey.HasValue) { SmartRestock.Log.Warning("Unable to resolve slot identity for auto-restock toggle."); return; } bool flag = SmartRestockStateStore.Toggle(_slotKey.Value); RefreshVisual(); if (Preferences.AdditionalLogging) { SmartRestock.Log.Msg($"Auto-restock {(flag ? "enabled" : "disabled")} for {_slotKey.Value}"); } RestockManager.NotifyToggleChanged(_slotUi, _slotKey.Value, flag); } } internal static class SmartRestockButtonInjectorReflection { public static Color EnabledColor => new Color(0.4f, 1f, 0.4f, 1f); public static Color DisabledColor => new Color(1f, 1f, 1f, 0.4f); } internal static class SmartRestockButtonIconAssets { private const string EnabledIconResource = "SmartRestock.Assets.Icons.refresh-cw.png"; private const string DisabledIconResource = "SmartRestock.Assets.Icons.refresh-cw-off.png"; private static Sprite? _enabledSprite; private static Sprite? _disabledSprite; private static bool _loadAttempted; public static void ApplyStateSprite(Image image, bool enabled) { EnsureLoaded(); Sprite val = (enabled ? _enabledSprite : _disabledSprite); if (!((Object)(object)val == (Object)null)) { image.sprite = val; image.type = (Type)0; image.preserveAspect = true; } } private static void EnsureLoaded() { if (!_loadAttempted || !IsSpriteAlive(_enabledSprite) || !IsSpriteAlive(_disabledSprite)) { _loadAttempted = true; _enabledSprite = LoadSpriteFromManifest("SmartRestock.Assets.Icons.refresh-cw.png"); _disabledSprite = LoadSpriteFromManifest("SmartRestock.Assets.Icons.refresh-cw-off.png"); } } private static bool IsSpriteAlive(Sprite? sprite) { if ((Object)(object)sprite == (Object)null) { return false; } try { return (Object)(object)sprite.texture != (Object)null; } catch { return false; } } private static Sprite? LoadSpriteFromManifest(string resourceName) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0156: 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_00e9: Unknown result type (might be due to invalid IL or missing references) Assembly assembly = typeof(SmartRestockButtonIconAssets).Assembly; using Stream stream = assembly.GetManifestResourceStream(resourceName); if (stream == null) { SmartRestock.Log.Warning("Auto-restock icon resource not found: " + resourceName); return null; } byte[] array = new byte[stream.Length]; stream.Read(array, 0, array.Length); Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); if (!ImageConversion.LoadImage(val, Il2CppStructArray<byte>.op_Implicit(array), false)) { Object.Destroy((Object)(object)val); SmartRestock.Log.Warning("Failed to decode auto-restock icon: " + resourceName); return null; } Il2CppStructArray<Color32> pixels = val.GetPixels32(); for (int i = 0; i < ((Il2CppArrayBase<Color32>)(object)pixels).Length; i++) { if (((Il2CppArrayBase<Color32>)(object)pixels)[i].a != 0) { ((Il2CppArrayBase<Color32>)(object)pixels)[i] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, ((Il2CppArrayBase<Color32>)(object)pixels)[i].a); } } val.SetPixels32(pixels); val.Apply(false, false); ((Texture)val).wrapMode = (TextureWrapMode)1; ((Texture)val).filterMode = (FilterMode)1; return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f); } } } namespace SmartRestock.State { internal readonly record struct SlotKey(string ContainerId, int SlotIndex) { public override string ToString() { return $"{ContainerId}:{SlotIndex}"; } } internal static class SmartRestockStateStore { private sealed record PersistedSlotState(string ContainerId, int SlotIndex, bool Enabled); private static readonly ConcurrentDictionary<SlotKey, bool> States = new ConcurrentDictionary<SlotKey, bool>(); private static readonly string StateDirectoryPath = Path.Combine(AppContext.BaseDirectory, "UserData", "SmartRestock"); private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions { WriteIndented = true }; private static bool _loaded; private static int _loadedForSaveSlot = -1; private static bool _dirty; private static long _saveRequestedAt; private const long SaveDebounceMs = 500L; public static bool Get(SlotKey key) { EnsureLoaded(); bool value; return States.TryGetValue(key, out value) && value; } public static bool Set(SlotKey key, bool enabled) { EnsureLoaded(); States[key] = enabled; MarkDirty(); return enabled; } public static bool Toggle(SlotKey key) { EnsureLoaded(); bool flag = !Get(key); States[key] = flag; MarkDirty(); return flag; } public static IReadOnlyCollection<SlotKey> Keys() { EnsureLoaded(); return (IReadOnlyCollection<SlotKey>)(object)States.Keys.ToArray(); } public static int RemoveWhere(Func<SlotKey, bool> predicate) { EnsureLoaded(); int num = 0; foreach (SlotKey key in States.Keys) { if (predicate(key) && States.TryRemove(key, out var _)) { num++; } } if (num > 0) { Save(); } return num; } private static int GetCurrentSaveSlot() { try { return Singleton<LoadManager>.Instance.ActiveSaveInfo.SaveSlotNumber; } catch { return 0; } } private static string GetStateFilePath(int saveSlotNumber) { return Path.Combine(StateDirectoryPath, $"slot-states-{saveSlotNumber}.json"); } private static void MarkDirty() { _dirty = true; _saveRequestedAt = Environment.TickCount64; } public static void FlushIfPending() { if (_dirty && Environment.TickCount64 - _saveRequestedAt >= 500) { _dirty = false; Save(); } } private static void EnsureLoaded() { int currentSaveSlot = GetCurrentSaveSlot(); if (_loaded && _loadedForSaveSlot == currentSaveSlot) { return; } if (_dirty) { _dirty = false; Save(); } _loaded = true; _loadedForSaveSlot = currentSaveSlot; States.Clear(); try { string stateFilePath = GetStateFilePath(currentSaveSlot); if (!File.Exists(stateFilePath)) { return; } string json = File.ReadAllText(stateFilePath); List<PersistedSlotState> list = JsonSerializer.Deserialize<List<PersistedSlotState>>(json, JsonOptions); if (list == null) { return; } foreach (PersistedSlotState item in list) { if (!string.IsNullOrWhiteSpace(item.ContainerId) && item.SlotIndex >= 0) { States[new SlotKey(item.ContainerId, item.SlotIndex)] = item.Enabled; } } if (Preferences.AdditionalLogging) { SmartRestock.Log.Msg($"Loaded {States.Count} auto-restock slot states for save slot {currentSaveSlot}."); } } catch (Exception ex) { SmartRestock.Log.Warning("Failed to load auto-restock state: " + ex.Message); } } private static void Save() { try { int currentSaveSlot = GetCurrentSaveSlot(); Directory.CreateDirectory(StateDirectoryPath); List<PersistedSlotState> value = States.Select((KeyValuePair<SlotKey, bool> pair) => new PersistedSlotState(pair.Key.ContainerId, pair.Key.SlotIndex, pair.Value)).ToList(); string contents = JsonSerializer.Serialize(value, JsonOptions); File.WriteAllText(GetStateFilePath(currentSaveSlot), contents); } catch (Exception ex) { SmartRestock.Log.Warning("Failed to save auto-restock state: " + ex.Message); } } } } namespace SmartRestock.Patches { [HarmonyPatch(typeof(ItemSlot), "ChangeQuantity")] internal static class ItemSlotChangeQuantityPatch { private static void Prefix(ItemSlot __instance, ref int change, ref ItemInstance? __state) { __state = null; if (__instance != null && change < 0 && RestockManager.IsSlotEnabled(__instance)) { ItemInstance itemInstance = __instance.ItemInstance; if (itemInstance != null && __instance.Quantity + change <= 0) { __state = itemInstance.GetCopy(((BaseItemInstance)itemInstance).Quantity); } } } private static void Postfix(ItemSlot __instance, ref int change, ItemInstance? __state) { if (__state != null && __instance != null && change < 0) { RestockManager.TryRestockDepletedSlot(__instance, __state); } } } [HarmonyPatch] internal static class ItemSlotUIStartPatch { [CompilerGenerated] private sealed class <>c__DisplayClass1_0 { public Type baseType; internal bool <TargetMethods>b__0(Type type) { return !type.IsAbstract && baseType.IsAssignableFrom(type); } } [CompilerGenerated] private sealed class <>c__DisplayClass1_1 { public string methodName; internal bool <TargetMethods>b__1(MethodInfo m) { return m.Name == methodName; } } [CompilerGenerated] private sealed class <TargetMethods>d__1 : IEnumerable<MethodBase>, IEnumerable, IEnumerator<MethodBase>, IEnumerator, IDisposable { private int <>1__state; private MethodBase <>2__current; private int <>l__initialThreadId; private <>c__DisplayClass1_0 <>8__1; private bool <patchedAny>5__2; private HashSet<MethodBase> <seen>5__3; private IEnumerable<Type> <slotUiTypes>5__4; private IEnumerator<Type> <>s__5; private Type <slotUiType>5__6; private string[] <>s__7; private int <>s__8; private <>c__DisplayClass1_1 <>8__9; private IEnumerable<MethodInfo> <methods>5__10; private IEnumerator<MethodInfo> <>s__11; private MethodInfo <method>5__12; MethodBase IEnumerator<MethodBase>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <TargetMethods>d__1(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if ((uint)(num - -4) <= 1u || num == 1) { try { if (num == -4 || num == 1) { try { } finally { <>m__Finally2(); } } } finally { <>m__Finally1(); } } <>8__1 = null; <seen>5__3 = null; <slotUiTypes>5__4 = null; <>s__5 = null; <slotUiType>5__6 = null; <>s__7 = null; <>8__9 = null; <methods>5__10 = null; <>s__11 = null; <method>5__12 = null; <>1__state = -2; } private bool MoveNext() { try { int num = <>1__state; if (num != 0) { if (num != 1) { return false; } <>1__state = -4; <method>5__12 = null; goto IL_019a; } <>1__state = -1; <>8__1 = new <>c__DisplayClass1_0(); <patchedAny>5__2 = false; <seen>5__3 = new HashSet<MethodBase>(); <>8__1.baseType = typeof(ItemSlotUI); <slotUiTypes>5__4 = from type in <>8__1.baseType.Assembly.GetTypes() where !type.IsAbstract && <>8__1.baseType.IsAssignableFrom(type) select type; <>s__5 = <slotUiTypes>5__4.GetEnumerator(); <>1__state = -3; goto IL_01f4; IL_01f4: if (<>s__5.MoveNext()) { <slotUiType>5__6 = <>s__5.Current; <>s__7 = CandidateMethodNames; <>s__8 = 0; goto IL_01d2; } <>m__Finally1(); <>s__5 = null; if (!<patchedAny>5__2) { SmartRestock.Log.Warning("No compatible ItemSlotUI lifecycle method found to patch."); } return false; IL_01d2: if (<>s__8 < <>s__7.Length) { <>8__9 = new <>c__DisplayClass1_1(); <>8__9.methodName = <>s__7[<>s__8]; <methods>5__10 = from m in <slotUiType>5__6.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) where m.Name == <>8__9.methodName select m; <>s__11 = <methods>5__10.GetEnumerator(); <>1__state = -4; goto IL_019a; } <>s__7 = null; <slotUiType>5__6 = null; goto IL_01f4; IL_019a: while (<>s__11.MoveNext()) { <method>5__12 = <>s__11.Current; if (!<seen>5__3.Add(<method>5__12)) { continue; } <patchedAny>5__2 = true; <>2__current = <method>5__12; <>1__state = 1; return true; } <>m__Finally2(); <>s__11 = null; <methods>5__10 = null; <>8__9 = null; <>s__8++; goto IL_01d2; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<>s__5 != null) { <>s__5.Dispose(); } } private void <>m__Finally2() { <>1__state = -3; if (<>s__11 != null) { <>s__11.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator<MethodBase> IEnumerable<MethodBase>.GetEnumerator() { if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; return this; } return new <TargetMethods>d__1(0); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable<MethodBase>)this).GetEnumerator(); } } private static readonly string[] CandidateMethodNames = new string[2] { "AssignSlot", "Refresh" }; [IteratorStateMachine(typeof(<TargetMethods>d__1))] private static IEnumerable<MethodBase> TargetMethods() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <TargetMethods>d__1(-2); } private static void Postfix(ItemSlotUI __instance) { SmartRestockButtonInjector.Attach(__instance); } } [HarmonyPatch] internal static class LoadingScreenPatch { [HarmonyPatch(typeof(LoadingScreen), "Close")] [HarmonyPostfix] public static void ClosePostfix() { NotificationManager.Initialize(); } [HarmonyPatch(typeof(LoadManager), "ExitToMenu")] [HarmonyPrefix] public static void ExitToMenuPrefix() { NotificationManager.Stop(); } } } namespace SmartRestock.Logic { internal static class Managers { public static NotificationsManager? Notifications { get; private set; } public static TimeManager? Time { get; private set; } public static MoneyManager? Money { get; private set; } public static SaveManager? Save { get; private set; } public static NPCManager? NPCs { get; private set; } public static MessagingManager? Messaging { get; private set; } public static bool TryGetNotifications(out NotificationsManager notifications) { notifications = Notifications; if ((Object)(object)notifications != (Object)null) { return true; } Notifications = Singleton<NotificationsManager>.Instance; notifications = Notifications; if ((Object)(object)notifications == (Object)null) { SmartRestock.Log.Warning("Failed to find NotificationsManager."); return false; } return true; } public static bool TryGetTime(out TimeManager time) { time = Time; if ((Object)(object)time != (Object)null) { return true; } Time = NetworkSingleton<TimeManager>.Instance; time = Time; if ((Object)(object)time == (Object)null) { SmartRestock.Log.Warning("Failed to find TimeManager."); return false; } return true; } public static bool TryGetMoney(out MoneyManager money) { money = Money; if ((Object)(object)money != (Object)null) { return true; } Money = NetworkSingleton<MoneyManager>.Instance; money = Money; if ((Object)(object)money == (Object)null) { SmartRestock.Log.Warning("Failed to find MoneyManager."); return false; } return true; } public static bool TryGetSave(out SaveManager save) { save = Save; if ((Object)(object)save != (Object)null) { return true; } Save = Singleton<SaveManager>.Instance; save = Save; if ((Object)(object)save == (Object)null) { SmartRestock.Log.Warning("Failed to find SaveManager."); return false; } return true; } public static bool TryGetNPCs(out NPCManager npcs) { npcs = NPCs; if ((Object)(object)npcs != (Object)null) { return true; } NPCs = NetworkSingleton<NPCManager>.Instance; npcs = NPCs; if ((Object)(object)npcs == (Object)null) { SmartRestock.Log.Warning("Failed to find NPCManager."); return false; } return true; } public static bool TryGetMessaging(out MessagingManager messaging) { messaging = Messaging; if ((Object)(object)messaging != (Object)null) { return true; } Messaging = NetworkSingleton<MessagingManager>.Instance; messaging = Messaging; if ((Object)(object)messaging == (Object)null) { SmartRestock.Log.Warning("Failed to find MessagingManager."); return false; } return true; } } internal static class NotificationIconAssets { private const string NotificationIconResource = "SmartRestock.Assets.Icons.smartrestock-logo.png"; private static Sprite? _notificationSprite; private static bool _loadAttempted; public static Sprite? GetNotificationIcon() { EnsureLoaded(); return _notificationSprite; } private static void EnsureLoaded() { if (!_loadAttempted) { _loadAttempted = true; _notificationSprite = LoadSpriteFromManifest("SmartRestock.Assets.Icons.smartrestock-logo.png"); } } private static Sprite? LoadSpriteFromManifest(string resourceName) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) Assembly assembly = typeof(NotificationIconAssets).Assembly; using Stream stream = assembly.GetManifestResourceStream(resourceName); if (stream == null) { SmartRestock.Log.Warning("Notification icon resource not found: " + resourceName); return null; } byte[] array = new byte[stream.Length]; stream.Read(array, 0, array.Length); Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); if (!ImageConversion.LoadImage(val, Il2CppStructArray<byte>.op_Implicit(array), false)) { Object.Destroy((Object)(object)val); SmartRestock.Log.Warning("Failed to decode notification icon: " + resourceName); return null; } ((Texture)val).wrapMode = (TextureWrapMode)1; ((Texture)val).filterMode = (FilterMode)1; return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f); } } internal static class NotificationManager { private sealed record LedgerEntry(string Property, string ItemId, string ItemName, int Quantity, float UnitPrice, float TotalCost); private static readonly List<LedgerEntry> _ledger = new List<LedgerEntry>(); private static readonly object _ledgerLock = new object(); private static EDay _ledgerDay; private static NPC? _oscar; private static bool _initialized; private static Action? _onDayPassDelegate; public static void Initialize() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (!_initialized && Managers.TryGetTime(out TimeManager time)) { _ledgerDay = time.CurrentDay; _oscar = ((IEnumerable<NPC>)Object.FindObjectsOfType<NPC>(true)).FirstOrDefault((Func<NPC, bool>)((NPC npc) => npc.ID == "oscar_holland")); _onDayPassDelegate = OnDayPass; TimeManager obj = time; obj.onDayPass += Action.op_Implicit(_onDayPassDelegate); _initialized = true; } } public static void Stop() { if (_initialized) { if (_onDayPassDelegate != null && Managers.TryGetTime(out TimeManager time)) { TimeManager obj = time; obj.onDayPass -= Action.op_Implicit(_onDayPassDelegate); } lock (_ledgerLock) { _ledger.Clear(); } _oscar = null; _onDayPassDelegate = null; _initialized = false; } } public static void LogTransaction(string itemId, string itemName, string? propertyName, int quantity, float unitPrice, float totalCost) { lock (_ledgerLock) { _ledger.Add(new LedgerEntry(propertyName ?? "Unknown", itemId, itemName, quantity, unitPrice, totalCost)); } } public static void NotifyRestocked(string itemName, string? propertyName, int quantity, float totalPrice) { if (Preferences.basePrefs.GetEntry<bool>("enableNotifications").Value && Managers.TryGetNotifications(out NotificationsManager notifications)) { string text = ((propertyName != null) ? ("[" + GetPropertyDisplayName(propertyName) + "] " + itemName) : itemName); string value = MoneyManager.ApplyMoneyTextColor($"${totalPrice:0.##}"); notifications.SendNotification(text, $"Restocked x{quantity} for {value}", NotificationIconAssets.GetNotificationIcon(), 5f, true); } } public static void NotifyFailedRestock(string itemName, string? propertyName, float amount) { if (Preferences.basePrefs.GetEntry<bool>("notifyFailedRestock").Value && Managers.TryGetNotifications(out NotificationsManager notifications)) { string text = ((propertyName != null) ? ("[" + GetPropertyDisplayName(propertyName) + "] Restock Failed") : "Restock Failed"); string value = MoneyManager.ApplyMoneyTextColor($"${amount:0.##}"); notifications.SendNotification(text, $"Not enough funds to restock {itemName} ({value})", NotificationIconAssets.GetNotificationIcon(), 5f, true); } } public static void OnDayPass() { //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown if (!InstanceFinder.IsServer) { return; } if (Preferences.basePrefs.GetEntry<bool>("enableTransactionText").Value) { List<LedgerEntry> list; lock (_ledgerLock) { list = _ledger.ToList(); } if (list.Count > 0 && (Object)(object)_oscar != (Object)null && Managers.TryGetMessaging(out MessagingManager messaging)) { messaging.SendMessage(new Message(GetReceipt(list, _ledgerDay), (ESenderType)1, true, -1), true, _oscar.ID); } } lock (_ledgerLock) { _ledger.Clear(); } if (Managers.TryGetTime(out TimeManager time)) { _ledgerDay = time.CurrentDay; } } private static string GetReceipt(List<LedgerEntry> entries, EDay day) { //IL_010a: Unknown result type (might be due to invalid IL or missing references) Dictionary<string, Dictionary<string, (int, float, string)>> dictionary = new Dictionary<string, Dictionary<string, (int, float, string)>>(); foreach (LedgerEntry entry in entries) { if (!dictionary.TryGetValue(entry.Property, out var value)) { value = (dictionary[entry.Property] = new Dictionary<string, (int, float, string)>()); } if (value.TryGetValue(entry.ItemId, out var value2)) { value[entry.ItemId] = (value2.Item1 + entry.Quantity, value2.Item2 + entry.TotalCost, entry.ItemName); } else { value[entry.ItemId] = (entry.Quantity, entry.TotalCost, entry.ItemName); } } StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(23, 1, stringBuilder2); handler.AppendLiteral("Restock receipt for "); handler.AppendFormatted<EDay>(day); handler.AppendLiteral(":\n\n"); stringBuilder3.Append(ref handler); float num = 0f; foreach (KeyValuePair<string, Dictionary<string, (int, float, string)>> item4 in dictionary) { item4.Deconstruct(out var key, out var value3); string value4 = key; Dictionary<string, (int, float, string)> dictionary3 = value3; float num2 = 0f; stringBuilder2 = stringBuilder; StringBuilder stringBuilder4 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(2, 1, stringBuilder2); handler.AppendFormatted(value4); handler.AppendLiteral(":\n"); stringBuilder4.Append(ref handler); foreach (KeyValuePair<string, (int, float, string)> item5 in dictionary3) { item5.Deconstruct(out key, out var value5); (int, float, string) tuple = value5; int item = tuple.Item1; float item2 = tuple.Item2; string item3 = tuple.Item3; stringBuilder2 = stringBuilder; StringBuilder stringBuilder5 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(11, 3, stringBuilder2); handler.AppendLiteral("• "); handler.AppendFormatted(item3); handler.AppendLiteral(" (x"); handler.AppendFormatted(item); handler.AppendLiteral(") = $"); handler.AppendFormatted(item2, "0.##"); handler.AppendLiteral("\n"); stringBuilder5.Append(ref handler); num2 += item2; } stringBuilder2 = stringBuilder; StringBuilder stringBuilder6 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(33, 1, stringBuilder2); handler.AppendLiteral("-------------\nProperty total: $"); handler.AppendFormatted(num2, "0.##"); handler.AppendLiteral("\n\n"); stringBuilder6.Append(ref handler); num += num2; } stringBuilder2 = stringBuilder; StringBuilder stringBuilder7 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(22, 1, stringBuilder2); handler.AppendLiteral("-------------\nTotal: $"); handler.AppendFormatted(num, "0.##"); stringBuilder7.Append(ref handler); return stringBuilder.ToString(); } private static string GetPropertyDisplayName(string propertyName) { List<Property> properties = Property.Properties; if (properties != null) { Enumerator<Property> enumerator = properties.GetEnumerator(); while (enumerator.MoveNext()) { Property current = enumerator.Current; if ((Object)(object)current != (Object)null && ((Object)current).name == propertyName) { return current.propertyName; } } } return propertyName; } } internal static class RestockManager { private sealed record RestockQuote(ItemInstance Template, int Quantity, float UnitPrice, float TotalPrice); [CompilerGenerated] private sealed class <InsertItemNextFrame>d__18 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public ItemSlot slot; public RestockQuote quote; public ItemInstance source; public string propertyName; public nint slotKey; private Exception <ex>5__1; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <InsertItemNextFrame>d__18(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <ex>5__1 = null; <>1__state = -2; } private bool MoveNext() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForEndOfFrame(); <>1__state = 1; return true; case 1: <>1__state = -1; try { if (!IsSlotAlive(slot)) { SmartRestock.Log.Warning("Auto-restock aborted: slot was destroyed before item could be inserted (" + ((BaseItemInstance)quote.Template).Name + ")."); return false; } ((BaseItemInstance)quote.Template).SetQuantity(quote.Quantity); slot.AddItem(quote.Template, false); if (slot.Quantity <= 0) { SmartRestock.Log.Warning("Auto-restock could not insert " + ((BaseItemInstance)quote.Template).Name + " after purchase."); return false; } NotificationManager.NotifyRestocked(((BaseItemInstance)quote.Template).Name, propertyName, quote.Quantity, quote.TotalPrice); NotificationManager.LogTransaction(((BaseItemInstance)source).ID, ((BaseItemInstance)source).Name, propertyName, quote.Quantity, quote.UnitPrice, quote.TotalPrice); if (Preferences.AdditionalLogging) { SmartRestock.Log.Msg($"Auto-restocked {((BaseItemInstance)quote.Template).Name} x{quote.Quantity} for ${quote.TotalPrice:0.##}."); } } catch (Exception ex) { <ex>5__1 = ex; SmartRestock.Log.Warning("Auto-restock insert failed: " + <ex>5__1.Message); } finally { PendingRestocks.Remove(slotKey); } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static readonly ConcurrentDictionary<nint, bool> EnabledSlots = new ConcurrentDictionary<IntPtr, bool>(); private static readonly ConcurrentDictionary<nint, SlotKey> RuntimeSlotKeys = new ConcurrentDictionary<IntPtr, SlotKey>(); private static readonly ConcurrentDictionary<nint, ItemSlot> RuntimeSlots = new ConcurrentDictionary<IntPtr, ItemSlot>(); private static readonly HashSet<string> WhitelistedCategories = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Agriculture", "Consumable", "Ingredient", "Packaging" }; private static readonly HashSet<string> WhitelistedItems = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "speedgrow" }; private static readonly HashSet<string> BlacklistedItems = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "cocaleaf", "cocainebase", "liquidmeth", "shroomspawn" }; private static readonly HashSet<string> CashOnlyItems = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "cocaseed", "granddaddypurpleseed", "greencrackseed", "ogkushseed", "sourdieselseed", "sporesyringe", "lowqualitypseudo", "pseudo", "highqualitypseudo" }; private static readonly HashSet<nint> PendingRestocks = new HashSet<IntPtr>(); private const float CleanupIntervalSeconds = 20f; private static float _lastCleanupAt; public static void NotifyToggleChanged(ItemSlotUI slotUi, SlotKey slotKey, bool enabled) { ItemSlot val = TryResolveItemSlot(slotUi); if (val != null) { nint slotRuntimeKey = GetSlotRuntimeKey(val); if (slotRuntimeKey != 0) { RuntimeSlotKeys[slotRuntimeKey] = slotKey; RuntimeSlots[slotRuntimeKey] = val; EnabledSlots[slotRuntimeKey] = enabled; } } } public static void SyncSlotBinding(ItemSlotUI slotUi, SlotKey slotKey) { ItemSlot val = TryResolveItemSlot(slotUi); if (val != null) { nint slotRuntimeKey = GetSlotRuntimeKey(val); if (slotRuntimeKey != 0) { RuntimeSlotKeys[slotRuntimeKey] = slotKey; RuntimeSlots[slotRuntimeKey] = val; EnabledSlots[slotRuntimeKey] = SmartRestockStateStore.Get(slotKey); } } } public static void Tick() { SmartRestockStateStore.FlushIfPending(); float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup - _lastCleanupAt < 20f)) { _lastCleanupAt = realtimeSinceStartup; CleanupDeletedContainers(); } } public static void EvaluateSlot(ItemSlotUI slotUi) { SlotKey? slotKey = SlotIdentity.TryResolve(slotUi); if (slotKey.HasValue && SmartRestockStateStore.Get(slotKey.Value)) { SyncSlotBinding(slotUi, slotKey.Value); } } public static bool TryRestockAfterDepletion(ItemSlot slot, int deltaQuantity) { try { if (slot == null || deltaQuantity >= 0) { return false; } nint slotRuntimeKey = GetSlotRuntimeKey(slot); if (slotRuntimeKey == 0 || !EnabledSlots.TryGetValue(slotRuntimeKey, out var value) || !value) { return false; } ItemInstance itemInstance = slot.ItemInstance; if (itemInstance == null) { return false; } int num = slot.Quantity + deltaQuantity; if (num > 0) { return false; } RestockQuote restockQuote = BuildQuote(itemInstance); if (restockQuote == null) { return false; } string propertyName = SlotIdentity.TryGetPropertyName(slot); if (!TryChargePlayer(restockQuote.TotalPrice, ((BaseItemDefinition)restockQuote.Template.Definition).Name, propertyName)) { return false; } ((BaseItemInstance)restockQuote.Template).SetQuantity(restockQuote.Quantity); slot.AddItem(restockQuote.Template, false); NotificationManager.NotifyRestocked(((BaseItemInstance)restockQuote.Template).Name, propertyName, restockQuote.Quantity, restockQuote.TotalPrice); NotificationManager.LogTransaction(((BaseItemInstance)itemInstance).ID, ((BaseItemInstance)itemInstance).Name, propertyName, restockQuote.Quantity, restockQuote.UnitPrice, restockQuote.TotalPrice); return true; } catch (Exception ex) { SmartRestock.Log.Warning("Auto-restock failed: " + ex.Message); return false; } } public static bool IsSlotEnabled(ItemSlot slot) { if (slot == null) { return false; } nint slotRuntimeKey = GetSlotRuntimeKey(slot); bool value = default(bool); return slotRuntimeKey != 0 && EnabledSlots.TryGetValue(slotRuntimeKey, out value) && value; } public static bool TryRestockDepletedSlot(ItemSlot slot, ItemInstance sourceBeforeDepletion) { nint num = 0; try { if (slot == null || sourceBeforeDepletion == null) { return false; } if (!IsSlotEnabled(slot)) { return false; } if (slot.Quantity > 0) { return false; } num = GetSlotRuntimeKey(slot); if (num == 0 || !PendingRestocks.Add(num)) { return false; } if (!IsRestockable(sourceBeforeDepletion)) { if (Preferences.AdditionalLogging) { SmartRestock.Log.Msg("Auto-restock skipped: " + ((BaseItemInstance)sourceBeforeDepletion).ID + " is not restockable (blacklisted, wrong category, or missing definition)."); } PendingRestocks.Remove(num); return false; } RestockQuote restockQuote = BuildQuote(sourceBeforeDepletion); if (restockQuote == null) { SmartRestock.Log.Warning($"Auto-restock skipped: could not build quote for {((BaseItemInstance)sourceBeforeDepletion).ID} (stackLimit={((BaseItemInstance)sourceBeforeDepletion).StackLimit}, GetCopy returned null?)."); PendingRestocks.Remove(num); return false; } if (!slot.DoesItemMatchPlayerFilters(restockQuote.Template)) { if (Preferences.AdditionalLogging) { SmartRestock.Log.Msg("Auto-restock skipped: " + ((BaseItemInstance)restockQuote.Template).Name + " does not match slot player filters."); } PendingRestocks.Remove(num); return false; } if (slot.GetCapacityForItem(restockQuote.Template, true) <= 0) { if (Preferences.AdditionalLogging) { SmartRestock.Log.Msg("Auto-restock skipped: slot has no capacity for " + ((BaseItemInstance)restockQuote.Template).Name + "."); } PendingRestocks.Remove(num); return false; } string propertyName = SlotIdentity.TryGetPropertyName(slot); if (!TryChargePlayer(restockQuote.TotalPrice, ((BaseItemDefinition)restockQuote.Template.Definition).Name, propertyName)) { PendingRestocks.Remove(num); return false; } MelonCoroutines.Start(InsertItemNextFrame(slot, restockQuote, sourceBeforeDepletion, propertyName, num)); return true; } catch (Exception ex) { if (num != 0) { PendingRestocks.Remove(num); } SmartRestock.Log.Warning("Auto-restock (post-depletion) failed: " + ex.Message); return false; } } [IteratorStateMachine(typeof(<InsertItemNextFrame>d__18))] private static IEnumerator InsertItemNextFrame(ItemSlot slot, RestockQuote quote, ItemInstance source, string? propertyName, nint slotKey) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <InsertItemNextFrame>d__18(0) { slot = slot, quote = quote, source = source, propertyName = propertyName, slotKey = slotKey }; } private static RestockQuote? BuildQuote(ItemInstance source) { int num = ((BaseItemInstance)source).StackLimit; if (Preferences.basePrefs.GetEntry<int>("restockAmount").Value > 0) { num = Preferences.basePrefs.GetEntry<int>("restockAmount").Value; } if (num <= 0) { return null; } ItemInstance copy = source.GetCopy(1); if (copy == null) { return null; } float num2 = ((BaseItemInstance)source).GetMonetaryValue() * 2f / (float)Math.Max(((BaseItemInstance)source).Quantity, 1); float totalPrice = num2 * (float)num; return new RestockQuote(copy, num, num2, totalPrice); } private static bool IsRestockable(ItemInstance item) { //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) if (item == null) { return false; } string iD = ((BaseItemInstance)item).ID; if (string.IsNullOrWhiteSpace(iD)) { return false; } if (Preferences.dangerPrefs.GetEntry<bool>("restockAnyItem").Value) { return true; } if (BlacklistedItems.Contains(iD)) { return false; } if (Preferences.basePrefs.GetEntry<bool>("restockSeeds").Value && CashOnlyItems.Contains(iD)) { return true; } if (CashOnlyItems.Contains(iD)) { return false; } if (WhitelistedItems.Contains(iD)) { return true; } ItemDefinition definition = item.Definition; object obj; if (definition == null) { obj = null; } else { EItemCategory category = ((BaseItemDefinition)definition).Category; obj = ((object)(EItemCategory)(ref category)).ToString(); } string text = (string)obj; return text != null && WhitelistedCategories.Contains(text); } private static void CleanupDeletedContainers() { try { int num = 0; KeyValuePair<IntPtr, ItemSlot>[] array = RuntimeSlots.ToArray(); for (int i = 0; i < array.Length; i++) { KeyValuePair<IntPtr, ItemSlot> keyValuePair = array[i]; if (!IsSlotAlive(keyValuePair.Value)) { RuntimeSlotKeys.TryGetValue(keyValuePair.Key, out var value); RuntimeSlots.TryRemove(keyValuePair.Key, out ItemSlot _); EnabledSlots.TryRemove(keyValuePair.Key, out var _); RuntimeSlotKeys.TryRemove(keyValuePair.Key, out var _); if (Preferences.AdditionalLogging) { SmartRestock.Log.Msg($"Auto-restock: evicted dead slot {value} from runtime tracking."); } num++; } } if (num != 0) { } } catch (Exception ex) { SmartRestock.Log.Warning("Auto-restock cleanup failed: " + ex.Message); } } private static bool IsSlotAlive(ItemSlot slot) { if (slot == null) { return false; } try { return slot.SlotOwner != null; } catch { return false; } } private static bool TryChargePlayer(float amount, string itemName, string? propertyName) { if (amount <= 0f) { return true; } MoneyManager instance = NetworkSingleton<MoneyManager>.Instance; if ((Object)(object)instance == (Object)null) { return false; } if (Preferences.dangerPrefs.GetEntry<bool>("useCashForRestock").Value) { if (instance.cashBalance < amount) { SmartRestock.Log.Msg($"Insufficient cash to auto-restock {itemName} (${amount:0.##})."); NotificationManager.NotifyFailedRestock(itemName, propertyName, amount); return false; } instance.ChangeCashBalance(0f - amount, true, true); return true; } if (instance.onlineBalance < amount) { SmartRestock.Log.Msg($"Insufficient funds to auto-restock {itemName} (${amount:0.##})."); NotificationManager.NotifyFailedRestock(itemName, propertyName, amount); return false; } instance.CreateOnlineTransaction("Auto Restock", 0f - amount, 1f, itemName); return true; } private static ItemSlot? TryResolveItemSlot(ItemSlotUI slotUi) { return SlotIdentity.TryResolveAssignedSlot(slotUi); } private static nint GetSlotRuntimeKey(ItemSlot slot) { if (slot != null) { return ((Il2CppObjectBase)slot).Pointer; } return IntPtr.Zero; } } }