Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of TimeNeverStops Mono v1.5.0
TimeNeverStops_Mono.dll
Decompiled a month agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using FishNet; using FishNet.Managing.Transporting; using FishNet.Object; using FishNet.Transporting; using FishNet.Transporting.Multipass; using FishNet.Transporting.Tugboat; using HarmonyLib; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using S1API.PhoneApp; using S1API.UI; using ScheduleOne; using ScheduleOne.Core.Items.Framework; using ScheduleOne.DevUtilities; using ScheduleOne.GameTime; using ScheduleOne.ItemFramework; using ScheduleOne.Levelling; using ScheduleOne.Map; using ScheduleOne.Money; using ScheduleOne.Networking; using ScheduleOne.Persistence; using ScheduleOne.PlayerScripts; using ScheduleOne.UI; using ScheduleOne.UI.Input; using SteamNetworkLib; using SteamNetworkLib.Events; using Time_Never_Stops; 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(Core), "Time Never Stops", "1.5.0", "DropDaDeuce", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("")] [assembly: AssemblyCompany("TimeNeverStops_Mono")] [assembly: AssemblyConfiguration("MONO")] [assembly: AssemblyFileVersion("1.5.0.0")] [assembly: AssemblyInformationalVersion("1.5.0+6481184e8b48f14747b88ae129580a3d361cdaf2")] [assembly: AssemblyProduct("TimeNeverStops_Mono")] [assembly: AssemblyTitle("TimeNeverStops_Mono")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.5.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Time_Never_Stops { internal static class TNSLog { private static bool DebugEnabled => Core.cfgDebugLogging?.Value ?? false; public static void Msg(string message) { MelonLogger.Msg(message); } public static void Msg(string format, params object[] args) { MelonLogger.Msg(string.Format(format, args)); } public static void Debug(string message) { if (DebugEnabled) { MelonLogger.Msg("[Debug] " + message); } } public static void Debug(string format, params object[] args) { if (DebugEnabled) { MelonLogger.Msg("[Debug] " + string.Format(format, args)); } } public static void Warning(string message) { MelonLogger.Warning(message); } public static void Warning(string format, params object[] args) { MelonLogger.Warning(string.Format(format, args)); } public static void Error(string message) { MelonLogger.Error(message); } public static void Error(string format, params object[] args) { MelonLogger.Error(string.Format(format, args)); } } public class Core : MelonMod { private static MelonPreferences_Category cfgCategory; public static MelonPreferences_Entry<string> cfgDaySpeedStr; public static MelonPreferences_Entry<bool> cfgEnableSummaryAwake; public static MelonPreferences_Entry<bool> cfgEnableSummaryApp; public static MelonPreferences_Entry<bool> cfgSilentSleep; public static MelonPreferences_Entry<bool> cfgLocalMultiplayer; public static MelonPreferences_Entry<float> cfgLegacyDaySpeed; public static MelonPreferences_Entry<bool> cfgMultiplierOnly; public static MelonPreferences_Entry<bool> cfgDebugLogging; public static MelonPreferences_Entry<int> cfgDailySummaryCutoffHHMM; private bool _updatingPref; private bool _lastEnableSummaryAwake; internal static bool SkipWakeFastForwardOnce; public const float DefaultSpeed = 1f; private const float MinSpeed = 0.1f; private const float MaxSpeed = 100f; private FileSystemWatcher _cfgWatcher; private volatile bool _cfgFileChanged; private TimeSyncManager? _timeSyncManager; private bool _timeSyncInitialized; private bool _timeSyncRetrying; private object? _timeSyncUpdateHandle; internal static bool IsHost => (Object)(object)Player.Local != (Object)null && ((NetworkBehaviour)Player.Local).IsHost; internal static bool SilentSleepActive { get { MelonPreferences_Entry<bool> obj = cfgMultiplierOnly; int result; if (obj == null || !obj.Value) { MelonPreferences_Entry<bool> obj2 = cfgSilentSleep; if (obj2 != null && obj2.Value) { result = (DailySummaryPatch.ShouldSuppressSummaryUI() ? 1 : 0); goto IL_0031; } } result = 0; goto IL_0031; IL_0031: return (byte)result != 0; } } public static float CurrentDaySpeed => Sanitize(ParseFloatOrDefault(cfgDaySpeedStr?.Value, 1f)); internal static int DailySummaryCutoffHHMM => SanitizeHHMM(cfgDailySummaryCutoffHHMM?.Value ?? 800); public override void OnInitializeMelon() { MelonPreferences_Category val = MelonPreferences.CreateCategory("TimeNeverStops"); cfgLegacyDaySpeed = val.GetEntry<float>("DaySpeedMultiplier") ?? val.CreateEntry<float>("DaySpeedMultiplier", 1f, "Day Speed Multiplier", "Sets the in-game time speed multiplier. 1.0 = normal speed, 0.5 = half speed, 2.0 = double speed.Minimum 0.1, Maximum 3.0. Default = 1.0 (Vanilla Game Speed)", false, false, (ValueValidator)null, (string)null); val.DeleteEntry("DaySpeedMultiplier"); if (cfgLegacyDaySpeed.Value != 1f) { TNSLog.Warning($"Legacy DaySpeedMultiplier found with value {cfgLegacyDaySpeed.Value}. Migrating to new config system."); } string text = Math.Clamp(cfgLegacyDaySpeed.Value, 0.1f, 100f).ToString("R", CultureInfo.InvariantCulture); string text2 = Path.Combine(MelonEnvironment.UserDataDirectory, "DropDaDeuce-TimeNeverStops"); string text3 = Path.Combine(text2, "TimeNeverStops.cfg"); Directory.CreateDirectory(text2); cfgCategory = val; val = null; cfgCategory.SetFilePath(text3, File.Exists(text3), true); cfgMultiplierOnly = cfgCategory.GetEntry<bool>("TimeMultiplierOnlyMode") ?? cfgCategory.CreateEntry<bool>("TimeMultiplierOnlyMode", false, "Multiplier Only Mode", "If true the mod ONLY changes the day speed multiplier.\nDisables:\n- 4AM freeze bypass\n- Daily summary while awake\n- Sleep prompt hiding.\nChange at runtime is applied but a restart is safest.", false, false, (ValueValidator)null, (string)null); cfgDaySpeedStr = cfgCategory.GetEntry<string>("DaySpeedMultiplier") ?? cfgCategory.CreateEntry<string>("DaySpeedMultiplier", text, "Day Speed Multiplier", "String parsed to float by the mod.\n1.0 = normal, 0.5 = half, 2.0 = double.\nMin 0.1, Max 100.0, Default 1.0.", false, false, (ValueValidator)null, (string)null); cfgEnableSummaryAwake = cfgCategory.GetEntry<bool>("EnableDailySummaryAwake") ?? cfgCategory.CreateEntry<bool>("EnableDailySummaryAwake", true, "Enable Daily Summary While Awake", "If enabled, the daily summary popup will be shown while awake (unless Multiplier Only Mode is ON).\nIgnored while the Daily Summary phone app is active — the app replaces the popup.", false, false, (ValueValidator)null, (string)null); cfgEnableSummaryApp = cfgCategory.GetEntry<bool>("EnableDailySummaryApp") ?? cfgCategory.CreateEntry<bool>("EnableDailySummaryApp", true, "Enable Daily Summary Phone App", "If enabled, yesterday's summary is available on demand from a phone app instead of\ninterrupting you with the popup. Requires S1API. If S1API is missing or the app fails\nto load, the popup is used instead so the summary is never lost.", false, false, (ValueValidator)null, (string)null); cfgSilentSleep = cfgCategory.GetEntry<bool>("EnableSilentSleep") ?? cfgCategory.CreateEntry<bool>("EnableSilentSleep", true, "Enable Silent Sleep", "If enabled, the nightly sleep sequence runs with no screen takeover at all:\nno fade to black, no HUD drop, no cursor, no waiting.\nEverything the game does overnight still happens, including the daily save,\nso expect a brief hitch while it writes. Turn this off to get the vanilla\nsleep screen back (ignored while Multiplier Only Mode is ON).", false, false, (ValueValidator)null, (string)null); cfgLocalMultiplayer = cfgCategory.GetEntry<bool>("EnableLocalMultiplayer") ?? cfgCategory.CreateEntry<bool>("EnableLocalMultiplayer", false, "Enable Local Multiplayer (Mono only)", "MONO BUILD ONLY. Lets a SECOND copy of the game on the SAME PC join your session\nover loopback, with no second Steam account. Host a normal single-player save,\nthen press F6 in the other instance. Same machine only - this is not LAN.\nTime sync over Steam lobby data does not run in a local session; keep\nDaySpeedMultiplier identical in both instances. Off by default.", false, false, (ValueValidator)null, (string)null); cfgDebugLogging = cfgCategory.GetEntry<bool>("EnableDebugLogging") ?? cfgCategory.CreateEntry<bool>("EnableDebugLogging", false, "Enable Debug Logging", "If true, prints additional debug messages from the mod to help diagnose issues.", false, false, (ValueValidator)null, (string)null); cfgDailySummaryCutoffHHMM = cfgCategory.GetEntry<int>("ForceDailySummaryWhileInUITime") ?? cfgCategory.CreateEntry<int>("ForceDailySummaryWhileInUITime", 800, "Force Daily Summary While In UI Time", "The time the mod will force the daily summary to show if the user is still in a UI.", false, false, (ValueValidator)null, (string)null); ((MelonEventBase<LemonAction<int, int>>)(object)cfgDailySummaryCutoffHHMM.OnEntryValueChanged).Subscribe((LemonAction<int, int>)delegate(int _, int newVal) { if (!_updatingPref) { int num2 = SanitizeHHMM(newVal); if (num2 != newVal) { _updatingPref = true; cfgDailySummaryCutoffHHMM.Value = num2; cfgCategory.SaveToFile(false); _updatingPref = false; } TNSLog.Msg($"Daily summary cutoff set to {FormatHHMM(num2)} ({num2:D4})."); } }, 0, false); if (!cfgMultiplierOnly.Value) { _lastEnableSummaryAwake = cfgEnableSummaryAwake.Value; MelonCoroutines.Start(SleepStateWatcher()); } else { TNSLog.Msg("TimeMultiplierOnlyMode enabled: all non-multiplier features disabled."); } float num = Sanitize(ParseFloatOrDefault(cfgDaySpeedStr.Value, 1f)); if (!IsStringValidAndInRange(cfgDaySpeedStr.Value)) { cfgDaySpeedStr.Value = num.ToString(CultureInfo.InvariantCulture); } _updatingPref = true; cfgCategory.SaveToFile(true); _updatingPref = false; ((MelonEventBase<LemonAction<string, string>>)(object)cfgDaySpeedStr.OnEntryValueChanged).Subscribe((LemonAction<string, string>)delegate(string _, string newV) { if (!_updatingPref) { float v = ParseFloatOrDefault(newV, 1f); float newSpeed = Sanitize(v); if (!IsStringValidAndInRange(newV)) { _updatingPref = true; cfgDaySpeedStr.Value = newSpeed.ToString(CultureInfo.InvariantCulture); cfgCategory.SaveToFile(false); _updatingPref = false; } ApplyDaySpeed(newSpeed); if (_timeSyncManager != null) { try { _timeSyncManager.SyncHostMultiplierNow(); } catch (Exception ex2) { TNSLog.Warning("Error syncing multiplier change to lobby: " + ex2.Message); } } } }, 0, false); ((MelonEventBase<LemonAction<bool, bool>>)(object)cfgEnableSummaryAwake.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate(bool _, bool newVal) { if (!_updatingPref) { _lastEnableSummaryAwake = newVal; TNSLog.Msg("EnableDailySummaryAwake: " + (newVal ? "ON" : "OFF")); _updatingPref = true; cfgCategory.SaveToFile(false); _updatingPref = false; } }, 0, false); ((MelonEventBase<LemonAction<bool, bool>>)(object)cfgDebugLogging.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate(bool _, bool newVal) { if (!_updatingPref) { cfgDebugLogging.Value = newVal; TNSLog.Msg("DebugLogging: " + (newVal ? "Enabled" : "Disabled")); _updatingPref = true; cfgCategory.SaveToFile(false); _updatingPref = false; } }, 0, false); try { _cfgWatcher = new FileSystemWatcher(text2, Path.GetFileName(text3)) { NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite), IncludeSubdirectories = false, EnableRaisingEvents = true }; _cfgWatcher.Changed += markDirty; _cfgWatcher.Created += markDirty; _cfgWatcher.Renamed += markDirtyRename; } catch (Exception ex) { TNSLog.Warning("Config watcher init failed: " + ex.Message + ". Falling back to in-process updates only."); } MelonCoroutines.Start(SetDaySpeedLoop()); TNSLog.Debug($"Init complete. DaySpeed={cfgDaySpeedStr.Value}, MultiplierOnly={cfgMultiplierOnly.Value}, " + $"SummaryAwake={cfgEnableSummaryAwake.Value}, DebugLogging={cfgDebugLogging.Value}, " + $"DailySummaryCutoff={FormatHHMM(cfgDailySummaryCutoffHHMM.Value)} ({cfgDailySummaryCutoffHHMM.Value:D4})."); void markDirty(object _, FileSystemEventArgs __) { _cfgFileChanged = true; } void markDirtyRename(object _, RenamedEventArgs __) { _cfgFileChanged = true; } } public override void OnUpdate() { LocalMultiplayer.Tick(); } public override void OnSceneWasInitialized(int buildIndex, string sceneName) { TNSLog.Debug($"Scene initialized: '{sceneName}' (buildIndex={buildIndex})."); if (sceneName == "Menu") { _timeSyncRetrying = false; _timeSyncInitialized = false; StopTimeSyncUpdateLoop(); if (_timeSyncManager != null) { try { _timeSyncManager.Dispose(); } catch { } _timeSyncManager = null; } DailySummaryStore.Reset(); S1APIBridge.Reset(); LocalMultiplayer.Reset(); } else if (sceneName == "Main" && !_timeSyncInitialized && !_timeSyncRetrying) { _timeSyncRetrying = true; MelonCoroutines.Start(InitTimeSyncWithRetry()); LocalMultiplayer.OnGameSceneLoaded(); } } private IEnumerator InitTimeSyncWithRetry() { for (int attempt = 1; attempt <= 3; attempt++) { if (TryInitTimeSync()) { TNSLog.Msg("Time synchronization enabled."); yield break; } if (attempt < 3) { TNSLog.Debug($"Steamworks not ready — retrying in {2f}s..."); yield return (object)new WaitForSecondsRealtime(2f); } } TNSLog.Debug($"Fast attempts exhausted. Polling every {15f}s " + "in case a multiplayer session starts later..."); WaitForSecondsRealtime pollWait = new WaitForSecondsRealtime(15f); int polls = 0; while ((Object)(object)NetworkSingleton<TimeManager>.Instance != (Object)null && !_timeSyncInitialized) { yield return pollWait; polls++; TNSLog.Debug($"TimeSyncManager poll {polls}: attempting init..."); if (TryInitTimeSync()) { TNSLog.Msg($"Time synchronization enabled (connected after {polls} " + "poll" + ((polls == 1) ? "" : "s") + ")."); yield break; } } if (!_timeSyncInitialized) { TNSLog.Debug("TimeSyncManager polling stopped (session ended or scene changed)."); } _timeSyncRetrying = false; } private void StopTimeSyncUpdateLoop() { if (_timeSyncUpdateHandle != null) { try { MelonCoroutines.Stop(_timeSyncUpdateHandle); } catch { } _timeSyncUpdateHandle = null; } } private bool TryInitTimeSync() { StopTimeSyncUpdateLoop(); if (_timeSyncManager != null) { try { _timeSyncManager.Dispose(); } catch { } _timeSyncManager = null; } try { _timeSyncManager = new TimeSyncManager(); _timeSyncManager.SetHostMultiplierCallback(() => Sanitize(ParseFloatOrDefault(cfgDaySpeedStr.Value, 1f))); _timeSyncManager.Initialize(); if (_timeSyncManager.IsInitialized) { _timeSyncUpdateHandle = MelonCoroutines.Start(TimeSyncUpdateLoop()); _timeSyncInitialized = true; _timeSyncRetrying = false; return true; } TNSLog.Debug("TryInitTimeSync: Initialize() returned but IsInitialized is false."); } catch (Exception ex) { TNSLog.Debug("TryInitTimeSync failed: " + ex.Message); } return false; } public override void OnDeinitializeMelon() { try { _cfgWatcher?.Dispose(); } catch { } _cfgWatcher = null; StopTimeSyncUpdateLoop(); try { _timeSyncManager?.Dispose(); } catch { } _timeSyncManager = null; } private IEnumerator SleepStateWatcher() { bool last = false; TimeManager lastTm = null; while (true) { if ((Object)(object)NetworkSingleton<TimeManager>.Instance == (Object)null) { lastTm = null; last = false; yield return null; continue; } TimeManager tm = NetworkSingleton<TimeManager>.Instance; if ((Object)(object)tm != (Object)(object)lastTm) { lastTm = tm; last = tm.IsSleepInProgress; } bool cur = tm.IsSleepInProgress; if (cur && !last) { OnSleepStart(); } else if (!cur && last) { OnSleepEnd(); } last = cur; yield return null; } } private void OnSleepStart() { //IL_0051: Unknown result type (might be due to invalid IL or missing references) TimeManager instance = NetworkSingleton<TimeManager>.Instance; TNSLog.Debug($"Sleep start detected (watcher). IsHost={IsHost}, t={((instance != null) ? new int?(instance.CurrentTime) : ((int?)null)):D4}, day={((instance != null) ? new EDay?(instance.CurrentDay) : ((EDay?)null))}, elapsed={((instance != null) ? new int?(instance.ElapsedDays) : ((int?)null))}."); Patch_Tick_XPMenu.SuppressDuringSleep(on: true); Patch_Tick_XPMenu.MarkHandledForToday(); } private void OnSleepEnd() { Patch_SleepCanvas_SleepStart.StopRoutine(); Patch_Tick_XPMenu.SuppressDuringSleep(on: false); Patch_Tick_XPMenu.TryRunSleepEndCleanup("SleepStateWatcher"); SkipWakeFastForwardOnce = false; TNSLog.Debug("Sleep end (SleepStateWatcher) -> cleanup delegated."); } public static bool TryParseFloatInvariant(string s, out float value) { return float.TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out value); } public static float ParseFloatOrDefault(string s, float fallback) { float value; return (TryParseFloatInvariant(s, out value) && float.IsFinite(value)) ? value : fallback; } public static float Sanitize(float v) { return Math.Clamp(float.IsFinite(v) ? v : 1f, 0.1f, 100f); } private static bool IsStringValidAndInRange(string s) { float value; return TryParseFloatInvariant(s, out value) && float.IsFinite(value) && value >= 0.1f && value <= 100f; } private IEnumerator SetDaySpeedLoop() { WaitForSecondsRealtime tick = new WaitForSecondsRealtime(1f); while (true) { if ((Object)(object)NetworkSingleton<TimeManager>.Instance == (Object)null) { yield return null; continue; } TimeManager tm = NetworkSingleton<TimeManager>.Instance; float last = ReadPrefAndNormalize(); TNSLog.Debug($"TimeManager acquired. IsHost={IsHost}, speed={last:0.########}x."); if (IsHost) { ApplyDaySpeed(last); } while ((Object)(object)NetworkSingleton<TimeManager>.Instance == (Object)(object)tm) { if (_cfgFileChanged) { _cfgFileChanged = false; cfgCategory.LoadFromFile(false); } float desired = ReadPrefAndNormalize(); if (IsHost && (Differs(desired, last) || Differs(desired, tm.TimeSpeedMultiplier))) { last = desired; ApplyDaySpeed(last); } yield return tick; } } static bool Differs(float a, float b, float eps = 0.0001f) { return Mathf.Abs(a - b) > eps; } float ReadPrefAndNormalize() { float num = ParseFloatOrDefault(cfgDaySpeedStr.Value, 1f); float result = Sanitize(float.IsFinite(num) ? num : 1f); string text = result.ToString(CultureInfo.InvariantCulture); if (cfgDaySpeedStr.Value != text) { _updatingPref = true; cfgDaySpeedStr.Value = text; cfgCategory.SaveToFile(false); _updatingPref = false; } return result; } } private IEnumerator TimeSyncUpdateLoop() { WaitForSecondsRealtime tick = new WaitForSecondsRealtime(0.1f); while (true) { try { _timeSyncManager?.Update(); } catch (Exception ex) { Exception ex2 = ex; TNSLog.Warning("Error in time sync update loop: " + ex2.Message); } yield return tick; } } public static void ApplyDaySpeed(float newSpeed) { TimeManager instance = NetworkSingleton<TimeManager>.Instance; if (!((Object)(object)instance == (Object)null)) { float num = Sanitize(newSpeed); if (Mathf.Abs(instance.TimeSpeedMultiplier - num) > 0.0001f) { instance.SetTimeSpeedMultiplier(num); TNSLog.Msg($"Day speed set to {num:0.########}x"); } } } internal static int SanitizeHHMM(int v) { if (v < 0) { v = 0; } int num = v / 100; int num2 = v % 100; if (num < 0) { num = 0; } else if (num > 23) { num = 23; } if (num2 < 0) { num2 = 0; } else if (num2 > 59) { num2 = 59; } return num * 100 + num2; } internal static string FormatHHMM(int hhmm) { try { return TimeManager.Get12HourTime((float)hhmm, true); } catch { int num = Mathf.Clamp(hhmm / 100, 0, 23); int num2 = Mathf.Clamp(hhmm % 100, 0, 59); return $"{num:00}:{num2:00}"; } } } public static class PatchLogger { public static void LogPatchLoad(string patchName) { TNSLog.Msg("[Harmony] " + patchName + " loaded."); } } [HarmonyPatch(typeof(TimeManager), "RpcLogic___PassMinute_Client_3316948804")] [HarmonyPriority(600)] public static class Patch_Tick_XPMenu { private static bool firedToday; private static int lastHHmm; private static int lastHH; private static bool suppressForSleep; public static bool startupSkip; private static int lastElapsedDays; private static bool wouldFreeze; private static bool _sleepEndCleanupDone; private const string SleepingUIElement = "Sleeping"; private const float SleepSettleBudget = 6f; private const float SleepSettleBudgetWithMessage = 20f; static Patch_Tick_XPMenu() { lastHH = -1; startupSkip = true; lastElapsedDays = -1; wouldFreeze = false; _sleepEndCleanupDone = false; PatchLogger.LogPatchLoad("Patch_Tick_XPMenu"); startupSkip = true; } private static bool IsDailySummaryOpen() { try { DailySummary instance = NetworkSingleton<DailySummary>.Instance; return (Object)(object)instance != (Object)null && instance.IsOpen; } catch { return false; } } private static void LogState(string tag, TimeManager tm) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) TNSLog.Debug($"{tag}: t={tm.CurrentTime:D4}, day={tm.CurrentDay}, elapsed={tm.ElapsedDays}, " + $"firedToday={firedToday}, startupSkip={startupSkip}, suppress={suppressForSleep}, " + $"sleep={tm.IsSleepInProgress}, dsOpen={IsDailySummaryOpen()}, lastHHmm={lastHHmm}, " + $"lastElapsedDays={lastElapsedDays}, wouldFreeze={wouldFreeze}"); } public static void SuppressDuringSleep(bool on) { suppressForSleep = on; if (on) { _sleepEndCleanupDone = false; } } public static void MarkHandledForToday() { firedToday = true; lastHHmm = 700; TNSLog.Debug("MarkHandledForToday: firedToday=true, lastHHmm=0700"); } public static bool TryRunSleepEndCleanup(string source) { if (_sleepEndCleanupDone) { TNSLog.Debug("Sleep end cleanup already done this cycle — skipping (" + source + ")."); return false; } _sleepEndCleanupDone = true; if (Patch_SleepCanvas_SleepStart.SilentSleepEngaged) { TNSLog.Debug("Sleep end cleanup skipped (" + source + "): silent sleep left the HUD alone."); MarkHandledForToday(); return false; } TNSLog.Debug("Sleep end cleanup running (" + source + ")."); MarkHandledForToday(); MelonCoroutines.Start(EnsureHUDResetWhenSettled(source)); return true; } private static float GetSleepSettleBudget() { try { SleepCanvas instance = Singleton<SleepCanvas>.Instance; if ((Object)(object)instance != (Object)null && !string.IsNullOrEmpty(instance.QueuedSleepMessage)) { TNSLog.Debug($"Sleep message queued; extending settle budget to {20f:0}s."); return 20f; } } catch (Exception ex) { TNSLog.Debug("Could not read QueuedSleepMessage (" + ex.Message + "); using the extended settle budget."); return 20f; } return 6f; } private static IEnumerator EnsureHUDResetWhenSettled(string source) { float maxWait = GetSleepSettleBudget(); float start = Time.realtimeSinceStartup; while (SleepUiStillRunning() && Time.realtimeSinceStartup - start < maxWait) { yield return null; } float waited = Time.realtimeSinceStartup - start; TNSLog.Debug(SleepUiStillRunning() ? $"Sleep sequence did NOT finish within {maxWait:0}s ({source}); forcing EnsureHUDReset (activeUI={GetLocalActiveUICountSafe()})." : $"Sleep sequence finished after {waited:0.00}s ({source}); running EnsureHUDReset as a safety pass (activeUI={GetLocalActiveUICountSafe()})."); EnsureHUDReset(); } private static bool SleepUiStillRunning() { try { PlayerCamera instance = PlayerSingleton<PlayerCamera>.Instance; if ((Object)(object)instance == (Object)null) { return false; } List<string> activeUIElements = instance.ActiveUIElements; if (activeUIElements == null) { return false; } for (int i = 0; i < activeUIElements.Count; i++) { if (string.Equals(activeUIElements[i], "Sleeping", StringComparison.Ordinal)) { return true; } } return false; } catch (Exception ex) { TNSLog.Debug("SleepUiStillRunning: could not read ActiveUIElements (" + ex.Message + "); falling back to active-UI count."); return GetLocalActiveUICountSafe() > 0; } } private static int GetLocalActiveUICountSafe() { try { return Patch_SleepCanvas_SleepStart.GetLocalActiveUICount(); } catch { return 0; } } [HarmonyPrefix] public static bool Prefix(TimeManager __instance, int oldTime) { //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Invalid comparison between Unknown and I4 if ((Object)(object)__instance == (Object)null || !((Behaviour)__instance).enabled) { return true; } if (Core.IsHost) { float currentDaySpeed = Core.CurrentDaySpeed; if (Mathf.Abs(__instance.TimeSpeedMultiplier - currentDaySpeed) > 0.0001f) { Core.ApplyDaySpeed(currentDaySpeed); } } MelonPreferences_Entry<bool> cfgMultiplierOnly = Core.cfgMultiplierOnly; if (cfgMultiplierOnly != null && cfgMultiplierOnly.Value) { return true; } wouldFreeze = __instance.CurrentTime == 400 || (__instance.IsCurrentTimeWithinRange(400, 600) && !GameManager.IS_TUTORIAL); if (!wouldFreeze) { return true; } if ((Object)(object)Player.Local == (Object)null) { TNSLog.Warning("Local player does not exist. Waiting for player spawn."); return false; } TMAccess.SetCurrentTime(__instance, oldTime); float num = TimeManager.MinuteDuration / (Time.timeScale * 0.9f); TMAccess.SetTimeOnCurrentMinute(__instance, 0f); if (__instance.CurrentTime == 2359) { TMAccess.SetElapsedDays(__instance, __instance.ElapsedDays + 1); TMAccess.SetCurrentTime(__instance, 0); TMAccess.SetDailyMinSum(__instance, 0); __instance.onHourPass?.Invoke(); __instance.onDayPass?.Invoke(); if ((int)__instance.CurrentDay == 0) { __instance.onWeekPass?.Invoke(); } } else if (__instance.CurrentTime % 100 >= 59) { TMAccess.SetCurrentTime(__instance, __instance.CurrentTime + 41); __instance.onHourPass?.Invoke(); } else { TMAccess.SetCurrentTime(__instance, __instance.CurrentTime + 1); } TMAccess.SetDailyMinSum(__instance, TimeManager.GetMinSumFrom24HourTime(__instance.CurrentTime)); __instance.HasChanged = true; ActionList onMinutePass = __instance.onMinutePass; if (onMinutePass != null) { onMinutePass.InvokeAllStaggered(num); } ActionList onUncappedMinutePass = __instance.onUncappedMinutePass; if (onUncappedMinutePass != null) { onUncappedMinutePass.InvokeAllStaggered(num); } __instance.onTimeChanged?.Invoke(); return false; } [HarmonyPostfix] public static void Postfix(TimeManager __instance) { MelonPreferences_Entry<bool> cfgMultiplierOnly = Core.cfgMultiplierOnly; if (cfgMultiplierOnly != null && cfgMultiplierOnly.Value) { return; } lastElapsedDays = __instance.ElapsedDays; if (lastHH != __instance.CurrentTime / 100) { LogState("PassMinute/Postfix/start", __instance); } lastHH = __instance.CurrentTime / 100; if (suppressForSleep && !__instance.IsSleepInProgress) { TNSLog.Warning("Guard clear: suppressForSleep was true while not sleeping."); suppressForSleep = false; } try { if (__instance.IsSleepInProgress && __instance.HostSleepDone && !IsDailySummaryOpen()) { TMAccess.SetSleepInProgress(__instance, v: false); TNSLog.Warning("SleepInProgress stuck after host done; forced SleepInProgress=false."); } } catch { } if (__instance.CurrentTime == Core.DailySummaryCutoffHHMM + 30) { MelonCoroutines.Start(ClosePostSleepCanvases()); } if (__instance.IsSleepInProgress || suppressForSleep) { return; } DailySummary instance = NetworkSingleton<DailySummary>.Instance; if ((Object)(object)instance != (Object)null && instance.IsOpen) { TNSLog.Debug("Skip: DailySummary is open."); return; } if (startupSkip) { startupSkip = false; lastHHmm = __instance.CurrentTime; firedToday = __instance.CurrentTime > 658; TNSLog.Debug($"Startup init: lastHHmm={lastHHmm}, firedToday={firedToday}"); } int currentTime = __instance.CurrentTime; if (lastHHmm > currentTime) { firedToday = false; TNSLog.Debug("Rearm: midnight wrap detected (lastHHmm > hhmm)."); } if (!firedToday && __instance.CurrentTime > 658 && __instance.ElapsedDays > 0) { if (Core.IsHost) { TNSLog.Debug($"Triggering StartSleep: t={__instance.CurrentTime:D4}, elapsed={__instance.ElapsedDays}, " + $"dsOpen={IsDailySummaryOpen()}, suppress={suppressForSleep}"); __instance.StartSleep(); } else { TNSLog.Debug($"Skipping StartSleep: not host (t={__instance.CurrentTime:D4}). " + "Waiting for the host's ObserversRpc to drive the sleep flow."); firedToday = true; } } lastHHmm = currentTime; } private static IEnumerator ClosePostSleepCanvases() { DailySummary ds = NetworkSingleton<DailySummary>.Instance; if ((Object)(object)ds != (Object)null && ds.IsOpen) { ds.Close(); TNSLog.Debug($"{Core.DailySummaryCutoffHHMM + 30}: Closed DailySummary."); } yield return (object)new WaitForSecondsRealtime(2f); try { RankUpCanvas[] ranks = Resources.FindObjectsOfTypeAll<RankUpCanvas>(); if (ranks != null) { RankUpCanvas[] array = ranks; foreach (RankUpCanvas r in array) { if (!((Object)(object)r == (Object)null) && (r.IsRunning || ((Object)(object)r.Canvas != (Object)null && ((Behaviour)r.Canvas).enabled))) { r.EndEvent(); if ((Object)(object)r.Canvas != (Object)null) { ((Behaviour)r.Canvas).enabled = false; } TNSLog.Debug($"{Core.DailySummaryCutoffHHMM + 30}: Closed RankUpCanvas."); } } } } catch (Exception ex) { TNSLog.Warning("CloseAllRankUpCanvases failed: " + ex.Message); } yield return (object)new WaitForSecondsRealtime(2f); RegionUnlockedCanvas region = Singleton<RegionUnlockedCanvas>.Instance; if ((Object)(object)region != (Object)null && region.IsRunning) { region.EndEvent(); TNSLog.Debug($"{Core.DailySummaryCutoffHHMM + 30}: Closed RegionUnlockedCanvas."); } } public static void EnsureHUDReset() { if ((Object)(object)Player.Local == (Object)null) { TNSLog.Debug("EnsureHUDReset: no local player (session ended while waiting) — skipping."); return; } Player.Local.SetReadyToSleep(false); int localActiveUICountSafe = GetLocalActiveUICountSafe(); bool flag = localActiveUICountSafe == 0; TNSLog.Debug($"EnsureHUDReset: uiCleared={flag} (activeUI={localActiveUICountSafe})."); PlayerCamera instance = PlayerSingleton<PlayerCamera>.Instance; if ((Object)(object)instance != (Object)null) { instance.StopTransformOverride(0f, true, false); if (flag) { instance.SetCanLook(true); instance.LockMouse(true); } else { instance.SetCanLook(false); instance.FreeMouse(true); } } PlayerInventory instance2 = PlayerSingleton<PlayerInventory>.Instance; if ((Object)(object)instance2 != (Object)null) { instance2.SetInventoryEnabled(true); } InputPromptsManager instance3 = Singleton<InputPromptsManager>.Instance; if ((Object)(object)instance3 != (Object)null) { instance3.UnloadModule("Back"); } PlayerMovement instance4 = PlayerSingleton<PlayerMovement>.Instance; if ((Object)(object)instance4 != (Object)null) { instance4.CanMove = true; } SleepCanvas instance5 = Singleton<SleepCanvas>.Instance; if ((Object)(object)instance5 != (Object)null && (Object)(object)instance5.MenuContainer != (Object)null) { ((Component)instance5.MenuContainer).gameObject.SetActive(false); } } } [HarmonyPatch(typeof(HUD), "Update")] [HarmonyPriority(200)] public static class Patch_HUD_HideSleepPrompt { private static void Postfix(HUD __instance) { MelonPreferences_Entry<bool> cfgMultiplierOnly = Core.cfgMultiplierOnly; if ((cfgMultiplierOnly == null || !cfgMultiplierOnly.Value) && (Object)(object)__instance?.SleepPrompt != (Object)null) { ((Component)__instance.SleepPrompt).gameObject.SetActive(false); } } } [HarmonyPatch(typeof(RankUpCanvas), "StartEvent")] public static class Patch_RankUpCanvas_StartEvent { static Patch_RankUpCanvas_StartEvent() { PatchLogger.LogPatchLoad("Patch_RankUpCanvas_StartEvent"); } [HarmonyPrefix] public static bool PostFix(RankUpCanvas __instance) { if (DailySummaryPatch.ShouldSuppressSummaryUI()) { TNSLog.Debug("Rank Up Canvas Skipping"); ((Behaviour)__instance.Canvas).enabled = false; __instance.EndEvent(); return false; } TNSLog.Debug("Rank Up Canvas Running"); return true; } } [HarmonyPatch(typeof(RegionUnlockedCanvas), "StartEvent")] public static class Patch_RegionUnlockedCanvas_StartEvent { static Patch_RegionUnlockedCanvas_StartEvent() { PatchLogger.LogPatchLoad("Patch_RegionUnlockedCanvas_StartEvent"); } [HarmonyPrefix] public static bool PostFix(RegionUnlockedCanvas __instance) { if (DailySummaryPatch.ShouldSuppressSummaryUI()) { TNSLog.Debug("Region Unlocked Canvas Skipping"); __instance.EndEvent(); return false; } TNSLog.Debug("Region Unlocked Canvas Running"); return true; } } [HarmonyPatch(typeof(RegionUnlockedCanvas), "QueueUnlocked")] public static class Patch_RegionUnlockedCanvas_QueueUnlocked { static Patch_RegionUnlockedCanvas_QueueUnlocked() { PatchLogger.LogPatchLoad("Patch_RegionUnlockedCanvas_QueueUnlocked"); } [HarmonyPostfix] public static void Postfix(EMapRegion _region) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) MelonPreferences_Entry<bool> cfgMultiplierOnly = Core.cfgMultiplierOnly; if (cfgMultiplierOnly == null || !cfgMultiplierOnly.Value) { DailySummaryStore.NoteRegionUnlocked(_region); } } } [HarmonyPatch(typeof(DailySummary))] public static class DailySummaryPatch { static DailySummaryPatch() { PatchLogger.LogPatchLoad("DailySummaryPatch"); } internal static bool ShouldSuppressSummaryUI() { MelonPreferences_Entry<bool> cfgMultiplierOnly = Core.cfgMultiplierOnly; if (cfgMultiplierOnly != null && cfgMultiplierOnly.Value) { return false; } MelonPreferences_Entry<bool> cfgEnableSummaryAwake = Core.cfgEnableSummaryAwake; if (cfgEnableSummaryAwake != null && !cfgEnableSummaryAwake.Value) { return true; } return S1APIBridge.ShouldGateSummaryBehindApp(); } [HarmonyPrefix] [HarmonyPatch("ClearStats")] public static void ClearStatsPrefix(DailySummary __instance) { MelonPreferences_Entry<bool> cfgMultiplierOnly = Core.cfgMultiplierOnly; if (cfgMultiplierOnly == null || !cfgMultiplierOnly.Value) { TNSLog.Debug("DailySummary.ClearStats reached — capturing the night's summary."); DailySummaryStore.Capture(__instance); } } [HarmonyPrefix] [HarmonyPatch("Open")] public static bool OpenPrefix(DailySummary __instance) { if (ShouldSuppressSummaryUI()) { TNSLog.Debug("Daily Summary Open Skipping"); return false; } TNSLog.Debug("Daily Summary Open Running"); return true; } [HarmonyPrefix] [HarmonyPatch("Close")] public static bool ClosePrefix(DailySummary __instance) { if (ShouldSuppressSummaryUI()) { TNSLog.Debug("Daily Summary Close Skipping"); return false; } TNSLog.Debug("Daily Summary Close Running"); return true; } } [HarmonyPatch(typeof(SleepCanvas), "SleepStart")] public static class Patch_SleepCanvas_SleepStart { private static readonly MethodInfo _miSleepStart = AccessTools.Method(typeof(SleepCanvas), "SleepStart", Type.EmptyTypes, (Type[])null); private static readonly HashSet<SleepCanvas> _allowOnce = new HashSet<SleepCanvas>(); private static readonly Dictionary<SleepCanvas, object> _gateRoutines = new Dictionary<SleepCanvas, object>(); private static object _lookRoutine; private static object _silentRoutine; private const float SilentSleepEndBudgetHost = 30f; private const float SilentSleepEndBudgetClient = 120f; private static int AbsoluteCutoffHHMM => Core.DailySummaryCutoffHHMM; internal static bool SilentSleepEngaged { get; private set; } [HarmonyPrefix] public static bool Prefix(SleepCanvas __instance) { MelonPreferences_Entry<bool> cfgMultiplierOnly = Core.cfgMultiplierOnly; if (cfgMultiplierOnly != null && cfgMultiplierOnly.Value) { return true; } SilentSleepEngaged = Core.SilentSleepActive; if (SilentSleepEngaged) { if (_silentRoutine != null) { TNSLog.Debug("SleepStart: a silent sleep is already running; ignoring the repeat."); return false; } _silentRoutine = MelonCoroutines.Start(SilentSleepRoutine(__instance)); return false; } if (_allowOnce.Contains(__instance)) { _allowOnce.Remove(__instance); TNSLog.Debug("SleepStart: allowOnce pass-through."); StartLookRoutine(__instance); return true; } int localActiveUICount = GetLocalActiveUICount(); if (localActiveUICount > 0) { if (!_gateRoutines.ContainsKey(__instance)) { TNSLog.Debug($"SleepStart deferred: activeUI={localActiveUICount}; starting gate routine."); object value = MelonCoroutines.Start(WaitUntilMenusClosedThenRun(__instance)); _gateRoutines[__instance] = value; } return false; } TNSLog.Debug("SleepStart: proceeding (no active UI)."); StartLookRoutine(__instance); return true; } private static void StartLookRoutine(SleepCanvas __instance) { if (_lookRoutine != null) { MelonCoroutines.Stop(_lookRoutine); } _lookRoutine = MelonCoroutines.Start(MakeThingsWorkRoutine(__instance)); } public static void StopRoutine() { MelonPreferences_Entry<bool> cfgMultiplierOnly = Core.cfgMultiplierOnly; if ((cfgMultiplierOnly == null || !cfgMultiplierOnly.Value) && _lookRoutine != null) { MelonCoroutines.Stop(_lookRoutine); _lookRoutine = null; } } private static IEnumerator MakeThingsWorkRoutine(SleepCanvas __instance) { for (int i = 0; i < 3; i++) { try { PlayerCamera cam = PlayerSingleton<PlayerCamera>.Instance; if ((Object)(object)cam != (Object)null) { if (Core.cfgEnableSummaryAwake?.Value ?? true) { cam.SetCanLook(false); cam.FreeMouse(true); } else { cam.SetCanLook(true); cam.LockMouse(true); SCAccess.LerpBlackOverlay(__instance, 0f, 0.1f); } } } catch (Exception ex) { TNSLog.Warning("SleepStart look restore failed: " + ex.Message); } yield return null; } _lookRoutine = null; } private static IEnumerator WaitUntilMenusClosedThenRun(SleepCanvas sc) { float startRT = Time.realtimeSinceStartup; while (!((Object)(object)sc == (Object)null)) { TimeManager tm = NetworkSingleton<TimeManager>.Instance; bool cutoffReached = (Object)(object)tm != (Object)null && tm.CurrentTime >= AbsoluteCutoffHHMM; bool timeoutReached = Time.realtimeSinceStartup - startRT >= 30f; bool uiCleared = GetLocalActiveUICount() == 0; if (uiCleared || cutoffReached || timeoutReached) { if (uiCleared) { TNSLog.Debug("SleepStart defer: UI cleared; proceeding."); } if (cutoffReached) { TNSLog.Debug("SleepStart defer: cutoff " + Core.FormatHHMM(AbsoluteCutoffHHMM) + " reached; proceeding."); } if (timeoutReached) { TNSLog.Debug("SleepStart defer: timeout reached; proceeding."); } if ((Object)(object)tm != (Object)null && tm.CurrentTime > 700) { Core.SkipWakeFastForwardOnce = true; TNSLog.Debug("Guard set: will skip SkipForwardToTime (already past wake)."); } _allowOnce.Add(sc); try { TNSLog.Debug("Deferred SleepStart: invoking private SleepStart via reflection..."); _miSleepStart?.Invoke(sc, null); TimeManager tm2 = NetworkSingleton<TimeManager>.Instance; string ct = (((Object)(object)tm2 != (Object)null) ? tm2.CurrentTime.ToString("D4") : "null"); bool sip = (Object)(object)tm2 != (Object)null && tm2.IsSleepInProgress; TNSLog.Debug($"Deferred SleepStart invoked. SleepInProgress={sip}, t={ct}"); } catch (Exception ex) { TNSLog.Error("Deferred SleepStart invoke failed: " + ex.Message); } break; } yield return (object)new WaitForSecondsRealtime(0.25f); } _gateRoutines.Remove(sc); } private static IEnumerator SilentSleepRoutine(SleepCanvas sc) { yield return null; TNSLog.Debug("Silent sleep: vanilla sleep sequence bypassed; running the callbacks by hand."); TimeManager tm = NetworkSingleton<TimeManager>.Instance; if ((Object)(object)tm == (Object)null) { TNSLog.Warning("Silent sleep: no TimeManager — aborting. The night will not complete."); _silentRoutine = null; yield break; } try { if ((Object)(object)Player.Local != (Object)null) { Player.Local.SetReadyToSleep(false); } if ((Object)(object)sc != (Object)null && (Object)(object)sc.MenuState != (Object)null) { sc.MenuState.RemoveFromDefaultParent(); } } catch (Exception ex) { Exception ex2 = ex; TNSLog.Warning("Silent sleep: menu teardown failed (continuing): " + ex2.Message); } string overnightMessage = ReadAndClearSleepMessage(sc); try { if (sc != null) { UnityEvent onSleepFullyFaded = sc.onSleepFullyFaded; if (onSleepFullyFaded != null) { onSleepFullyFaded.Invoke(); } } } catch (Exception ex) { Exception ex3 = ex; TNSLog.Error($"Silent sleep: onSleepFullyFaded threw: {ex3}"); } DailySummaryStore.NoteOvernightMessage(overnightMessage); if (Core.IsHost) { TNSLog.Debug("Silent sleep: host — SetHostSleepDone(true); onSleepEnd and the daily save follow."); try { tm.SetHostSleepDone(true); } catch (Exception ex) { Exception ex4 = ex; TNSLog.Error($"Silent sleep: SetHostSleepDone failed: {ex4}"); } } else { TNSLog.Debug("Silent sleep: client — waiting for the host's SetHostSleepDone RPC."); } float budget = (Core.IsHost ? 30f : 120f); float start = Time.realtimeSinceStartup; while ((Object)(object)tm != (Object)null && tm.IsSleepInProgress && Time.realtimeSinceStartup - start < budget) { yield return null; } float waited = Time.realtimeSinceStartup - start; if ((Object)(object)tm != (Object)null && tm.IsSleepInProgress) { TNSLog.Warning($"Silent sleep: sleep end did NOT complete within {budget:0}s. " + "onSleepEnd and the daily save may not have run this night. " + (Core.IsHost ? "This machine is the host, so SetHostSleepDone(true) was sent — suspect the patch or a throwing subscriber." : "This machine is a client; the host never flagged sleep done.")); } else { TNSLog.Debug($"Silent sleep: sleep end completed after {waited:0.00}s (save included on the host)."); } try { if (sc != null) { UnityEvent onSleepEndFade = sc.onSleepEndFade; if (onSleepEndFade != null) { onSleepEndFade.Invoke(); } } } catch (Exception arg) { TNSLog.Error($"Silent sleep: onSleepEndFade threw: {arg}"); } if (!string.IsNullOrEmpty(overnightMessage)) { TNSLog.Msg("Overnight: " + overnightMessage); } _silentRoutine = null; } private static string ReadAndClearSleepMessage(SleepCanvas sc) { try { if ((Object)(object)sc == (Object)null) { return null; } string queuedSleepMessage = sc.QueuedSleepMessage; if (string.IsNullOrEmpty(queuedSleepMessage)) { return null; } sc.QueueSleepMessage(string.Empty, 0f); return queuedSleepMessage; } catch (Exception ex) { TNSLog.Warning("Silent sleep: could not read the queued sleep message: " + ex.Message); return null; } } public static int GetLocalActiveUICount() { try { PlayerCamera instance = PlayerSingleton<PlayerCamera>.Instance; return ((Object)(object)instance != (Object)null) ? instance.ActiveUIElementCount : 0; } catch { return 0; } } } [HarmonyPatch(typeof(SleepCanvas), "AddPostSleepEvent")] public static class Patch_SleepCanvas_AddPostSleepEvent { static Patch_SleepCanvas_AddPostSleepEvent() { PatchLogger.LogPatchLoad("Patch_SleepCanvas_AddPostSleepEvent"); } [HarmonyPrefix] public static bool Prefix() { return !Core.SilentSleepActive; } } [HarmonyPatch(typeof(TimeManager), "SkipForwardToTime")] public static class Patch_TimeManager_SkipForwardToTime { [HarmonyPrefix] public static bool Prefix(TimeManager __instance) { if ((Object)(object)__instance == (Object)null) { return true; } bool skipWakeFastForwardOnce = Core.SkipWakeFastForwardOnce; bool flag = __instance.CurrentTime >= 700; if (skipWakeFastForwardOnce || flag) { Core.SkipWakeFastForwardOnce = false; TNSLog.Debug($"Skipping SkipForwardToTime (skipFlag={skipWakeFastForwardOnce}, pastWake={flag}, t={__instance.CurrentTime:D4})."); return false; } TNSLog.Debug($"SkipForwardToTime: running original (t={__instance.CurrentTime:D4})."); return true; } } internal static class TMAccess { private static readonly Action<TimeManager, int> _setCurrentTime; private static readonly Action<TimeManager, int> _setElapsedDays; private static readonly Action<TimeManager, int> _setDailyMinSum; private static readonly FieldInfo _secondsOnCurrentMinute; private static readonly Action<TimeManager, bool> _setSleepInProgress; private static readonly FieldInfo _timeSpeedMultiplierField; static TMAccess() { _setCurrentTime = BuildSetter<int>("CurrentTime"); _setElapsedDays = BuildSetter<int>("ElapsedDays"); _setDailyMinSum = BuildSetter<int>("DailyMinSum"); _secondsOnCurrentMinute = AccessTools.Field(typeof(TimeManager), "_secondsOnCurrentMinute"); _setSleepInProgress = BuildSetter<bool>("IsSleepInProgress"); _timeSpeedMultiplierField = AccessTools.Field(typeof(TimeManager), "<TimeSpeedMultiplier>k__BackingField"); } private static Action<TimeManager, T> BuildSetter<T>(string propName) { MethodInfo methodInfo = AccessTools.PropertySetter(typeof(TimeManager), propName); if (methodInfo == null) { return delegate { }; } return (Action<TimeManager, T>)Delegate.CreateDelegate(typeof(Action<TimeManager, T>), null, methodInfo); } public static void SetCurrentTime(TimeManager tm, int v) { _setCurrentTime(tm, v); } public static void SetElapsedDays(TimeManager tm, int v) { _setElapsedDays(tm, v); } public static void SetDailyMinSum(TimeManager tm, int v) { _setDailyMinSum(tm, v); } public static void SetTimeOnCurrentMinute(TimeManager tm, float v) { _secondsOnCurrentMinute?.SetValue(tm, v); } public static void SetSleepInProgress(TimeManager tm, bool v) { _setSleepInProgress?.Invoke(tm, v); } public static void SetTimeSpeedMultiplierDirect(TimeManager tm, float v) { _timeSpeedMultiplierField?.SetValue(tm, Mathf.Max(v, 0f)); } } internal static class SCAccess { private static readonly MethodInfo _lerpBlackOverlay = AccessTools.Method(typeof(SleepCanvas), "LerpBlackOverlay", new Type[2] { typeof(float), typeof(float) }, (Type[])null); public static void LerpBlackOverlay(SleepCanvas inst, float transparency, float lerpTime) { if (_lerpBlackOverlay != null) { try { _lerpBlackOverlay.Invoke(inst, new object[2] { transparency, lerpTime }); } catch (Exception ex) { TNSLog.Warning("Invoke SleepCanvas.LerpBlackOverlay failed: " + ex.Message); } } } } [HarmonyPatch(typeof(TimeManager))] public static class Debug_TimeManager { [HarmonyPostfix] [HarmonyPatch("StartSleep")] private static void StartSleep_Postfix(TimeManager __instance) { MelonPreferences_Entry<bool> cfgMultiplierOnly = Core.cfgMultiplierOnly; if (cfgMultiplierOnly == null || !cfgMultiplierOnly.Value) { TNSLog.Debug("StartSleep (RPC) observed -> suppressing tick actions."); Patch_Tick_XPMenu.SuppressDuringSleep(on: true); } } [HarmonyPostfix] [HarmonyPatch("SetHostSleepDone")] private static void SetHostSleepDone_Postfix(TimeManager __instance, bool done) { MelonPreferences_Entry<bool> cfgMultiplierOnly = Core.cfgMultiplierOnly; if (cfgMultiplierOnly != null && cfgMultiplierOnly.Value) { return; } TNSLog.Debug($"SetHostSleepDone (RPC) observed: done={done}"); if (done) { Patch_SleepCanvas_SleepStart.StopRoutine(); Patch_Tick_XPMenu.SuppressDuringSleep(on: false); Patch_Tick_XPMenu.TryRunSleepEndCleanup("SetHostSleepDone_RPC"); Core.SkipWakeFastForwardOnce = false; if (__instance.IsSleepInProgress) { TMAccess.SetSleepInProgress(__instance, v: false); TNSLog.Warning("Host done -> forced SleepInProgress=false (engine hadn't cleared yet)."); } } } } public class TimeSyncManager : IDisposable { private SteamNetworkClient? _steamClient; private bool _disposed = false; private bool _isInitialized = false; private object? _syncCoroutine; private object? _readCoroutine; private const string LOBBY_DATA_KEY_CURRENT_TIME = "__tns_current_time"; private const string LOBBY_DATA_KEY_ELAPSED_DAYS = "__tns_elapsed_days"; private const string LOBBY_DATA_KEY_TIME_UPDATE_TIMESTAMP = "__tns_time_update_ts"; private const string LOBBY_DATA_KEY_TIME_MULTIPLIER = "__tns_time_multiplier"; private const float HOST_CHECK_INTERVAL = 5f; private const float CLIENT_SYNC_INTERVAL = 10f; private const float MULTIPLIER_SYNC_INTERVAL = 5f; private const float MAX_TIME_DIFF_THRESHOLD = 5f; private int _lastSyncedCurrentTime = -1; private int _lastSyncedElapsedDays = -1; private int _lastSyncedHour = -1; private float _lastSyncTimestamp = 0f; private float _lastSyncedMultiplier = float.NaN; private float _lastMultiplierCheckTime = 0f; private int _lastSyncedHostTime = -1; private int _lastSyncedHostDays = -1; private float _lastSyncedHostMultiplier = float.NaN; private Func<float>? _getHostMultiplierCallback; private float? _clientOriginalMultiplier = null; public bool IsInitialized => _isInitialized && !_disposed; public bool IsInLobby { get { SteamNetworkClient? steamClient = _steamClient; return steamClient != null && steamClient.IsInLobby; } } public bool IsHost { get { SteamNetworkClient? steamClient = _steamClient; return steamClient != null && steamClient.IsHost; } } public void SetHostMultiplierCallback(Func<float> callback) { _getHostMultiplierCallback = callback; } public void SyncHostMultiplierNow() { if (IsHost && IsInLobby) { _lastSyncedMultiplier = float.NaN; SyncHostMultiplierToLobby(); } } public void Initialize() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown if (_isInitialized || _disposed) { return; } try { _steamClient = new SteamNetworkClient(); if (!_steamClient.Initialize()) { TNSLog.Warning("Failed to initialize SteamNetworkClient for time sync"); return; } _steamClient.OnLobbyJoined += OnLobbyJoined; _steamClient.OnLobbyCreated += OnLobbyCreated; _steamClient.OnLobbyLeft += OnLobbyLeft; _steamClient.OnLobbyDataChanged += OnLobbyDataChanged; _isInitialized = true; TNSLog.Debug("TimeSyncManager initialized"); if (_steamClient.IsInLobby) { StartSyncCoroutines(); } } catch (Exception ex) { TNSLog.Error("Failed to initialize TimeSyncManager: " + ex.Message); } } public void Update() { if (!_isInitialized || _disposed) { return; } try { SteamNetworkClient? steamClient = _steamClient; if (steamClient != null) { steamClient.ProcessIncomingMessages(); } } catch (Exception ex) { TNSLog.Warning("Error processing Steam messages in TimeSyncManager: " + ex.Message); } } private void StartSyncCoroutines() { if ((Object)(object)NetworkSingleton<TimeManager>.Instance == (Object)null) { TNSLog.Debug("TimeManager.Instance is null, waiting..."); MelonCoroutines.Start(WaitForTimeManagerAndStartSync()); } else if (IsHost) { if (_syncCoroutine != null) { MelonCoroutines.Stop(_syncCoroutine); } _syncCoroutine = MelonCoroutines.Start(HostUpdateTimeLoop()); TNSLog.Debug("Started host time sync loop"); } else { if (_readCoroutine != null) { MelonCoroutines.Stop(_readCoroutine); } _readCoroutine = MelonCoroutines.Start(ClientSyncTimeLoop()); TNSLog.Debug("Started client time sync loop"); } } private IEnumerator WaitForTimeManagerAndStartSync() { TNSLog.Debug("TimeSyncManager: waiting for TimeManager before starting sync coroutines..."); while ((Object)(object)NetworkSingleton<TimeManager>.Instance == (Object)null) { yield return null; } TNSLog.Debug("TimeSyncManager: TimeManager ready, starting sync coroutines."); StartSyncCoroutines(); } private IEnumerator HostUpdateTimeLoop() { WaitForSecondsRealtime waitTime = new WaitForSecondsRealtime(5f); while (IsInLobby && IsHost && (Object)(object)NetworkSingleton<TimeManager>.Instance != (Object)null) { try { TimeManager tm = NetworkSingleton<TimeManager>.Instance; int currentTime = tm.CurrentTime; int elapsedDays = tm.ElapsedDays; int currentHour = currentTime / 100; bool hourChanged = _lastSyncedHour != currentHour; bool daysChanged = _lastSyncedElapsedDays != elapsedDays; bool isFirstSync = _lastSyncedCurrentTime == -1; if (isFirstSync || hourChanged || daysChanged) { string currentTimeStr = currentTime.ToString(); string elapsedDaysStr = elapsedDays.ToString(); string timestampStr = Time.realtimeSinceStartup.ToString("F2"); SteamNetworkClient? steamClient = _steamClient; if (steamClient != null) { steamClient.SetLobbyData("__tns_current_time", currentTimeStr); } SteamNetworkClient? steamClient2 = _steamClient; if (steamClient2 != null) { steamClient2.SetLobbyData("__tns_elapsed_days", elapsedDaysStr); } SteamNetworkClient? steamClient3 = _steamClient; if (steamClient3 != null) { steamClient3.SetLobbyData("__tns_time_update_ts", timestampStr); } _lastSyncedCurrentTime = currentTime; _lastSyncedElapsedDays = elapsedDays; _lastSyncedHour = currentHour; _lastSyncTimestamp = Time.realtimeSinceStartup; TNSLog.Debug($"Host updated lobby time: {currentTime} (Hour: {currentHour}), Days: {elapsedDays}"); } } catch (Exception ex) { TNSLog.Warning("Error in host time update loop: " + ex.Message); } float currentTimeRealtime = Time.realtimeSinceStartup; if (currentTimeRealtime - _lastMultiplierCheckTime >= 5f) { _lastMultiplierCheckTime = currentTimeRealtime; try { SyncHostMultiplierToLobby(); } catch (Exception ex2) { Exception ex3 = ex2; TNSLog.Warning("Error syncing host multiplier: " + ex3.Message); } } yield return waitTime; } } private void SyncHostMultiplierToLobby() { if (!IsHost || _steamClient == null || (Object)(object)NetworkSingleton<TimeManager>.Instance == (Object)null) { return; } try { float num = _getHostMultiplierCallback?.Invoke() ?? NetworkSingleton<TimeManager>.Instance.TimeSpeedMultiplier; if (float.IsNaN(_lastSyncedMultiplier) || Mathf.Abs(_lastSyncedMultiplier - num) > 0.0001f) { string text = num.ToString("R", CultureInfo.InvariantCulture); _steamClient.SetLobbyData("__tns_time_multiplier", text); _lastSyncedMultiplier = num; TNSLog.Debug($"Host synced multiplier to lobby: {num:0.########}x"); } } catch (Exception ex) { TNSLog.Warning("Error syncing host multiplier to lobby: " + ex.Message); } } private void SyncToHostMultiplier() { if (IsHost || _steamClient == null || (Object)(object)NetworkSingleton<TimeManager>.Instance == (Object)null) { return; } try { string lobbyData = _steamClient.GetLobbyData("__tns_time_multiplier"); if (!string.IsNullOrEmpty(lobbyData) && float.TryParse(lobbyData, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && float.IsFinite(result) && result > 0f && (float.IsNaN(_lastSyncedHostMultiplier) || Mathf.Abs(_lastSyncedHostMultiplier - result) > 0.0001f)) { TimeManager instance = NetworkSingleton<TimeManager>.Instance; if (!_clientOriginalMultiplier.HasValue) { _clientOriginalMultiplier = instance.TimeSpeedMultiplier; } TMAccess.SetTimeSpeedMultiplierDirect(instance, result); _lastSyncedHostMultiplier = result; TNSLog.Msg($"Synced time multiplier to host: {result:0.########}x (was {_clientOriginalMultiplier.Value:0.########}x)"); TNSLog.Debug($"[Config Sync] Client received and applied host time multiplier config: {result:0.########}x"); } } catch (Exception ex) { TNSLog.Warning("Error syncing to host multiplier: " + ex.Message); } } private IEnumerator ClientSyncTimeLoop() { WaitForSecondsRealtime waitTime = new WaitForSecondsRealtime(10f); while (IsInLobby && !IsHost && (Object)(object)NetworkSingleton<TimeManager>.Instance != (Object)null) { try { TimeManager tm = NetworkSingleton<TimeManager>.Instance; SteamNetworkClient? steamClient = _steamClient; string currentTimeStr = ((steamClient != null) ? steamClient.GetLobbyData("__tns_current_time") : null); SteamNetworkClient? steamClient2 = _steamClient; string elapsedDaysStr = ((steamClient2 != null) ? steamClient2.GetLobbyData("__tns_elapsed_days") : null); if (!string.IsNullOrEmpty(currentTimeStr) && !string.IsNullOrEmpty(elapsedDaysStr) && int.TryParse(currentTimeStr, out var hostCurrentTime) && int.TryParse(elapsedDaysStr, out var hostElapsedDays)) { bool hostDataChanged = _lastSyncedHostTime != hostCurrentTime || _lastSyncedHostDays != hostElapsedDays; bool isFirstSync = _lastSyncedHostTime == -1; if (hostDataChanged) { int localCurrentTime = tm.CurrentTime; int localElapsedDays = tm.ElapsedDays; int localMinutes = localCurrentTime / 100 * 60 + localCurrentTime % 100; int hostMinutes = hostCurrentTime / 100 * 60 + hostCurrentTime % 100; int timeDiff = Mathf.Abs(localMinutes - hostMinutes); int daysDiff = Mathf.Abs(localElapsedDays - hostElapsedDays); if (isFirstSync || (float)timeDiff > 5f || daysDiff > 0) { TMAccess.SetCurrentTime(tm, hostCurrentTime); if (daysDiff > 0 || isFirstSync) { TMAccess.SetElapsedDays(tm, hostElapsedDays); } TMAccess.SetDailyMinSum(tm, TimeManager.GetMinSumFrom24HourTime(hostCurrentTime)); _lastSyncedHostTime = hostCurrentTime; _lastSyncedHostDays = hostElapsedDays; TNSLog.Debug($"Client synced time to host: {localCurrentTime} -> {hostCurrentTime}, Days: {localElapsedDays} -> {hostElapsedDays}"); } else { TNSLog.Debug($"Client skipped sync: timeDiff={timeDiff}min <= {5f}, daysDiff={daysDiff}. (local={localCurrentTime:D4}, host={hostCurrentTime:D4})"); _lastSyncedHostTime = hostCurrentTime; _lastSyncedHostDays = hostElapsedDays; } } } } catch (Exception ex) { TNSLog.Warning("Error in client time sync loop: " + ex.Message); } yield return waitTime; } } private void OnLobbyDataChanged(object? sender, LobbyDataChangedEventArgs e) { if (_disposed || !IsInitialized || IsHost || !((Object)(object)NetworkSingleton<TimeManager>.Instance != (Object)null)) { return; } if (e.Key == "__tns_current_time" || e.Key == "__tns_elapsed_days" || e.Key == "__tns_time_update_ts") { try { TimeManager instance = NetworkSingleton<TimeManager>.Instance; SteamNetworkClient? steamClient = _steamClient; string text = ((steamClient != null) ? steamClient.GetLobbyData("__tns_current_time") : null); SteamNetworkClient? steamClient2 = _steamClient; string text2 = ((steamClient2 != null) ? steamClient2.GetLobbyData("__tns_elapsed_days") : null); if (!string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(text2) && int.TryParse(text, out var result) && int.TryParse(text2, out var result2) && (_lastSyncedHostTime != result || _lastSyncedHostDays != result2)) { TMAccess.SetCurrentTime(instance, result); TMAccess.SetElapsedDays(instance, result2); TMAccess.SetDailyMinSum(instance, TimeManager.GetMinSumFrom24HourTime(result)); _lastSyncedHostTime = result; _lastSyncedHostDays = result2; TNSLog.Debug($"Client synced time immediately on lobby data change: {result}, Days: {result2}"); } return; } catch (Exception ex) { TNSLog.Warning("Error syncing time on lobby data change: " + ex.Message); return; } } if (e.Key == "__tns_time_multiplier") { SyncToHostMultiplier(); } } private void OnLobbyJoined(object? sender, LobbyJoinedEventArgs e) { TNSLog.Debug($"Lobby joined. IsHost={IsHost}, TM ready={(Object)(object)NetworkSingleton<TimeManager>.Instance != (Object)null}."); if (!IsHost && (Object)(object)NetworkSingleton<TimeManager>.Instance != (Object)null) { SteamNetworkClient? steamClient = _steamClient; string text = ((steamClient != null) ? steamClient.GetLobbyData("__tns_time_multiplier") : null); TNSLog.Debug("Client joined lobby. Host multiplier in lobby data: '" + text + "'."); SyncToHostMultiplier(); } else if (IsHost) { TNSLog.Debug("Host joined/created lobby. Broadcasting multiplier."); SyncHostMultiplierToLobby(); } StartSyncCoroutines(); } private void OnLobbyCreated(object? sender, LobbyCreatedEventArgs e) { TNSLog.Debug($"Lobby created. IsHost={IsHost}."); if (IsHost) { SyncHostMultiplierToLobby(); } StartSyncCoroutines(); } private void OnLobbyLeft(object? sender, LobbyLeftEventArgs e) { if (sender != null) { TNSLog.Debug("Lobby left, stopping time sync"); } if (_syncCoroutine != null) { MelonCoroutines.Stop(_syncCoroutine); _syncCoroutine = null; } if (_readCoroutine != null) { MelonCoroutines.Stop(_readCoroutine); _readCoroutine = null; } if (_clientOriginalMultiplier.HasValue && (Object)(object)NetworkSingleton<TimeManager>.Instance != (Object)null) { try { TMAccess.SetTimeSpeedMultiplierDirect(NetworkSingleton<TimeManager>.Instance, _clientOriginalMultiplier.Value); TNSLog.Msg($"Restored original time multiplier: {_clientOriginalMultiplier.Value:0.########}x"); } catch (Exception ex) { TNSLog.Warning("Error restoring original multiplier: " + ex.Message); } _clientOriginalMultiplier = null; } _lastSyncedCurrentTime = -1; _lastSyncedElapsedDays = -1; _lastSyncedHour = -1; _lastSyncTimestamp = 0f; _lastSyncedMultiplier = float.NaN; _lastMultiplierCheckTime = 0f; _lastSyncedHostTime = -1; _lastSyncedHostDays = -1; _lastSyncedHostMultiplier = float.NaN; } public void Dispose() { if (_disposed) { return; } try { OnLobbyLeft(null, null); if (_steamClient != null) { _steamClient.OnLobbyJoined -= OnLobbyJoined; _steamClient.OnLobbyCreated -= OnLobbyCreated; _steamClient.OnLobbyLeft -= OnLobbyLeft; _steamClient.OnLobbyDataChanged -= OnLobbyDataChanged; _steamClient.Dispose(); _steamClient = null; } } catch (Exception ex) { TNSLog.Warning("Error disposing TimeSyncManager: " + ex.Message); } _disposed = true; _isInitialized = false; } } internal static class DailySummaryStore { internal readonly struct SoldItem { internal string Id { get; } internal int Count { get; } internal SoldItem(string id, int count) { Id = id; Count = count; } } private static readonly List<string> _unlocks; private static readonly List<string> _pendingRegions; private static readonly List<string> _regions; private static string _pendingOvernightMessage; private static readonly List<SoldItem> _items; private static readonly FieldInfo _fiItemsSold; private static readonly FieldInfo _fiMoneyPlayer; private static readonly FieldInfo _fiMoneyDealers; internal static bool HasSummary { get; private set; } internal static string CaptureError { get; private set; } internal static string DayLabel { get; private set; } internal static float MoneyEarnedByPlayer { get; private set; } internal static float MoneyEarnedByDealers { get; private set; } internal static int XpGained { get; private set; } internal static string RankBefore { get; private set; } internal static string RankAfter { get; private set; } internal static IReadOnlyList<string> Unlocks => _unlocks; internal static IReadOnlyList<string> RegionsUnlocked => _regions; internal static string OvernightMessage { get; private set; } internal static IReadOnlyList<SoldItem> Items => _items; internal static event Action OnSummaryChanged; static DailySummaryStore() { DayLabel = string.Empty; _unlocks = new List<string>(); _pendingRegions = new List<string>(); _regions = new List<string>(); _items = new List<SoldItem>(); _fiItemsSold = AccessTools.Field(typeof(DailySummary), "itemsSoldByPlayer"); _fiMoneyPlayer = AccessTools.Field(typeof(DailySummary), "moneyEarnedByPlayer"); _fiMoneyDealers = AccessTools.Field(typeof(DailySummary), "moneyEarnedByDealers"); if (_fiItemsSold == null || _fiMoneyPlayer == null || _fiMoneyDealers == null) { TNSLog.Warning("DailySummaryStore: one or more DailySummary fields were not found — the phone app will show an incomplete summary. A game update likely renamed them; check References\\MONO\\Assembly-CSharp\\ScheduleOne\\UI\\DailySummary.cs."); } } internal static void Capture(DailySummary ds) { try { if ((Object)(object)ds == (Object)null) { Fail("DailySummary instance was null at capture time."); return; } _items.Clear(); if (_fiItemsSold?.GetValue(ds) is Dictionary<string, int> dictionary) { foreach (KeyValuePair<string, int> item in dictionary) { _items.Add(new SoldItem(item.Key, item.Value)); } } MoneyEarnedByPlayer = ((_fiMoneyPlayer?.GetValue(ds) is float num) ? num : 0f); MoneyEarnedByDealers = ((_fiMoneyDealers?.GetValue(ds) is float num2) ? num2 : 0f); XpGained = ds.xpGained; DayLabel = BuildDayLabel(); CaptureRankUp(XpGained); _regions.Clear(); _regions.AddRange(_pendingRegions); _pendingRegions.Clear(); OvernightMessage = _pendingOvernightMessage; _pendingOvernightMessage = null; HasSummary = true; CaptureError = null; TNSLog.Debug($"Daily summary captured: {_items.Count} item type(s), " + $"player={MoneyEarnedByPlayer}, dealers={MoneyEarnedByDealers}, xp={XpGained}, " + "rankUp=" + ((RankAfter != null) ? (RankBefore + " -> " + RankAfter) : "none") + ", " + $"regions={_regions.Count}."); RaiseChanged(); } catch (Exception ex) { Fail(ex.GetType().Name + ": " + ex.Message); TNSLog.Error($"Daily summary capture failed, sleep is unaffected: {ex}"); } } private static void RaiseChanged() { try { DailySummaryStore.OnSummaryChanged?.Invoke(); } catch (Exception arg) { TNSLog.Error($"A summary-changed subscriber threw; sleep is unaffected: {arg}"); } } private static void CaptureRankUp(int xpGained) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: 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_005e: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) RankBefore = null; RankAfter = null; OvernightMessage = null; _unlocks.Clear(); try { LevelManager instance = NetworkSingleton<LevelManager>.Instance; if ((Object)(object)instance == (Object)null || xpGained <= 0) { return; } int totalXP = instance.TotalXP; FullRank fullRank = instance.GetFullRank(totalXP - xpGained); FullRank fullRank2 = instance.GetFullRank(totalXP); if (fullRank.Rank == fullRank2.Rank && fullRank.Tier == fullRank2.Tier) { return; } RankBefore = FullRank.GetString(fullRank); RankAfter = FullRank.GetString(fullRank2); try { if (instance.Unlockables == null || !instance.Unlockables.ContainsKey(fullRank2)) { return; } List<Unlockable> list = instance.Unlockables[fullRank2]; if (list == null) { return; } foreach (Unlockable item in list) { if (item != null && !string.IsNullOrEmpty(item.Title)) { _unlocks.Add(item.Title); } } } catch (Exception ex) { TNSLog.Debug("Could not read unlockables for the new rank: " + ex.Message); } } catch (Exception ex2) { TNSLog.Debug("Rank-up detection failed: " + ex2.Message); RankBefore = null; RankAfter = null; _unlocks.Clear(); } } internal unsafe static void NoteRegionUnlocked(EMapRegion region) { //IL_004d: 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) try { string text = ((object)(*(EMapRegion*)(®ion))/*cast due to .constrained prefix*/).ToString(); try { Map instance = Singleton<Map>.Instance; MapRegionData val = ((instance != null) ? instance.GetRegionData(region) : null); if (val != null && !string.IsNullOrEmpty(val.Name)) { text = val.Name; } } catch (Exception ex) { TNSLog.Debug($"Could not resolve region name for {region}: {ex.Message}"); } if (!_pendingRegions.Contains(text)) { _pendingRegions.Add(text); } TNSLog.Debug("Region unlock recorded for the summary: " + text); } catch (Exception ex2) { TNSLog.Debug("NoteRegionUnlocked failed: " + ex2.Message); } } internal static void NoteOvernightMessage(string message) { if (!string.IsNullOrEmpty(message)) { _pendingOvernightMessage = message; TNSLog.Debug("Overnight message recorded for the summary: " + message); } } private static void Fail(string reason) { HasSummary = false; CaptureError = reason; ClearData(); RaiseChanged(); } internal static void Reset() { HasSummary = false; CaptureError = null; ClearData(); _pendingRegions.Clear(); _pendingOvernightMessage = null; DailySummaryStore.OnSummaryChanged = null; } private static void ClearData() { DayLabel = string.Empty; _items.Clear(); _regions.Clear(); _unlocks.Clear(); RankBefore = null; RankAfter = null; MoneyEarnedByPlayer = (MoneyEarnedByDealers = 0f); XpGained = 0; } private static string BuildDayLabel() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) try { TimeManager instance = NetworkSingleton<TimeManager>.Instance; if ((Object)(object)instance == (Object)null) { return string.Empty; } return $"{instance.CurrentDay}, Day {instance.ElapsedDays + 1}"; } catch (Exception ex) { TNSLog.Debug("Could not build day label: " + ex.Message); return string.Empty; } } internal static bool TryGetItemDisplay(string id, out string displayName, out Sprite icon) { displayName = id; icon = null; try { ItemDefinition item = Registry.GetItem(id); if ((Object)(object)item == (Object)null) { return false; } if (!string.IsNullOrEmpty(((BaseItemDefinition)item).Name)) { displayName = ((BaseItemDefinition)item).Name; } icon = ((BaseItemDefinition)item).Icon; return true; } catch (Exception ex) { TNSLog.Debug("Could not resolve item '" + id + "': " + ex.Message); return false; } } internal static string FormatMoney(float amount) { try { return MoneyManager.FormatAmount(amount, false, false); } catch (Exception ex) { TNSLog.Debug("MoneyManager.FormatAmount failed: " + ex.Message); return "$" + amount.ToString("N2"); } } } internal static class S1APIBridge { private const string S1ApiAssemblyName = "S1API"; private const string PhoneAppTypeName = "S1API.PhoneApp.PhoneApp"; private static bool _probed; private static bool _present; private static bool _registered; private static string _failure; private static bool _loggedAbsence; internal static bool IsS1APIPresent { get { if (_probed) { return _present; } _probed = true; try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { if (string.Equals(assembly.GetName().Name, "S1API", StringComparison.OrdinalIgnoreCase)) { _present = assembly.GetType("S1API.PhoneApp.PhoneApp", throwOnError: false) != null; break; } } } catch (Exception ex) { _present = false; TNSLog.Debug("S1API probe failed, treating as absent: " + ex.Message); } return _present; } } internal static bool AppRegistered => _registered && _failure == null; internal static string AppFailure => _failure; internal static void MarkAppRegistered() { _registered = true; _failure = null; } internal static void MarkAppFailed(string reason) { _failure = reason ?? "unknown"; TNSLog.Error("Daily Summary phone app is unavailable (" + _failure + "). Falling back to the vanilla popup so the summary is not lost."); } internal static bool ShouldGateSummaryBehindApp() { MelonPreferences_Entry<bool> cfgEnableSummaryApp = Core.cfgEnableSummaryApp; if (cfgEnableSummaryApp == null || !cfgEnableSummaryApp.Value) { return false; } if (!IsS1APIPresent) { if (!_loggedAbsence) { _loggedAbsence = true; TNSLog.Error("EnableDailySummaryApp is on but S1API is not installed, so there is no phone app to show the summary in. Install ifBars-S1API_Forked, or set EnableDailySummaryApp=false to silence this. The vanilla popup stays enabled in the meantime."); } return false; } return AppRegistered; } internal static void Reset() { _registered = false; _failure = null; } } public class DailySummaryApp : PhoneApp { internal const string AppKey = "TNSDailySummary"; private const string IconResourceName = "Time_Never_Stops.icon.png"; private static readonly Color PanelColor = new Color(0.11f, 0.11f, 0.13f, 1f); private static readonly Color HeaderColor = new Color(0.16f, 0.16f, 0.19f, 1f); private static readonly Color MutedColor = new Color(0.68f, 0.68f, 0.72f, 1f); private static readonly Color AccentColor = new Color(0.45f, 0.8f, 0.55f, 1f); private static readonly Color StoryColor = new Color(0.92f, 0.8f, 0.45f, 1f); private GameObject _container; protected override string AppName => "TNSDailySummary"; protected override string AppTitle => "Daily Summary"; protected override string IconLabel => "Summary"; protected override string IconFileName => "TimeNeverStops_Summary.png"; protected override void OnCreated() { try { TrySetEmbeddedIcon(); ((PhoneApp)this).OnCreated(); S1APIBridge.MarkAppRegistered(); TNSLog.Msg("Daily Summary phone app registered."); } catch (Exception ex) { S1APIBridge.MarkAppFailed("OnCreated failed: " + ex.Message); TNSLog.Error($"Daily Summary app failed during OnCreated: {ex}"); } } protected override void OnCreatedUI(GameObject container) { _container = container; DailySummaryStore.OnSummaryChanged -= Rebuild; DailySummaryStore.OnSummaryChanged += Rebuild; Rebuild(); } protected override void OnDestroyed() { DailySummaryStore.OnSummaryChanged -= Rebuild; _container = null; ((PhoneApp)this).OnDestroyed(); } private void Rebuild() { GameObject container = _container; if ((Object)(object)container == (Object)null) { return; } try { UIFactory.ClearChildren(container.transform); BuildUI(container); } catch (Exception ex) { S1APIBridge.MarkAppFailed("UI build failed: " + ex.Message); TNSLog.Error($"Daily Summary app UI failed to build; the mod's time features are unaffected: {ex}"); TryShowFatalPanel(container, ex); } } private void BuildUI(GameObject container) { //IL_000c: 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_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Expected O, but got Unknown //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_031c: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_0364: Unknown result type (might be due to invalid IL or missing references) //IL_03c0: Unknown result type (might be due to invalid IL or missing references) //IL_03e8: Unknown result type (might be due to invalid IL or missing references) //IL_0410: Unknown result type (might be due to invalid IL or missing references) GameObject val = UIFactory.Panel("TNSSummaryPanel", container.transform, PanelColor, (Vector2?)null, (Vector2?)null, true); GameObject val2 = UIFactory.Panel("TNSSummaryHeader", val.transform, HeaderColor, (Vector2?)null, (Vector2?)null, false); SetRect(val2, 0f, 0.86f, 1f, 1f); Text val3 = UIFactory.Text("TNSSummaryTitle", "Daily Summary", val2.transform, 24, (TextAnchor)3, (FontStyle)1); SetRect(((Component)val3).gameObject, 0.03f, 0f, 0.97f, 1f); GameObject val4 = UIFactory.Panel("TNSSummaryBody", val.transform, PanelColor, (Vector2?)null, (Vector2?)null, false); SetRect(val4, 0f, 0f, 1f, 0.86f); if (!DailySummaryStore.HasSummary) { Text val5 = UIFactory.Text("TNSSummaryEmpty", EmptyMessage(), val4.transform, 18, (TextAnchor)4, (FontStyle)0); SetRect(((Component)val5).gameObject, 0.06f, 0.1f, 0.94f, 0.9f); return; } ScrollRect val7 = default(ScrollRect); RectTransform val6 = UIFactory.ScrollableVerticalList("TNSSummaryList", val4.transform, ref val7); val6.sizeDelta = new Vector2(0f, val6.sizeDelta.y); val6.anchoredPosition = new Vector2(0f, val6.anchoredPosition.y); VerticalLayoutGroup component = ((Component)val6).GetComponent<VerticalLayoutGroup>(); if ((Object)(object)component != (Object)null) { ((HorizontalOrVerticalLayoutGroup)component).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)component).childForceExpandWidth = true; ((HorizontalOrVerticalLayoutGroup)component).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)component).childForceExpandHeight = false; ((LayoutGroup)component).childAlignment = (TextAnchor)0; ((HorizontalOrVerticalLayoutGroup)component).spacing = 6f; ((LayoutGroup)component).padding = new RectOffset(16, 16, 12, 12); } if (!string.IsNullOrEmpty(DailySummaryStore.DayLabel)) { AddLine((Transform)(object)val6, "TNSSummaryDay", DailySummaryStore.DayLabel, 20, MutedColor); } if (!string.IsNullOrEmpty(DailySummaryStore.OvernightMessage)) { AddLine((Transform)(object)val6, "TNSSummaryOvernight", DailySummaryStore.OvernightMessage, 18, StoryColor); } if (DailySummaryStore.RankAfter != null) { AddLine((Transform)(object)val6, "TNSSummaryRank", "Rank up: " + DailySummaryStore.RankBefore + " -> " + DailySummaryStore.RankAfter, 20, AccentColor); IReadOnlyList<string> unlocks = DailySummaryStore.Unlocks; for (int i = 0; i < unlocks.Count; i++) { AddLine((Transform)(object)val6, $"TNSSummaryUnlock{i}", " Unlocked: " + unlocks[i], 16, MutedColor); } } IReadOnlyList<string> regionsUnlocked = DailySummaryStore.RegionsUnlocked; for (int j = 0; j < regionsUnlocked.Count; j++) { AddLine((Transform)(object)val6, $"TNSSummaryRegion{j}", "New region: " + regionsUnlocked[j], 20, AccentColor); } IReadOnlyList<DailySummaryStore.SoldItem> items = DailySummaryStore.Items; if (items.Count == 0) { AddLine((Transform)(object)val6, "TNSSummaryNoSales", "No sales recorded.", 18, MutedColor); } else { for (int k = 0; k < items.Count; k++) { AddItemRow((Transform)(object)val6, k, items[k]); } } AddLine((Transform)(object)val6, "TNSSummaryPlayer", "You earned " + DailySummaryStore.FormatMoney(DailySummaryStore.MoneyEarnedByPlayer), 20, AccentColor); AddLine((Transform)(object)val6, "TNSSummaryDealers", "Dealers earned " + DailySummaryStore.FormatMoney(DailySummaryStore.MoneyEarnedByDealers), 20, AccentColor); AddLine((Transform)(object)val6, "TNSSummaryXP", $"{DailySummaryStore.XpGained} XP", 20, AccentColor); } private static void SetRect(GameObject go, float xMin, float yMin, float xMax, float yMax) { //IL_0025: 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_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)go == (Object)null)) { RectTransform component = go.GetComponent<RectTransform>(); if (!((Object)(object)component == (Object)null)) { component.anchorMin = new Vector2(xMin, yMin); component.anchorMax = new Vector2(xMax, yMax); component.pivot = new Vector2(0.5f, 0.5f); component.offsetMin = Vector2.zero; component.offsetMax = Vector2.zero; ((Transform)component).localScale = Vector3.one; } } } private static string EmptyMessage() { string captureError = DailySummaryStore.CaptureError; if (!string.IsNullOrEmpty(captureError)) { return "Yesterday's summary could not be read.\n\n" + captureError; } return "No summary yet.\n\nIt will appear here after your first night."; } private static void AddLine(Transform parent, string name, string text, int size, Color color) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) Text val = UIFactory.Text(name, text, parent, size, (TextAnchor)3, (FontStyle)0); if (!((Object)(object)val == (Object)null)) { ((Graphic)val).color = color; LayoutElement val2 = ((Component)val).gameObject.AddComponent<LayoutElement>(); val2.minHeight = size + 8; } } private static void AddItemRow(Transform parent, int index, DailySummaryStore.SoldItem item) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) DailySummaryStore.TryGetItemDisplay(item.Id, out string displayName, out Sprite _); AddLine(parent, $"TNSSummaryItem{index}", $" {item.Count}x {displayName}", 18, Color.white); } private void TrySetEmbeddedIcon() { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown try { Assembly assembly = typeof(DailySummaryApp).Assembly; using Stream stream = assembly.GetManifestResourceStream("Time_Never_Stops.icon.png"); if (stream == null) { TNSLog.Debug("Embedded icon 'Time_Never_Stops.icon.png' not found; using the default app icon."); return; } byte[] array = new byte[stream.Length]; int num; for (int i = 0; i < array.Length; i += num) { num = stream.Read(array, i, array.Length - i); if (num <= 0) { break; } } Texture2D val = new Texture2D(2, 2); if (ImageConversion.LoadImage(val, array)) { ((PhoneApp)this).SetIconTexture(val); } else { TNSLog.Debug("Embedded icon failed to decode; using the default app icon."); } } catch (Exception ex) { TNSLog.Debug("Could not apply the embedded app icon: " + ex.Message); } } private static void TryShowFatalPanel(GameObject container, Exception cause) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) try { GameObject val = UIFactory.Panel("TNSSummaryError", container.transform, PanelColor, (Vector2?)null, (Vector2?)null, true); Text val2 = UIFactory.Text("TNSSummaryErrorText", "Daily Summary could not be displayed.\n\n" + cause.GetType().Name + ": " + cause.Message + "\n\nThe rest of Time Never Stops is unaffected.\nCheck MelonLoader\\Latest.log for detail.", val.transform, 16, (TextAnchor)4, (FontStyle)0); SetRect(((Component)val2).gameObject, 0.06f, 0.1f, 0.94f, 0.9f); } catch { } } } internal static class LocalMultiplayer { internal const ushort LoopbackPort = 38465; internal const KeyCode JoinKey = (KeyCode)287; private static bool _serverOpened; private static bool _syncNoticeLogged; private static object _openRoutine; internal static bool Enabled => Core.cfgLocalMultiplayer?.Value ?? false; internal static void Reset() { if (_openRoutine != null) { try { MelonCoroutines.Stop(_openRoutine); } catch { } _openRoutine = null; } _serverOpened = false; _syncNoticeLogged = false; } internal static void OnGameSceneLoaded() { if (Enabled) { _openRoutine = MelonCoroutines.Start(OpenLoopbackServerWhenReady()); } } internal static void Tick() { if (Enabled && Input.GetKeyDown((KeyCode)287)) { TryJoinLocalHost(); } } private static IEnumerator OpenLoopbackServerWhenReady() { float start = Time.realtimeSinceStartup; while (!InstanceFinder.IsServer && Time.realtimeSinceStartup - start < 180f) { yield return (object)new WaitForSecondsRealtime(0.5f); } if (!InstanceFinder.IsServer) { TNSLog.Debug("Local multiplayer: this instance is not the server, so there is no loopback port to open."); _openRoutine = null; } else if (Singleton<Lobby>.InstanceExists && (Object)(object)Singleton<Lobby>.Instance != (Object)null && Singleton<Lobby>.Instance.IsInLobby) { TNSLog.Msg("Local multiplayer: skipped — this session is in a Steam lobby, which already has real networking."); _openRoutine = null; } else { OpenLoopbackServer(); _openRoutine = null; } } private static void OpenLoopbackServer() { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Invalid comparison between Unknown and I4 try { TransportManager transportManager = InstanceFinder.TransportManager; Multipass val = ((transportManager != null) ? transportManager.GetTransport<Multipass>() : null); if ((Object)(object)val == (Object)null) { TNSLog.Warning("Local multiplayer: no Multipass transport on this build — cannot open the loopback port."); return; } Tugboat transport = val.GetTransport<Tugboat>(); if ((Object)(object)transport == (Object)null) { TNSLog.Error("Local multiplayer: Tugboat is not in this build's Multipass transport list, so a second instance cannot connect. Nothing else about the mod is affected."); return; } if ((int)((Transport)transport).GetConnectionState(true) == 2) { TNSLog.Debug("Local multiplayer: the loopback port is already open."); _serverOpened = true; return; } ((Transport)transport).SetPort((ushort)38465); if (!((Transport)transport).StartConnection(true)) { TNSLog.Error($"Local multiplayer: Tugboat refused to listen on port {(ushort)38465}. " + "Something else may already hold it."); return; } _serverOpened = true; TNSLog.Msg($"Local multiplayer: listening on 127.0.0.1:{(ushort)38465}. " + $"Press {(object)(KeyCode)287} in the OTHER instance to join this session."); LogSyncNoticeOnce(); } catch (Exception arg) { TNSLog.Error($"Local multiplayer: could not open the loopback port; the session is unaffected: {arg}"); } } private static void TryJoinLocalHost() { try { LoadManager val = (Singleton<LoadManager>.InstanceExists ? Singleton<LoadManager>.Instance : null); if ((Object)(object)val == (Object)null) { TNSLog.Warning("Local multiplayer: no LoadManager yet — wait for the main menu and try again."); return; } if (val.IsLoading) { TNSLog.Debug("Local multiplayer: already loading; ignoring the join key."); return; } if (InstanceFinder.IsServer) { TNSLog.Warning($"Local multiplayer: this instance IS the host — press {(object)(KeyCode)287} in the other one. Ignoring."); return; } TNSLog.Msg($"Local multiplayer: joining 127.0.0.1:{(ushort)38465} as a client..."); LogSyncNoticeOnce(); val.LoadAsClient("localhost"); } catch (Exception arg) { TNSLog.Error($"Local multiplayer: join failed; the session is unaffected: {arg}"); } } private static void LogSyncNoticeOnce() { if (!_syncNoticeLogged) { _syncNoticeLogged = true; TNSLog.Msg("Local multiplayer: there is no Steam lobby here, so TimeSyncManager's lobby-data sync is INACTIVE. The clock still tracks the host through the game's own PassMinute_Client RPC. Keep DaySpeedMultiplier identical in both instances — the host cannot push it without a lobby."); } } } internal static class BuildVersion { internal const string Value = "1.5.0"; } } namespace Time_Never_Stops.TemplateUtils { public static class AssetBundleUtils { private static Core mod = MelonAssembly.FindMelonInstance<Core>(); private static MelonAssembly melonAssembly = ((MelonBase)mod).MelonAssembly; public static AssetBundle LoadAssetBundle(string bundleFileName) { try { string text = typeof(Core).Namespace + ".Assets." + bundleFileName; Stream manifestResourceStream = melonAssembly.Assembly.GetManifestResourceStream(text ?? ""); if (manifestResourceStream == null) { ((MelonBase)mod).Unregister("AssetBundle: '" + text + "' not found. \nOpen .csproj file and search for '" + bundleFileName + "'.\nIf it doesn't exist,\nCopy your asset to Assets/ folder then look for 'your.assetbundle' in .csproj file.", false); return null; } return AssetBundle.LoadFromStream(manifestResourceStream); } catch (Exception arg) { ((MelonBase)mod).Unregister($"Failed to load AssetBundle. Please report to dev: {arg}", false); return null; } } public static AssetBundle GetLoadedAssetBundle(string asset_name_flag) { AssetBundle[] array = AssetBundle.GetAllLoadedAssetBundles().ToArray(); try { AssetBundle[] array2 = array; foreach (AssetBundle val in array2) { if (val.Contains(asset_name_flag)) { return val; } } string text = ""; AssetBundle[] array3 = array; foreach (AssetBundle val2 in array3) { string[] allAssetNames = val2.GetAllAssetNames(); string text2 = string.Join("\n\r -", allAssetNames); text = text + ((Object)val2).name + $"({allAssetNames.Length} assets):" + text2; } throw new Exception($"Asset '{asset_name_flag}' not found in {array.Length} bundle(s).\n{text}"); } catch (Exception arg) { ((MelonBase)mod).Unregister($"Failed to get loaded AssetBundle. Please report to dev: \n{arg}", false); return null; } } public static GameObject LoadAssetFromBundle(string asset_name) { AssetBundle loadedAssetBundle = GetLoadedAssetBundle(asset_name); return loadedAssetBundle.LoadAsset<GameObject>(asset_name); } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { internal IgnoresAccessChecksToAttribute(string assemblyName) { } } }