using System; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Pigeon.Math; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Sparroh")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("WeeklyTimerFix")] [assembly: AssemblyTitle("WeeklyTimerFix")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.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.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } internal static class ConfigManager { private static ConfigFile config; private static ManualLogSource log; private static FileSystemWatcher watcher; private static bool reloadPending; private static float reloadReadyTime; private const float ReloadDebounceSeconds = 0.25f; public static ConfigEntry UseUnifiedOvertimeSeed { get; private set; } public static bool UnifiedOvertimeSeedEnabled { get { if (UseUnifiedOvertimeSeed != null) { return UseUnifiedOvertimeSeed.Value; } return false; } } public static void Initialize(ConfigFile configFile, ManualLogSource logSource) { config = configFile; log = logSource; UseUnifiedOvertimeSeed = config.Bind("General", "Use Unified Overtime Seed", false, "When enabled, Weekly Overtime mission seed (and legacy weekData content) use the same Mycopunk week boundary as Incursion/Cranius/ResetWeekly (Thu 17:00 UTC). When disabled (default), overtime keeps the vanilla seed/roll so it matches unmodded clients. The weekly reset TIMER is always synced either way."); StartWatcher(); } public static void Tick() { if (!reloadPending || config == null || Time.unscaledTime < reloadReadyTime) { return; } reloadPending = false; try { config.Reload(); ManualLogSource obj = log; if (obj != null) { obj.LogInfo((object)"Config reloaded."); } WeeklyTimerPatches.OnConfigChanged(); } catch (Exception ex) { ManualLogSource obj2 = log; if (obj2 != null) { obj2.LogError((object)("Config reload failed: " + ex.Message)); } } } public static void Dispose() { if (watcher != null) { watcher.EnableRaisingEvents = false; watcher.Changed -= OnWatcherEvent; watcher.Created -= OnWatcherEvent; watcher.Renamed -= OnWatcherEvent; watcher.Dispose(); watcher = null; } } private static void StartWatcher() { try { string configPath = Paths.ConfigPath; string fileName = Path.GetFileName(config.ConfigFilePath); if (!string.IsNullOrEmpty(configPath) && !string.IsNullOrEmpty(fileName)) { watcher = new FileSystemWatcher(configPath, fileName) { NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite) }; watcher.Changed += OnWatcherEvent; watcher.Created += OnWatcherEvent; watcher.Renamed += OnWatcherEvent; watcher.EnableRaisingEvents = true; } } catch (Exception ex) { ManualLogSource obj = log; if (obj != null) { obj.LogWarning((object)("Config file watcher not started: " + ex.Message)); } } } private static void OnWatcherEvent(object sender, FileSystemEventArgs e) { reloadPending = true; reloadReadyTime = Time.unscaledTime + 0.25f; } } [BepInPlugin("sparroh.weeklytimerfix", "WeeklyTimerFix", "1.0.0")] [MycoMod(/*Could not decode attribute arguments.*/)] public class SparrohPlugin : BaseUnityPlugin { public const string PluginGUID = "sparroh.weeklytimerfix"; public const string PluginName = "WeeklyTimerFix"; public const string PluginVersion = "1.0.0"; internal static ManualLogSource Log; private Harmony _harmony; private void Awake() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; ConfigManager.Initialize(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger); _harmony = new Harmony("sparroh.weeklytimerfix"); try { _harmony.PatchAll(typeof(WeeklyTimerPatches)); WeeklyTimerPatches.OnConfigChanged(); Log.LogInfo((object)("WeeklyTimerFix v1.0.0 loaded — overtime timer synced to Global.CurrentWeek; unified overtime seed=" + (ConfigManager.UnifiedOvertimeSeedEnabled ? "ON" : "OFF (vanilla)") + "; granular weekly countdowns enabled.")); } catch (Exception arg) { Log.LogError((object)$"Error applying patches: {arg}"); } } private void Update() { ConfigManager.Tick(); } private void OnDestroy() { ConfigManager.Dispose(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } internal static class WeeklyTimerPatches { private static readonly FieldInfo WeekDataField = AccessTools.Field(typeof(WeeklyOvertimeAssignment), "weekData"); private static readonly Type WeekDataType = AccessTools.Inner(typeof(WeeklyOvertimeAssignment), "WeekData"); private static bool weekDataCleared; private static bool warnedWeekDataNeedsRestart; private static readonly FieldInfo MissionSelectTimerField = AccessTools.Field(typeof(MissionSelectButton), "timer"); private static readonly FieldInfo MissionSelectLastTimerField = AccessTools.Field(typeof(MissionSelectButton), "lastTimerUpdateTime"); private static readonly FieldInfo MissionSelectMissionField = AccessTools.Field(typeof(MissionSelectButton), "mission"); internal static long GetNextWeeklyResetUnix() { return 1752166800 + (long)(Global.CurrentWeek + 1) * 604800L; } internal static int GetSyncedOvertimeSeed() { return MathUtil.Squirrel3Hash((int)GetNextWeeklyResetUnix(), 47832154); } internal static void OnConfigChanged() { if (ConfigManager.UnifiedOvertimeSeedEnabled) { ClearLegacyWeekData(); } else if (weekDataCleared && !warnedWeekDataNeedsRestart) { warnedWeekDataNeedsRestart = true; ManualLogSource log = SparrohPlugin.Log; if (log != null) { log.LogWarning((object)"Use Unified Overtime Seed was turned off after legacy weekData was cleared this session. Restart the game to fully restore vanilla overtime weekData content."); } } } internal static void ClearLegacyWeekData() { if (!ConfigManager.UnifiedOvertimeSeedEnabled || weekDataCleared || WeekDataField == null || WeekDataType == null) { return; } WeeklyOvertimeAssignment val = null; try { if (Global.Instance != null) { val = Global.Instance.OvertimeAssignment; } } catch (Exception) { return; } if (val != null) { Array value = Array.CreateInstance(WeekDataType, 0); WeekDataField.SetValue(val, value); weekDataCleared = true; ManualLogSource log = SparrohPlugin.Log; if (log != null) { log.LogInfo((object)"Cleared legacy WeeklyOvertimeAssignment.weekData; unified overtime seed uses Global.CurrentWeek."); } } } [HarmonyPatch(typeof(Global), "LoadInstance")] [HarmonyPostfix] private static void GlobalLoadInstancePostfix() { ClearLegacyWeekData(); } [HarmonyPatch(typeof(WeeklyOvertimeAssignment), "GetResetDateTime")] [HarmonyPrefix] private static bool GetResetDateTimePrefix(out long unixTime, ref bool __result) { unixTime = GetNextWeeklyResetUnix(); __result = true; return false; } [HarmonyPatch(typeof(WeeklyOvertimeAssignment), "GetMissionSeed")] [HarmonyPrefix] private static bool GetMissionSeedPrefix(out int seed, ref bool __result) { if (!ConfigManager.UnifiedOvertimeSeedEnabled) { seed = 0; return true; } ClearLegacyWeekData(); seed = GetSyncedOvertimeSeed(); __result = true; return false; } [HarmonyPatch(typeof(MissionSelectButton), "Update")] [HarmonyPrefix] private static bool MissionSelectButtonUpdatePrefix(MissionSelectButton __instance) { //IL_005a: 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) if (MissionSelectTimerField == null || MissionSelectLastTimerField == null || MissionSelectMissionField == null) { return true; } object? value = MissionSelectTimerField.GetValue(__instance); TextMeshProUGUI val = (TextMeshProUGUI)((value is TextMeshProUGUI) ? value : null); if ((Object)(object)val == (Object)null) { return false; } long num = default(long); if (!(MissionSelectMissionField.GetValue(__instance) is MissionData val2) || ((MissionData)(ref val2)).Container == null || !((MissionData)(ref val2)).Container.GetResetDateTime(((MissionData)(ref val2)).Mission, ref num)) { return false; } float unscaledTime = Time.unscaledTime; float num2 = (float)MissionSelectLastTimerField.GetValue(__instance); if (unscaledTime - num2 <= 1f) { return false; } MissionSelectLastTimerField.SetValue(__instance, unscaledTime); int num3 = FillGranularWeeklyDuration(num - Global.GlobalSeconds, Global.charBuffer); ((TMP_Text)val).SetCharArray(Global.charBuffer, 0, num3); return false; } internal static int FillGranularWeeklyDuration(long totalSeconds, char[] buffer) { if (totalSeconds < 0) { totalSeconds = 0L; } int num = (int)(totalSeconds / 86400); int num2 = (int)(totalSeconds % 86400 / 3600); int value = (int)(totalSeconds % 3600 / 60); int value2 = (int)(totalSeconds % 60); int index = 0; if (num > 0) { index = WriteInt(num, buffer, index); buffer[index++] = 'd'; buffer[index++] = ' '; index = WriteTwoDigits(num2, buffer, index); buffer[index++] = ':'; index = WriteTwoDigits(value, buffer, index); buffer[index++] = ':'; return WriteTwoDigits(value2, buffer, index); } if (num2 > 0) { index = WriteInt(num2, buffer, index); buffer[index++] = ':'; index = WriteTwoDigits(value, buffer, index); buffer[index++] = ':'; return WriteTwoDigits(value2, buffer, index); } index = WriteInt(value, buffer, index); buffer[index++] = ':'; return WriteTwoDigits(value2, buffer, index); } private static int WriteInt(int value, char[] buffer, int index) { if (value == 0) { buffer[index++] = '0'; return index; } int num = index; for (int num2 = value; num2 > 0; num2 /= 10) { buffer[index++] = (char)(48 + num2 % 10); } int num3 = index - 1; while (num < num3) { char c = buffer[num]; buffer[num] = buffer[num3]; buffer[num3] = c; num++; num3--; } return index; } private static int WriteTwoDigits(int value, char[] buffer, int index) { if (value < 0) { value = 0; } if (value > 99) { value %= 100; } buffer[index++] = (char)(48 + value / 10); buffer[index++] = (char)(48 + value % 10); return index; } } namespace WeeklyTimerFix { public static class MyPluginInfo { public const string PLUGIN_GUID = "WeeklyTimerFix"; public const string PLUGIN_NAME = "WeeklyTimerFix"; public const string PLUGIN_VERSION = "1.0.0"; } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }