using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using HarmonyLib; using Il2CppFishNet; using Il2CppFishNet.Object; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.GameTime; using Il2CppScheduleOne.Money; using Il2CppScheduleOne.Persistence; using Il2CppScheduleOne.Property; using Il2CppScheduleOne.UI; using Il2CppScheduleOne.UI.Phone; using Il2CppScheduleOne.Variables; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using LaundryApp; using LaundryApp.App; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Core), "Laundry App", "1.1.4", "DazUki", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: AssemblyMetadata("NexusModID", "1455")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("LaundryApp")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.1.4.0")] [assembly: AssemblyInformationalVersion("1.0.0+38fadcb4602ed0babdf17a3908b933ca01c7976e")] [assembly: AssemblyProduct("LaundryApp")] [assembly: AssemblyTitle("LaundryApp")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.4.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.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LaundryApp { public class Core : MelonMod { [CompilerGenerated] private sealed class d__8 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Core <>4__this; private int 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__8(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Expected O, but got Unknown int num = <>1__state; Core core = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; 5__2 = 0; break; case 1: <>1__state = -1; break; } if (!PlayerSingleton.InstanceExists || !PlayerSingleton.InstanceExists) { 5__2++; if (5__2 % 50 == 1) { ((MelonBase)core).LoggerInstance.Msg($"Core: waiting for phone UI, elapsed={(float)5__2 * 0.1f:F0}s"); } <>2__current = (object)new WaitForSeconds(0.1f); <>1__state = 1; return true; } try { ((MelonBase)core).LoggerInstance.Msg("Core: phone UI ready, creating app"); core._laundryApp = new LaundryApp(); core._laundryApp.Initialize(); ((MelonBase)core).LoggerInstance.Msg("Core: initialized successfully"); } catch (Exception ex) { ((MelonBase)core).LoggerInstance.Error("Core: failed to initialize, error=" + ex.Message); ((MelonBase)core).LoggerInstance.Error("Core: stack trace=" + ex.StackTrace); } 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 bool _initialized; private LaundryApp _laundryApp; public static Core Instance { get; private set; } public override void OnInitializeMelon() { Instance = this; Settings.Initialize(); ((MelonBase)this).LoggerInstance.Msg("Core: loaded"); } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { if (sceneName == "Menu") { _initialized = false; _laundryApp?.Cleanup(); _laundryApp = null; } else if (sceneName == "Main" && !_initialized) { _initialized = true; MelonCoroutines.Start(InitializeLaundryApp()); } } [IteratorStateMachine(typeof(d__8))] private IEnumerator InitializeLaundryApp() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__8(0) { <>4__this = this }; } public override void OnUpdate() { _laundryApp?.Update(); } } public static class Settings { private static MelonPreferences_Category s_category; public static MelonPreferences_Entry ShowProgressCircle { get; private set; } public static MelonPreferences_Entry ProgressCircleColorLerp { get; private set; } public static MelonPreferences_Entry ShowPauseIcon { get; private set; } public static MelonPreferences_Entry AutoCapAmount { get; private set; } public static MelonPreferences_Entry ShowBalanceSummary { get; private set; } public static MelonPreferences_Entry AutoLaunderMorning { get; private set; } public static MelonPreferences_Entry DebugLogging { get; private set; } public static void Initialize() { s_category = MelonPreferences.CreateCategory("LaundryApp", "Laundry App Settings"); ShowProgressCircle = s_category.CreateEntry("ShowProgressCircle", true, "Show Progress Circle", "Show a progress circle per business.", false, false, (ValueValidator)null, (string)null); ProgressCircleColorLerp = s_category.CreateEntry("ProgressCircleColorLerp", true, "Progress Circle Colored", "Color circles red-to-green by completion.", false, false, (ValueValidator)null, (string)null); ShowPauseIcon = s_category.CreateEntry("ShowPauseIcon", true, "Show Pause Icon", "Show a pause icon when laundering is paused at 4 AM.", false, false, (ValueValidator)null, (string)null); AutoCapAmount = s_category.CreateEntry("AutoCapAmount", false, "Auto-Cap Amount", "Pre-fill the max launderable amount.", false, false, (ValueValidator)null, (string)null); ShowBalanceSummary = s_category.CreateEntry("ShowBalanceSummary", true, "Show Balance Summary", "Show bank balance and expected balance after ops.", false, false, (ValueValidator)null, (string)null); AutoLaunderMorning = s_category.CreateEntry("AutoLaunderMorning", false, "Auto-Launder", "Launder max cash into businesses every 5 min.", false, false, (ValueValidator)null, (string)null); DebugLogging = s_category.CreateEntry("DebugLogging", false, "Debug Logging", "Log extra info for troubleshooting.", false, false, (ValueValidator)null, (string)null); } } public class LaundryApp { private readonly UI _ui; private BusinessView _businessView; private bool _isOpen; private bool _wasPhoneOpen; private float _lastRefreshTime; private float _lastAddButtonClickTime = -2f; private Action _onMinutePassCallback; private Action _onSleepStartCallback; private int _minutePassCount; private bool _wasSleepInProgress; private bool _autoLaunderedThisSleep; private readonly List _ownedBusinesses = new List(); private readonly Sprite _autoLaunderIcon = BusinessView.LoadEmbeddedSprite("autoicon.png"); public LaundryApp() { _ui = new UI(); } public void Initialize() { _ui.Initialize(); if (_ui.BusinessListContainer == null || _ui.CircleSprite == null) { ((MelonBase)Core.Instance).LoggerInstance.Error("UI components are null, aborting init"); return; } _businessView = new BusinessView(_ui.BusinessListContainer, _ui.CircleSprite, OnBusinessListItemClicked); RegisterEventHandlers(); ((MelonBase)Core.Instance).LoggerInstance.Msg("initialized"); } private void RegisterEventHandlers() { UI.RegisterIconClickHandler(OnAppIconClicked); OperationsView operationsView = _ui.OperationsView; if (operationsView != null) { Button startOperationButton = operationsView.StartOperationButton; if (startOperationButton != null) { ((UnityEvent)startOperationButton.onClick).AddListener(UnityAction.op_Implicit((Action)OnStartOperationClicked)); } } OperationsView operationsView2 = _ui.OperationsView; if (operationsView2 != null) { InputField amountInput = operationsView2.AmountInput; if (amountInput != null) { ((UnityEvent)(object)amountInput.onValueChanged).AddListener(UnityAction.op_Implicit((Action)delegate { ValidateAmountInput(); })); } } OperationsView operationsView3 = _ui.OperationsView; if (operationsView3 != null) { Dropdown businessDropdown = operationsView3.BusinessDropdown; if (businessDropdown != null) { ((UnityEvent)(object)businessDropdown.onValueChanged).AddListener(UnityAction.op_Implicit((Action)OnBusinessDropdownChanged)); } } OperationsView operationsView4 = _ui.OperationsView; if (operationsView4 != null) { Button maxButton = operationsView4.MaxButton; if (maxButton != null) { ((UnityEvent)maxButton.onClick).AddListener(UnityAction.op_Implicit((Action)OnMaxButtonClicked)); } } GameInput.RegisterExitListener(ExitDelegate.op_Implicit((Action)OnExitAction), 1); if (PlayerSingleton.InstanceExists) { Phone instance = PlayerSingleton.Instance; instance.closeApps += Action.op_Implicit((Action)Close); } } private void ValidateAmountInput() { //IL_00ca: 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_00cf: Unknown result type (might be due to invalid IL or missing references) OperationsView operationsView = _ui.OperationsView; if (!((Object)(object)operationsView?.AmountInput == (Object)null) && !((Object)(object)operationsView?.StartOperationButton == (Object)null)) { string text = operationsView.AmountInput.text; bool flag = false; if (int.TryParse(text, out var result) && result >= 100) { Dropdown businessDropdown = operationsView.BusinessDropdown; int dropdownIndex = ((businessDropdown != null) ? businessDropdown.value : (-1)); float availableCapacity = GetAvailableCapacity(dropdownIndex); MoneyManager instance = NetworkSingleton.Instance; float num = ((instance != null) ? instance.cashBalance : 0f); flag = result <= Mathf.FloorToInt(num) && result <= Mathf.FloorToInt(availableCapacity); } ((Selectable)operationsView.StartOperationButton).interactable = flag; Image image = ((Selectable)operationsView.StartOperationButton).image; if (image != null) { ((Graphic)image).color = Color32.op_Implicit(flag ? Constants.BUTTON_BACKGROUND_COLOR : Constants.BUTTON_DISABLED_COLOR); } } } private float GetAvailableCapacity(int dropdownIndex) { if (dropdownIndex < 0 || dropdownIndex >= _ownedBusinesses.Count) { return 0f; } Business val = _ownedBusinesses[dropdownIndex]; return val.LaunderCapacity - val.currentLaunderTotal; } private int GetMaxLaunderAmount(int dropdownIndex) { float availableCapacity = GetAvailableCapacity(dropdownIndex); if (availableCapacity <= 0f) { return 0; } MoneyManager instance = NetworkSingleton.Instance; float num = ((instance != null) ? instance.cashBalance : 0f); return Mathf.FloorToInt(Mathf.Min(availableCapacity, num)); } private void OnAppIconClicked() { if (_isOpen) { Close(); } else { Open(); } } private void OnMaxButtonClicked() { OperationsView operationsView = _ui?.OperationsView; if (operationsView?.BusinessDropdown != null && operationsView?.AmountInput != null) { int value = operationsView.BusinessDropdown.value; int maxLaunderAmount = GetMaxLaunderAmount(value); if (maxLaunderAmount >= 100) { operationsView.AmountInput.text = maxLaunderAmount.ToString(); } } } private void PopulateBusinessDropdown() { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown OperationsView operationsView = _ui.OperationsView; if ((Object)(object)operationsView?.BusinessDropdown == (Object)null) { return; } _ownedBusinesses.Clear(); List ownedBusinesses = Business.OwnedBusinesses; List val = new List(); for (int i = 0; i < ownedBusinesses.Count; i++) { Business val2 = ownedBusinesses[i]; if ((Object)(object)val2 != (Object)null) { _ownedBusinesses.Add(val2); OptionData val3 = new OptionData { text = ((Property)val2).PropertyName }; val.Add(val3); } } operationsView.BusinessDropdown.ClearOptions(); operationsView.BusinessDropdown.AddOptions(val); operationsView.BusinessDropdown.value = 0; UpdateCapacityDisplay(0); } private void OnBusinessDropdownChanged(int index) { UpdateCapacityDisplay(index); } private void OnBusinessListItemClicked(int index) { OperationsView operationsView = _ui.OperationsView; if (operationsView?.BusinessDropdown != null && index >= 0 && index < operationsView.BusinessDropdown.options.Count) { operationsView.BusinessDropdown.value = index; } } private void SubscribeToTimeEvents() { if (_onMinutePassCallback == null && NetworkSingleton.InstanceExists) { TimeManager instance = NetworkSingleton.Instance; _onMinutePassCallback = OnMinutePass; instance.onMinutePass += Action.op_Implicit(_onMinutePassCallback); _onSleepStartCallback = OnSleepStart; instance.onSleepStart += Action.op_Implicit(_onSleepStartCallback); if (Settings.DebugLogging.Value) { ((MelonBase)Core.Instance).LoggerInstance.Msg("auto-launder: subscribed to onMinutePass + onSleepStart"); } } } private void OnSleepStart() { if (Settings.AutoLaunderMorning.Value && !_autoLaunderedThisSleep) { _autoLaunderedThisSleep = true; if (Settings.DebugLogging.Value) { ((MelonBase)Core.Instance).LoggerInstance.Msg("auto-launder: sleep started trigger, running auto-launder"); } CheckAutoLaunder(); } } private void PollSleepStart() { if (!NetworkSingleton.InstanceExists) { return; } bool isSleepInProgress = NetworkSingleton.Instance.IsSleepInProgress; if (isSleepInProgress && !_wasSleepInProgress) { if (Settings.DebugLogging.Value) { ((MelonBase)Core.Instance).LoggerInstance.Msg("auto-launder: poll detected sleep start (IsSleepInProgress true)"); } OnSleepStart(); } else if (!isSleepInProgress && _wasSleepInProgress) { _autoLaunderedThisSleep = false; } _wasSleepInProgress = isSleepInProgress; } private void OnMinutePass() { if (Settings.AutoLaunderMorning.Value) { _minutePassCount++; if (_minutePassCount >= 5) { _minutePassCount = 0; CheckAutoLaunder(); } } } private void CheckAutoLaunder() { //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Expected O, but got Unknown if (!NetworkSingleton.InstanceExists || !Singleton.InstanceExists || !Singleton.Instance.IsGameLoaded) { return; } try { int currentTime = NetworkSingleton.Instance.CurrentTime; MoneyManager instance = NetworkSingleton.Instance; if (instance == null) { ((MelonBase)Core.Instance).LoggerInstance.Error($"auto-launder: MoneyManager is null, time={currentTime}"); return; } if (!((NetworkBehaviour)instance).IsClientInitialized) { ((MelonBase)Core.Instance).LoggerInstance.Warning($"auto-launder: FishNet not ready, will retry next tick, time={currentTime}"); return; } List ownedBusinesses = Business.OwnedBusinesses; if (ownedBusinesses == null || ownedBusinesses.Count == 0) { if (Settings.DebugLogging.Value) { ((MelonBase)Core.Instance).LoggerInstance.Msg($"auto-launder: no owned businesses, skipping, time={currentTime}"); } return; } VariableDatabase instance2 = NetworkSingleton.Instance; Sprite val = _autoLaunderIcon ?? instance.LaunderingNotificationIcon; int num = 0; float num2 = 0f; List list = (Settings.DebugLogging.Value ? new List() : null); for (int i = 0; i < ownedBusinesses.Count; i++) { Business val2 = ownedBusinesses[i]; if (val2 == null) { continue; } float num3 = val2.LaunderCapacity - val2.currentLaunderTotal; float cashBalance = instance.cashBalance; int num4 = Mathf.FloorToInt(Mathf.Min(num3, cashBalance)); if (num4 < 100) { list?.Add($"{((Property)val2).PropertyName}(avail=${num3:F0})"); continue; } instance.ChangeCashBalance((float)(-num4), true, true); LaunderingOperation val3 = new LaunderingOperation(val2, (float)num4, 0); val2.LaunderingOperations.Add(val3); ((Property)val2).HasChanged = true; num++; num2 += (float)num4; if (instance2 != null) { float value = instance2.GetValue("LaunderingOperationsStarted"); instance2.SetVariableValue("LaunderingOperationsStarted", (value + 1f).ToString(), true); } Singleton.Instance.SendNotification(((Property)val2).PropertyName, "Auto-laundering " + MoneyManager.FormatAmount((float)num4, false, false) + "", val, 5f, true); } if (num > 0) { ((MelonBase)Core.Instance).LoggerInstance.Msg($"auto-launder: started {num} operations, total=${num2:F0}, time={currentTime}"); } else if (list != null && list.Count > 0) { ((MelonBase)Core.Instance).LoggerInstance.Msg($"auto-launder: all skipped, time={currentTime}, cash=${instance.cashBalance:F0} - {string.Join(", ", list)}"); } } catch (Exception ex) { ((MelonBase)Core.Instance).LoggerInstance.Error("auto-launder: error=" + ex.Message); } } private void UpdateCapacityDisplay(int businessIndex) { if (businessIndex >= 0 && businessIndex < _ownedBusinesses.Count) { ValidateAmountInput(); } } private void UpdateBalanceSummary() { //IL_0084: Unknown result type (might be due to invalid IL or missing references) bool flag = _ownedBusinesses.Count > 0; bool flag2 = flag && Settings.ShowBalanceSummary.Value; if ((Object)(object)_ui.BalanceSummaryRow != (Object)null) { _ui.BalanceSummaryRow.SetActive(flag2); } if ((Object)(object)_ui.BusinessListScrollRect != (Object)null) { float num = ((!flag) ? 0.89f : (flag2 ? 0.74f : 0.79f)); _ui.BusinessListScrollRect.anchorMax = new Vector2(0.92f, num); } if (!flag2) { return; } MoneyManager instance = NetworkSingleton.Instance; if (instance == null) { return; } float onlineBalance = instance.onlineBalance; if ((Object)(object)_ui.BalanceText != (Object)null) { _ui.BalanceText.text = $"${onlineBalance:N0}"; } float num2 = 0f; int num3 = 0; List ownedBusinesses = Business.OwnedBusinesses; if (ownedBusinesses != null) { for (int i = 0; i < ownedBusinesses.Count; i++) { Business val = ownedBusinesses[i]; if (val == null) { continue; } List launderingOperations = val.LaunderingOperations; for (int j = 0; j < launderingOperations.Count; j++) { LaunderingOperation val2 = launderingOperations[j]; if (val2 != null) { num2 += val2.amount; int num4 = val2.completionTime_Minutes - val2.minutesSinceStarted; if (num4 > num3) { num3 = num4; } } } } } bool flag3 = num2 > 0f; if ((Object)(object)_ui.ExpectedBalanceText != (Object)null) { if (flag3) { int num5 = num3 / 60; int value = num3 % 60; string value2 = ((num5 > 0) ? $"{num5}h" : $"{value}m"); float value3 = onlineBalance + num2; string value4 = "#5EFF5E"; _ui.ExpectedBalanceText.text = $"{value2} ยป ${value3:N0}"; } else { _ui.ExpectedBalanceText.text = $"${onlineBalance:N0}"; } } } private void OnStartOperationClicked() { OperationsView operationsView = _ui.OperationsView; if (Time.time - _lastAddButtonClickTime < 2f) { if ((Object)(object)operationsView?.StartOperationButtonText != (Object)null) { operationsView.StartOperationButtonText.text = "..."; } return; } if (operationsView?.BusinessDropdown == null || operationsView?.AmountInput == null) { ((MelonBase)Core.Instance).LoggerInstance.Error("OperationsView components are null"); return; } int value = operationsView.BusinessDropdown.value; if (value < 0 || value >= _ownedBusinesses.Count) { ((MelonBase)Core.Instance).LoggerInstance.Warning("invalid business selection"); return; } Business val = _ownedBusinesses[value]; if (!int.TryParse(operationsView.AmountInput.text, out var result) || result < 100) { ((MelonBase)Core.Instance).LoggerInstance.Warning("invalid amount"); return; } MoneyManager instance = NetworkSingleton.Instance; if (instance == null) { ((MelonBase)Core.Instance).LoggerInstance.Error("MoneyManager is null"); return; } int maxLaunderAmount = GetMaxLaunderAmount(value); if (result > maxLaunderAmount) { ((MelonBase)Core.Instance).LoggerInstance.Warning($"amount={result} exceeds max={maxLaunderAmount}"); return; } try { if (Settings.DebugLogging.Value) { ((MelonBase)Core.Instance).LoggerInstance.Msg($"manual launder: business={((Property)val).PropertyName}, amount=${result}, cash=${instance.cashBalance:F0}"); } instance.ChangeCashBalance((float)(-result), true, true); val.StartLaunderingOperation((float)result, 0); _lastAddButtonClickTime = Time.time; if ((Object)(object)operationsView?.StartOperationButtonText != (Object)null) { operationsView.StartOperationButtonText.text = "..."; } VariableDatabase instance2 = NetworkSingleton.Instance; if (instance2 != null) { float value2 = instance2.GetValue("LaunderingOperationsStarted"); instance2.SetVariableValue("LaunderingOperationsStarted", (value2 + 1f).ToString(), true); } _businessView.RefreshBusinessList(); } catch (Exception value3) { ((MelonBase)Core.Instance).LoggerInstance.Error($"start operation failed, error={value3}"); } } public void Open() { if (!_isOpen && Phone.ActiveApp == null) { _isOpen = true; _ui.AppContainer.SetActive(true); Phone.ActiveApp = _ui.AppContainer; PlayerSingleton.Instance.SetIsOpen(false); PlayerSingleton.Instance.SetIsOpen(true); PlayerSingleton.Instance.SetIsHorizontal(false); PlayerSingleton.Instance.SetLookOffsetMultiplier(1f); _businessView.InvalidateCache(); _businessView.RefreshBusinessList(); PopulateBusinessDropdown(); } } public void Close() { if (_isOpen) { _isOpen = false; if ((Object)(object)_ui.AppContainer != (Object)null) { _ui.AppContainer.SetActive(false); } if ((Object)(object)Phone.ActiveApp == (Object)(object)_ui.AppContainer) { Phone.ActiveApp = null; } if (PlayerSingleton.InstanceExists) { PlayerSingleton.Instance.SetIsOpen(true); } if (PlayerSingleton.InstanceExists) { PlayerSingleton.Instance.SetIsHorizontal(false); PlayerSingleton.Instance.SetLookOffsetMultiplier(1f); } } } public void Cleanup() { Close(); OperationsView operationsView = _ui.OperationsView; if (operationsView != null) { Button startOperationButton = operationsView.StartOperationButton; if (startOperationButton != null) { ((UnityEventBase)startOperationButton.onClick).RemoveAllListeners(); } } OperationsView operationsView2 = _ui.OperationsView; if (operationsView2 != null) { InputField amountInput = operationsView2.AmountInput; if (amountInput != null) { ((UnityEventBase)amountInput.onValueChanged).RemoveAllListeners(); } } OperationsView operationsView3 = _ui.OperationsView; if (operationsView3 != null) { InputField amountInput2 = operationsView3.AmountInput; if (amountInput2 != null) { ((UnityEventBase)amountInput2.onEndEdit).RemoveAllListeners(); } } OperationsView operationsView4 = _ui.OperationsView; if (operationsView4 != null) { Dropdown businessDropdown = operationsView4.BusinessDropdown; if (businessDropdown != null) { ((UnityEventBase)businessDropdown.onValueChanged).RemoveAllListeners(); } } OperationsView operationsView5 = _ui.OperationsView; if (operationsView5 != null) { Button maxButton = operationsView5.MaxButton; if (maxButton != null) { ((UnityEventBase)maxButton.onClick).RemoveAllListeners(); } } if (PlayerSingleton.InstanceExists) { Phone instance = PlayerSingleton.Instance; instance.closeApps -= Action.op_Implicit((Action)Close); } if ((_onMinutePassCallback != null || _onSleepStartCallback != null) && NetworkSingleton.InstanceExists) { TimeManager instance2 = NetworkSingleton.Instance; if (_onMinutePassCallback != null) { instance2.onMinutePass -= Action.op_Implicit(_onMinutePassCallback); } if (_onSleepStartCallback != null) { instance2.onSleepStart -= Action.op_Implicit(_onSleepStartCallback); } if (Settings.DebugLogging.Value) { ((MelonBase)Core.Instance).LoggerInstance.Msg("auto-launder: unsubscribed from onMinutePass + onSleepStart"); } } _onMinutePassCallback = null; _onSleepStartCallback = null; _ownedBusinesses.Clear(); } private void OnExitAction(ExitAction exit) { if (exit != null && !exit.Used && _isOpen && PlayerSingleton.InstanceExists && PlayerSingleton.Instance.IsOpen) { exit.Used = true; Close(); } } public void Update() { //IL_0089: 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_00d1: Invalid comparison between Unknown and I4 //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) SubscribeToTimeEvents(); PollSleepStart(); bool flag = PlayerSingleton.InstanceExists && PlayerSingleton.Instance.IsOpen; if (_isOpen && flag && !_wasPhoneOpen) { _businessView.InvalidateCache(); PopulateBusinessDropdown(); } _wasPhoneOpen = flag; if (!_isOpen) { return; } _businessView?.UpdatePauseIconPulse(); _businessView?.UpdateAutoIconRotation(); if ((Object)(object)_ui.BusinessListScroll != (Object)null && Input.mouseScrollDelta.y != 0f) { _ui.BusinessListScroll.movementType = (MovementType)2; } else if ((Object)(object)_ui.BusinessListScroll != (Object)null && (int)_ui.BusinessListScroll.movementType == 2) { _ui.BusinessListScroll.movementType = (MovementType)1; } if (PlayerSingleton.Instance.IsOpen && IsHoveringHomeButton() && GameInput.GetButtonDown((ButtonCode)0)) { Close(); } OperationsView operationsView = _ui.OperationsView; if (Time.time - _lastRefreshTime >= 0.1f) { _lastRefreshTime = Time.time; bool flag2 = _businessView.RefreshBusinessList(); if ((Object)(object)_ui.OperationsFormContainer != (Object)null) { _ui.OperationsFormContainer.SetActive(flag2); } if (flag2 && (Object)(object)operationsView?.BusinessDropdown != (Object)null) { UpdateCapacityDisplay(operationsView.BusinessDropdown.value); } UpdateBalanceSummary(); if (flag2) { EnforceAutoCapAmount(); } if ((Object)(object)operationsView?.MaxButton != (Object)null) { bool value = Settings.AutoCapAmount.Value; ((Component)operationsView.MaxButton).gameObject.SetActive(!value); Button startOperationButton = operationsView.StartOperationButton; RectTransform val = ((startOperationButton != null) ? ((Component)startOperationButton).GetComponent() : null); if (val != null) { float num = (value ? 0.02f : 0.53f); val.anchorMin = new Vector2(num, val.anchorMin.y); } } } if ((Object)(object)operationsView?.StartOperationButtonText != (Object)null && operationsView.StartOperationButtonText.text == "..." && Time.time - _lastAddButtonClickTime >= 2f) { operationsView.StartOperationButtonText.text = "Add"; } } private void EnforceAutoCapAmount() { OperationsView operationsView = _ui.OperationsView; if (Settings.AutoCapAmount.Value && !((Object)(object)operationsView?.AmountInput == (Object)null) && !((Object)(object)operationsView?.BusinessDropdown == (Object)null)) { int value = operationsView.BusinessDropdown.value; int maxLaunderAmount = GetMaxLaunderAmount(value); string text = Mathf.Max(100, maxLaunderAmount).ToString(); if (operationsView.AmountInput.text != text) { operationsView.AmountInput.text = text; } } } private static bool IsHoveringHomeButton() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) RaycastHit val = default(RaycastHit); if (Physics.Raycast(Singleton.Instance.OverlayCamera.ScreenPointToRay(Input.mousePosition), ref val, 2f, 1 << LayerMask.NameToLayer("Overlay"))) { return ((Object)((Component)((RaycastHit)(ref val)).collider).gameObject).name == "Button"; } return false; } } [HarmonyPatch(typeof(MoneyManager), "ReceiveOnlineTransaction")] public static class ReceiveOnlineTransactionPatch { [HarmonyPrefix] public static bool Prefix(MoneyManager __instance, string _transaction_Name, float _unit_Amount, float _quantity, string _transaction_Note) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown if (!InstanceFinder.IsServer) { return true; } try { Transaction val = new Transaction(_transaction_Name, _unit_Amount, _quantity, _transaction_Note); __instance.ledger.Add(val); float num = __instance.sync___get_value_onlineBalance() + val.total_Amount; __instance.sync___set_value_onlineBalance(num, true); if (Singleton.InstanceExists) { Singleton.Instance.OnlineBalanceDisplay.SetBalance(num); } __instance.HasChanged = true; } catch (Exception ex) { ((MelonBase)Core.Instance).LoggerInstance.Error("ReceiveOnlineTransactionPatch: error=" + ex.Message); return true; } return false; } } } namespace LaundryApp.App { public static class Constants { public const float REFRESH_INTERVAL = 0.1f; public const int HEADER_FONT_SIZE = 48; public const int BUSINESS_NAME_FONT_SIZE = 44; public const int CAPACITY_FONT_SIZE = 36; public const int OPERATION_FONT_SIZE = 38; public const int MESSAGE_FONT_SIZE = 34; public const float BUSINESS_NAME_HEIGHT = 50f; public const float CAPACITY_HEIGHT = 40f; public const float OPERATION_HEIGHT = 50f; public const float BUSINESS_SPACING = 14f; public const float OPERATIONS_TOP_SPACING = 6f; public const float BUSINESS_INFO_PADDING = 8f; public const float CIRCLE_SIZE = 40f; public const float CIRCLE_LEFT_OFFSET = 25f; public const int CIRCLE_TEXTURE_SIZE = 64; public const float CIRCLE_TEXTURE_RADIUS = 30f; public static readonly Color32 BACKGROUND_COLOR = new Color32((byte)20, (byte)20, (byte)20, byte.MaxValue); public static readonly Color32 BUSINESS_BACKGROUND_COLOR = new Color32((byte)40, (byte)40, (byte)40, byte.MaxValue); public static readonly Color32 CIRCLE_BACKGROUND_COLOR = new Color32((byte)10, (byte)10, (byte)10, byte.MaxValue); public static readonly Color CIRCLE_PROGRESS_START = new Color(0.9f, 0.2f, 0.2f, 1f); public static readonly Color CIRCLE_PROGRESS_MID = new Color(1f, 0.6f, 0.2f, 1f); public static readonly Color CIRCLE_PROGRESS_END = new Color(0.3f, 0.9f, 0.3f, 1f); public static readonly Color32 BUTTON_BACKGROUND_COLOR = new Color32(byte.MaxValue, (byte)184, (byte)93, byte.MaxValue); public static readonly Color32 MAX_BUTTON_BACKGROUND_COLOR = new Color32((byte)33, (byte)33, (byte)33, byte.MaxValue); public static readonly Color32 BUTTON_DISABLED_COLOR = new Color32(byte.MaxValue, (byte)103, (byte)93, byte.MaxValue); public static readonly Color32 BUTTON_TEXT_COLOR = new Color32((byte)0, (byte)0, (byte)0, byte.MaxValue); public static readonly Color32 WHITE_TEXT_COLOR = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); public static readonly Color32 PLACEHOLDER_TEXT_COLOR = new Color32((byte)60, (byte)60, (byte)60, byte.MaxValue); public static readonly Color32 INPUT_BACKGROUND_COLOR = new Color32((byte)43, (byte)43, (byte)43, byte.MaxValue); public const string COLOR_LIGHT_GREEN = "#5EFF5E"; public const string COLOR_LIGHT_ORANGE = "#FFB85D"; public const string COLOR_ONLINE_BALANCE_BLUE = "#4CBFFF"; public const float SUMMARY_ROW_HEIGHT = 50f; public const int SUMMARY_FONT_SIZE = 32; public const float PAUSE_ICON_SIZE = 48f; public const float PAUSE_ICON_OFFSET = 4f; public const float PAUSE_PULSE_SPEED = 4f; public const float PAUSE_PULSE_MIN_ALPHA = 0.3f; public const string PAUSE_ICON_FILENAME = "pauseicon.png"; public const string AUTO_ICON_FILENAME = "autoicon.png"; public const float AUTO_ICON_SIZE = 48f; public const float AUTO_ICON_OFFSET = 4f; public const float AUTO_ICON_ROTATE_SPEED = 50f; public const string APP_NAME = "Laundry"; public const string APP_TITLE = "Laundry Operations"; public const string ICON_FILENAME = "appicon.png"; public const string LOGO_FILENAME = "applogo.png"; public const string NO_BUSINESSES_MESSAGE = "No businesses available"; public const string NO_OPERATIONS_MESSAGE = "No ongoing operations"; public const int MIN_LAUNDER_AMOUNT = 100; public const int INPUT_CHARACTER_LIMIT = 7; public const string ICON_OBJECT_NAME = "LaundryAppIcon"; public const string CONTAINER_OBJECT_NAME = "LaundryApp"; public const string DEALER_APP_NAME = "DealerManagement"; public const string DELIVERY_APP_NAME = "Delivery"; public const string PRODUCT_MANAGER_APP_NAME = "ProductManager"; public const string COUNTEROFFER_APP_NAME = "Counteroffer"; public static Font ArialFont { get; } = Resources.GetBuiltinResource("Arial.ttf"); } public class UI { public GameObject OperationsFormContainer { get; private set; } public GameObject AppContainer { get; private set; } public Text HeaderText { get; private set; } public GameObject BusinessListContainer { get; private set; } public Sprite CircleSprite { get; private set; } public OperationsView OperationsView { get; private set; } public Text BalanceText { get; private set; } public Text ExpectedBalanceText { get; private set; } public GameObject BalanceSummaryRow { get; private set; } public RectTransform BusinessListScrollRect { get; private set; } public ScrollRect BusinessListScroll { get; private set; } public void Initialize() { CreateCircleSprite(); CreateAppIcon(); CreateAppUI(); } private void CreateCircleSprite() { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Expected O, but got Unknown //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(64, 64); Color[] array = (Color[])(object)new Color[4096]; Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(32f, 32f); for (int i = 0; i < 64; i++) { for (int j = 0; j < 64; j++) { float num = Vector2.Distance(new Vector2((float)j, (float)i), val2); array[i * 64 + j] = ((num <= 30f) ? Color.white : Color.clear); } } val.SetPixels(Il2CppStructArray.op_Implicit(array)); val.Apply(); CircleSprite = Sprite.Create(val, new Rect(0f, 0f, 64f, 64f), new Vector2(0.5f, 0.5f)); } private static void CreateAppIcon() { //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) HomeScreen instance = PlayerSingleton.Instance; if (instance == null) { MelonLogger.Error("UI: HomeScreen not found"); return; } RectTransform appIconContainer = instance.appIconContainer; GameObject appIconPrefab = instance.appIconPrefab; if (appIconContainer == null || appIconPrefab == null) { MelonLogger.Error("UI: icon container or prefab is null"); return; } GameObject obj = Object.Instantiate(appIconPrefab, ((Component)appIconContainer).transform); ((Object)obj).name = "LaundryAppIcon"; Transform obj2 = obj.transform.Find("Label"); Text val = ((obj2 != null) ? ((Component)obj2).GetComponent() : null); if (val != null) { val.text = "Laundry"; } Transform obj3 = obj.transform.Find("Mask/Image"); Image val2 = ((obj3 != null) ? ((Component)obj3).GetComponent() : null); if (val2 != null) { Sprite val3 = LoadEmbeddedSprite("appicon.png"); val2.sprite = val3 ?? val2.sprite; if (val3 == null) { ((Graphic)val2).color = Color32.op_Implicit(new Color32((byte)45, (byte)45, (byte)45, byte.MaxValue)); } } Transform val4 = obj.transform.Find("Notifications"); if (val4 != null) { ((Component)val4).gameObject.SetActive(false); } MelonLogger.Msg("UI: app icon created"); } public static void RegisterIconClickHandler(Action onClickHandler) { if (onClickHandler == null) { MelonLogger.Error("UI: icon click handler is null"); return; } HomeScreen instance = PlayerSingleton.Instance; if (instance == null) { MelonLogger.Error("UI: HomeScreen instance is null"); return; } Transform val = ((Component)instance.appIconContainer).transform.Find("LaundryAppIcon"); if (val == null) { MelonLogger.Error("UI: icon transform not found"); return; } Button component = ((Component)val).GetComponent