using System; using System.Collections; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Configuration; using Extensions; using HarmonyLib; using Mirror; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.InputSystem.UI; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace DayRetry; [BepInPlugin("com.adam.dayretry", "Day Retry", "1.0.4")] public sealed class DayRetryPlugin : BaseUnityPlugin { [HarmonyPatch(typeof(MoneyManager), "SetDayStartBalance")] private static class SetDayStartBalancePatch { private static void Postfix() { CaptureDayStart(NetworkSingleton.Instance); } } [HarmonyPatch(typeof(GameManager), "ProgressGame")] private static class ProgressGamePatch { [HarmonyPriority(800)] private static bool Prefix(GameManager __instance) { return !TryHoldQuotaFailure(__instance); } } [HarmonyPatch(typeof(GameManager), "ShowGameOverStats")] private static class ShowGameOverStatsPatch { private static void Prefix(GameManager __instance) { ArmLossRetryFromGameOverStats(__instance); } } [HarmonyPatch(typeof(GameOverUI), "Show")] private static class GameOverUiShowPatch { private static void Postfix(GameOverUI __instance) { InstallRestartButton(__instance); } } public const string PluginGuid = "com.adam.dayretry"; public const string PluginName = "Day Retry"; public const string PluginVersion = "1.0.4"; private static readonly object SnapshotLock = new object(); private static ConfigEntry _enabled; private static ConfigEntry _showDebugLogs; private static ConfigEntry _buttonAnchor; private static ConfigEntry _snapshotBackupsToKeep; private static ConfigEntry _restartHoldDuration; private static ConfigEntry _showVerificationLogs; private static Harmony _harmony; private static DayRetryPlugin _instance; private string _snapshotDirectory; private string _latestSnapshotPath; private string _latestSaveName; private int _latestSnapshotDaysPassed = -1; private long _latestSnapshotMoney; private GameManager _pendingGameManager; private bool _waitingForHostChoice; private bool _restoreInProgress; private bool _continueVanillaProgression; private bool _lossRetryAvailable; private GameObject _restartButtonObject; private Image _holdFillImage; private TextMeshProUGUI _restartButtonLabel; private TextMeshProUGUI _holdPromptLabel; private float _restartHoldProgress; private bool _hasCursorOverride; private bool _previousCursorVisible; private CursorLockMode _previousCursorLockState; private Rect _windowRect; private GUIStyle _windowStyle; private GUIStyle _titleStyle; private GUIStyle _bodyStyle; private GUIStyle _primaryButtonStyle; private GUIStyle _secondaryButtonStyle; private Texture2D _cursorTexture; public static bool IsEnabled { get { if (_enabled != null) { return _enabled.Value; } return false; } } private void Awake() { //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Expected O, but got Unknown _instance = this; _enabled = ((BaseUnityPlugin)this).Config.Bind("General", "Enabled", true, "Enable day-start retry after missing quota."); _showDebugLogs = ((BaseUnityPlugin)this).Config.Bind("General", "ShowDebugLogs", true, "Write detailed DayRetry messages to the BepInEx log."); _buttonAnchor = ((BaseUnityPlugin)this).Config.Bind("UI", "ButtonAnchor", "Center", "Restart prompt anchor: Center, Top, Bottom."); _snapshotBackupsToKeep = ((BaseUnityPlugin)this).Config.Bind("Snapshots", "SnapshotBackupsToKeep", 3, "Number of per-save day-start snapshots to retain."); _restartHoldDuration = ((BaseUnityPlugin)this).Config.Bind("UI", "RestartHoldDuration", 1.35f, "Seconds the host must hold E to restart the failed day."); _showVerificationLogs = ((BaseUnityPlugin)this).Config.Bind("Debug", "ShowVerificationLogs", true, "Write high-signal DayRetry verification values for snapshot and restore testing."); _snapshotDirectory = Path.Combine(Paths.ConfigPath, "DayRetry", "snapshots"); Directory.CreateDirectory(_snapshotDirectory); _windowRect = new Rect(((float)Screen.width - 460f) * 0.5f, ((float)Screen.height - 230f) * 0.5f, 460f, 230f); _harmony = new Harmony("com.adam.dayretry"); _harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"DayRetry loaded"); } private void OnDestroy() { if (_harmony != null) { _harmony.UnpatchSelf(); _harmony = null; } if ((Object)(object)_instance == (Object)(object)this) { _instance.ReleaseCursorControl(); _instance = null; } } private void Update() { if (_waitingForHostChoice && !_restoreInProgress && NetworkServer.active && NetworkClient.active) { TakeCursorControl(); } if (_lossRetryAvailable && (Object)(object)_restartButtonObject == (Object)null && !_restoreInProgress && NetworkServer.active && NetworkClient.active) { GameOverUI val = Object.FindAnyObjectByType(); if ((Object)(object)val != (Object)null) { CreateRestartButton(val); } } UpdateRestartHoldProgress(); } private void LateUpdate() { if (_waitingForHostChoice && !_restoreInProgress && NetworkServer.active && NetworkClient.active) { TakeCursorControl(); } } internal static void CaptureDayStart(GameManager gameManager) { if (!((Object)(object)_instance == (Object)null) && IsEnabled && !((Object)(object)gameManager == (Object)null) && NetworkServer.active) { _instance.CaptureDayStartSnapshot(); } } internal static bool TryHoldQuotaFailure(GameManager gameManager) { if ((Object)(object)_instance == (Object)null || !IsEnabled || (Object)(object)gameManager == (Object)null || !NetworkServer.active) { return false; } if (!NetworkClient.active) { _instance.LogWarning("Quota failure detected on a server without a local host client. DayRetry needs the lobby host UI, so vanilla loss flow will continue."); return false; } if (_instance._continueVanillaProgression) { _instance._continueVanillaProgression = false; return false; } if (!IsQuotaFailure(gameManager)) { return false; } _instance.LogVerify("LOSS_DETECTED " + _instance.FormatRuntimeState(gameManager, NetworkSingleton.Instance)); if (!_instance.HasUsableSnapshot()) { _instance.LogWarning("Quota failure detected, but no usable day-start snapshot was available. Allowing vanilla loss flow. " + _instance.FormatSnapshotPointer()); return false; } if (_instance._waitingForHostChoice) { return true; } _instance._pendingGameManager = gameManager; _instance._lossRetryAvailable = true; _instance._waitingForHostChoice = false; _instance._restoreInProgress = false; _instance.LogInfo("Quota failure detected. Restart button will be added to the loss stats menu."); return false; } private static bool IsQuotaFailure(GameManager gameManager) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 try { if ((int)gameManager.state != 1) { return false; } MoneyManager instance = NetworkSingleton.Instance; return gameManager.daysLeft <= 1 && (Object)(object)instance != (Object)null && instance.balance < gameManager.currentQuota; } catch (Exception ex) { if ((Object)(object)_instance != (Object)null) { _instance.LogWarning("Failed to evaluate quota failure: " + ex); } return false; } } private void CaptureDayStartSnapshot() { lock (SnapshotLock) { _lossRetryAvailable = false; DestroyRestartButton(); SaveManager instance = NetworkSingleton.Instance; if ((Object)(object)instance == (Object)null) { LogWarning("SaveManager is not available; cannot snapshot day start."); return; } string currentSaveName = instance.CurrentSaveName; if (string.IsNullOrEmpty(currentSaveName)) { LogWarning("Current save name is empty; cannot snapshot day start."); return; } string saveDirectory = GetSaveDirectory(instance); string text = Path.Combine(saveDirectory, currentSaveName + ".json"); string text2 = MakeSafeFileName(currentSaveName); string text3 = DateTimeOffset.UtcNow.ToString("yyyyMMdd-HHmmss"); string text4 = Path.Combine(_snapshotDirectory, text2 + "-" + text3 + ".json"); string text5 = Path.Combine(_snapshotDirectory, text2 + ".active-before-capture.tmp"); bool flag = File.Exists(text); try { Directory.CreateDirectory(saveDirectory); Directory.CreateDirectory(_snapshotDirectory); if (flag) { File.Copy(text, text5, overwrite: true); } LogVerify("CAPTURE_BEGIN save='" + currentSaveName + "' activeSave='" + text + "' " + FormatRuntimeState(NetworkSingleton.Instance, NetworkSingleton.Instance)); instance.SaveGame(); if (!File.Exists(text)) { LogWarning("SaveGame did not produce an active save file; snapshot skipped."); return; } File.Copy(text, text4, overwrite: true); _latestSnapshotMoney = ForceSnapshotDayStartMoney(text4); _latestSnapshotPath = text4; _latestSaveName = currentSaveName; GameManager instance2 = NetworkSingleton.Instance; _latestSnapshotDaysPassed = (((Object)(object)instance2 != (Object)null) ? instance2.daysPassed : (-1)); PruneSnapshots(text2); LogInfo("Captured day-start snapshot for save '" + currentSaveName + "' with money " + _latestSnapshotMoney + ": " + text4); LogVerify("CAPTURE_SAVED " + FormatSnapshotData(text4)); } catch (Exception ex) { LogWarning("Failed to capture day-start snapshot: " + ex); } finally { try { if (flag && File.Exists(text5)) { File.Copy(text5, text, overwrite: true); } else if (!flag && File.Exists(text)) { File.Delete(text); } if (File.Exists(text5)) { File.Delete(text5); } } catch (Exception ex2) { LogWarning("Failed to restore active save after snapshot capture: " + ex2); } } } } private bool HasUsableSnapshot() { if (string.IsNullOrEmpty(_latestSnapshotPath) || !File.Exists(_latestSnapshotPath)) { return false; } GameManager val = (((Object)(object)_pendingGameManager != (Object)null) ? _pendingGameManager : NetworkSingleton.Instance); if ((Object)(object)val == (Object)null || _latestSnapshotDaysPassed < 0) { return true; } int num = val.daysPassed - _latestSnapshotDaysPassed; if (num >= 0) { return num <= 1; } return false; } private IEnumerator RestoreDayRoutine() { if (_restoreInProgress) { yield break; } _restoreInProgress = true; bool restored = false; try { restored = RestoreSnapshotToGame(); } catch (Exception ex) { LogWarning("Restore failed: " + ex); } if (restored) { _waitingForHostChoice = false; _lossRetryAvailable = false; _pendingGameManager = null; ReleaseCursorControl(); DestroyRestartButton(); yield return null; NetworkManager networkManager = NetworkManager.singleton; if ((Object)(object)networkManager != (Object)null && NetworkServer.active) { GameManager instance = NetworkSingleton.Instance; if ((Object)(object)instance != (Object)null) { TrySetField(instance, "_isTransitioning", true); } LogVerify("SCENE_RELOAD CasinoScene " + FormatRuntimeState(instance, NetworkSingleton.Instance)); networkManager.ServerChangeScene("CasinoScene"); LogInfo("Restarted failed day from snapshot."); } else { LogWarning("NetworkManager was not available after restore."); } } else { LogWarning("Snapshot restore failed. Continuing vanilla loss flow."); ContinueVanillaLoss(); } _restoreInProgress = false; } private bool RestoreSnapshotToGame() { if (!HasUsableSnapshot()) { return false; } SaveManager instance = NetworkSingleton.Instance; GameManager val = (((Object)(object)_pendingGameManager != (Object)null) ? _pendingGameManager : NetworkSingleton.Instance); if ((Object)(object)instance == (Object)null || (Object)(object)val == (Object)null) { LogWarning("Required managers are missing during restore."); return false; } string text = (string.IsNullOrEmpty(_latestSaveName) ? instance.CurrentSaveName : _latestSaveName); if (string.IsNullOrEmpty(text)) { LogWarning("No save name available during restore."); return false; } string text2 = File.ReadAllText(_latestSnapshotPath); SaveData val2 = JsonUtility.FromJson(text2); if (val2 == null) { LogWarning("Snapshot JSON could not be parsed."); return false; } val2.saveName = text; if (_latestSnapshotMoney > 0) { val2.money = _latestSnapshotMoney; } LogVerify("RESTORE_BEGIN snapshot='" + _latestSnapshotPath + "' " + FormatSaveData(val2) + " runtimeBefore=" + FormatRuntimeState(val, NetworkSingleton.Instance)); string text3 = Path.Combine(GetSaveDirectory(instance), text + ".json"); Directory.CreateDirectory(Path.GetDirectoryName(text3)); File.WriteAllText(text3, JsonUtility.ToJson((object)val2, true)); AccessTools.Field(typeof(SaveManager), "currentSaveData").SetValue(instance, val2); AccessTools.Field(typeof(SaveManager), "hasLoadedGame").SetValue(instance, false); instance.LoadGame(); MoneyManager instance2 = NetworkSingleton.Instance; if ((Object)(object)instance2 != (Object)null) { instance2.SetBalance(val2.money, (PlayerProfile)null, (ChangeType)3); instance2.TrySetTicketBalance(val2.tickets); TrySetProperty(instance2, "NetworkdayStartBalance", val2.money); LogInfo("Forced restored day-start money to " + val2.money + "."); } val.Networkstate = (GameState)1; val.Network_timer = 0f; TrySetField(val, "_hasDayStarted", false); TrySetField(val, "k__BackingField", false); TrySetProperty(val, "HasDayStarted", false); TrySetField(val, "_isTimeOver", false); TrySetField(val, "_isTransitioning", false); LogVerify("RESTORE_APPLIED " + FormatRuntimeState(val, NetworkSingleton.Instance) + " activeSave='" + text3 + "'"); return true; } private void ContinueVanillaLoss() { GameManager val = (((Object)(object)_pendingGameManager != (Object)null) ? _pendingGameManager : NetworkSingleton.Instance); _waitingForHostChoice = false; _lossRetryAvailable = false; _pendingGameManager = null; ReleaseCursorControl(); DestroyRestartButton(); if (!((Object)(object)val == (Object)null)) { _continueVanillaProgression = true; val.ProgressGame(); } } internal static void InstallRestartButton(GameOverUI gameOverUI) { if ((Object)(object)_instance == (Object)null || !IsEnabled || (Object)(object)gameOverUI == (Object)null || !NetworkServer.active || !NetworkClient.active) { if ((Object)(object)_instance != (Object)null) { _instance.LogInfo("Skipped Restart Day button install because this instance is not the local host."); } } else if (!_instance._lossRetryAvailable || !_instance.HasUsableSnapshot()) { _instance.LogInfo("Skipped Restart Day button install. RetryAvailable=" + _instance._lossRetryAvailable + ", HasSnapshot=" + _instance.HasUsableSnapshot()); } else { _instance.CreateRestartButton(gameOverUI); } } internal static void ArmLossRetryFromGameOverStats(GameManager gameManager) { if (!((Object)(object)_instance == (Object)null) && IsEnabled && !((Object)(object)gameManager == (Object)null) && NetworkServer.active) { if (!_instance.HasSnapshotFile()) { _instance.LogWarning("Game over stats opened, but no day-start snapshot file exists."); return; } _instance._pendingGameManager = gameManager; _instance._lossRetryAvailable = true; _instance._waitingForHostChoice = false; _instance._restoreInProgress = false; _instance.LogInfo("Game over stats opened. Armed Restart Day button."); _instance.LogVerify("LOSS_STATS_ARMED " + _instance.FormatRuntimeState(gameManager, NetworkSingleton.Instance) + " " + _instance.FormatSnapshotPointer()); } } private void UpdateRestartHoldProgress() { if ((Object)(object)_restartButtonObject == (Object)null || !_lossRetryAvailable || _restoreInProgress || !NetworkServer.active || !NetworkClient.active) { _restartHoldProgress = 0f; UpdateHoldFillVisual(); return; } Keyboard current = Keyboard.current; bool flag = current != null && current.eKey != null && ((ButtonControl)current.eKey).isPressed; float num = Mathf.Max(0.15f, (_restartHoldDuration != null) ? _restartHoldDuration.Value : 1.35f); if (flag) { _restartHoldProgress = Mathf.Min(1f, _restartHoldProgress + Time.unscaledDeltaTime / num); } else { _restartHoldProgress = 0f; } UpdateHoldFillVisual(); if (_restartHoldProgress >= 1f && !_restoreInProgress) { _restartHoldProgress = 0f; LogVerify("HOLD_COMPLETE " + FormatRuntimeState(NetworkSingleton.Instance, NetworkSingleton.Instance) + " " + FormatSnapshotPointer()); if ((Object)(object)_restartButtonLabel != (Object)null) { ((TMP_Text)_restartButtonLabel).text = "Restarting..."; } if ((Object)(object)_holdPromptLabel != (Object)null) { ((TMP_Text)_holdPromptLabel).text = ""; } ((MonoBehaviour)this).StartCoroutine(RestoreDayRoutine()); } } private void UpdateHoldFillVisual() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_holdFillImage == (Object)null)) { RectTransform rectTransform = ((Graphic)_holdFillImage).rectTransform; rectTransform.anchorMin = new Vector2(0f, 0f); rectTransform.anchorMax = new Vector2(_restartHoldProgress, 1f); rectTransform.offsetMin = Vector2.zero; rectTransform.offsetMax = Vector2.zero; } } private void CreateRestartButton(GameOverUI gameOverUI) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Expected O, but got Unknown //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_0255: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02a4: Expected O, but got Unknown //IL_02de: Unknown result type (might be due to invalid IL or missing references) //IL_0325: Unknown result type (might be due to invalid IL or missing references) //IL_032c: Expected O, but got Unknown //IL_034a: Unknown result type (might be due to invalid IL or missing references) //IL_0356: Unknown result type (might be due to invalid IL or missing references) //IL_0362: Unknown result type (might be due to invalid IL or missing references) //IL_036e: Unknown result type (might be due to invalid IL or missing references) //IL_03af: Unknown result type (might be due to invalid IL or missing references) //IL_03f4: Unknown result type (might be due to invalid IL or missing references) //IL_03fb: Expected O, but got Unknown //IL_0423: Unknown result type (might be due to invalid IL or missing references) //IL_0439: Unknown result type (might be due to invalid IL or missing references) //IL_044f: Unknown result type (might be due to invalid IL or missing references) //IL_0465: Unknown result type (might be due to invalid IL or missing references) //IL_047b: Unknown result type (might be due to invalid IL or missing references) //IL_04e8: Unknown result type (might be due to invalid IL or missing references) DestroyRestartButton(); EnsureEventSystem(); GameObject val = new GameObject("DayRetry_RestartDayCanvas", new Type[4] { typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster) }); Object.DontDestroyOnLoad((Object)(object)val); Canvas component = val.GetComponent(); component.renderMode = (RenderMode)0; component.sortingOrder = 32000; CanvasScaler component2 = val.GetComponent(); component2.uiScaleMode = (ScaleMode)1; component2.referenceResolution = new Vector2(1920f, 1080f); component2.matchWidthOrHeight = 0.5f; _restartButtonObject = val; _restartHoldProgress = 0f; GameObject val2 = new GameObject("RestartDayButton", new Type[4] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(Button) }); val2.transform.SetParent(val.transform, false); RectTransform component3 = val2.GetComponent(); component3.anchorMin = new Vector2(0.5f, 0f); component3.anchorMax = new Vector2(0.5f, 0f); component3.pivot = new Vector2(0.5f, 0.5f); component3.anchoredPosition = new Vector2(0f, 150f); component3.sizeDelta = new Vector2(320f, 68f); Image component4 = val2.GetComponent(); ((Graphic)component4).color = new Color(0.06f, 0.62f, 0.38f, 0.96f); Button component5 = val2.GetComponent