using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BaseRaidTimer.Configuration; using BaseRaidTimer.Localization; using BaseRaidTimer.Runtime; using BaseRaidTimer.UI; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Fusion; using HarmonyLib; using ModSettingsMenu.Api; using SunkenlandLocalizationAPI.Api; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("BaseRaidTimer")] [assembly: AssemblyDescription("Base raid countdown timer mod for Sunkenland by Ice Box Studio")] [assembly: AssemblyCompany("Ice Box Studio")] [assembly: AssemblyProduct("BaseRaidTimer")] [assembly: AssemblyCopyright("Copyright © 2026 Ice Box Studio All rights reserved.")] [assembly: ComVisible(false)] [assembly: Guid("6d68fc4b-c0c5-4663-a743-f2da4e5a1268")] [assembly: AssemblyFileVersion("1.0.1.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.1.0")] namespace BaseRaidTimer { [BepInPlugin("IceBoxStudio.Sunkenland.BaseRaidTimer", "BaseRaidTimer", "1.0.1")] [BepInDependency("IceBoxStudio.Sunkenland.ModSettingsMenu", "1.2.0")] [BepInDependency("IceBoxStudio.Sunkenland.LocalizationAPI", "1.1.0")] public sealed class BaseRaidTimer : BaseUnityPlugin { private Harmony _harmony; internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; LocalizationApi.LanguageChanged += OnLanguageChanged; try { Log.LogInfo((object)"============================================="); Log.LogInfo((object)("BaseRaidTimer " + I18n.Text("plugin.initializing"))); Log.LogInfo((object)(I18n.Text("plugin.author_prefix") + "Ice Box Studio(https://steamcommunity.com/id/ibox666/)")); ConfigManager.Bind(((BaseUnityPlugin)this).Config); RegisterSettings(); _harmony = new Harmony("IceBoxStudio.Sunkenland.BaseRaidTimer"); _harmony.PatchAll(Assembly.GetExecutingAssembly()); Log.LogInfo((object)("BaseRaidTimer " + I18n.Text("plugin.initialized"))); Log.LogInfo((object)"============================================="); } catch (Exception arg) { Log.LogError((object)$"Failed to initialize Base Raid Timer: {arg}"); } } private static void RegisterSettings() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005a: 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_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Expected O, but got Unknown ModSettingsRegistry.Register("IceBoxStudio.Sunkenland.BaseRaidTimer", new ModSettingsModOptions { Name = "Base Raid Timer", LocalizedName = () => I18n.Text("mod.name"), Description = I18n.Text("mod.description"), Author = "Ice Box Studio", Version = "1.0.1", NexusModsId = 81, ThunderstoreTeam = "Ice_Box_Studio_Sunkenland", ThunderstoreModName = "BaseRaidTimer" }); } private static void OnLanguageChanged(string language) { RegisterSettings(); } } public static class PluginInfo { public const string PLUGIN_GUID = "IceBoxStudio.Sunkenland.BaseRaidTimer"; public const string PLUGIN_NAME = "BaseRaidTimer"; public const string PLUGIN_VERSION = "1.0.1"; } } namespace BaseRaidTimer.UI { internal static class RaidTimerDisplay { internal static void Append(TextMeshProUGUI timeText) { if (ConfigManager.Enabled && !((Object)(object)timeText == (Object)null) && RaidCountdown.TryGet(out var state, out var seconds)) { ((TMP_Text)timeText).enableWordWrapping = false; ((TMP_Text)timeText).text = ((TMP_Text)timeText).text + " | " + GetText(state, seconds); } } private static string GetText(RaidState state, int seconds) { if (state == RaidState.HostDataUnavailable) { return I18n.Text("ui.raid.host_data_unavailable"); } return I18n.Text("ui.raid.countdown", FormatTime(seconds)); } private static string FormatTime(int totalSeconds) { int num = totalSeconds / 86400; int num2 = totalSeconds / 3600 % 24; int num3 = totalSeconds / 60 % 60; int num4 = totalSeconds % 60; if (num > 0) { return I18n.Text("ui.duration.days", num, num2, num3, num4); } return I18n.Text("ui.duration.time", num2, num3, num4); } } } namespace BaseRaidTimer.Runtime { internal enum RaidState { Countdown, HostDataUnavailable } internal static class RaidCountdown { internal static bool TryGet(out RaidState state, out int seconds) { state = RaidState.Countdown; seconds = 0; GameLogics code = GameLogics.code; Mainframe code2 = Mainframe.code; if ((Object)(object)code == (Object)null || (Object)(object)code2 == (Object)null || (Object)(object)code2.Networking == (Object)null) { return false; } if (!code2.Networking.IsDedicatedServer && !code2.Networking.IsInSinglePlayerGameSession && !code2.Networking.IsServerAndInGameSession) { if (!RaidSync.TryGet(out var visible, out seconds)) { state = RaidState.HostDataUnavailable; return true; } state = RaidState.Countdown; return visible; } return TryGetHost(code, out seconds); } internal static bool TryGetHost(GameLogics logic, out int seconds) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) seconds = 0; Mainframe code = Mainframe.code; if ((Object)(object)logic == (Object)null || (Object)(object)code == (Object)null || (Object)(object)code.WorldManager == (Object)null) { return false; } GlobalData globalData = code.GlobalData; if ((Object)(object)globalData == (Object)null || (Object)(object)((SimulationBehaviour)globalData).Object == (Object)null) { return false; } if ((int)code.WorldManager.EnemyRaidDifficulty == 0 && !logic.SlaverAttacking) { return false; } if (globalData.GameTime <= 1 && !logic.SlaverAttacking) { return false; } if (float.IsNaN(logic.nextSiegeTime) || float.IsInfinity(logic.nextSiegeTime)) { return false; } seconds = Mathf.Max(0, Mathf.FloorToInt(logic.nextSiegeTime)); return true; } } internal static class RaidSync { private enum MessageType : byte { Hello, Hidden, Countdown } private const int PacketSize = 12; private const byte ProtocolVersion = 1; private const float StateTimeout = 5f; private const float RequestInterval = 2f; private const int MaxRequests = 3; private static readonly HashSet Clients = new HashSet(); private static NetworkRunner _runner; private static FusionCallbacksHandler _callbacks; private static bool _hasState; private static bool _visible; private static int _seconds; private static float _receivedAt; private static float _lastRequestAt = -2f; private static int _requestCount; internal static void Bind() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_004d: 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_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Expected O, but got Unknown NetworkRunner runner = GetRunner(); if (_runner == runner) { return; } Unbind(); if (!((Object)(object)runner == (Object)null)) { _runner = runner; _callbacks = new FusionCallbacksHandler { PlayerLeft = OnPlayerLeft, Shutdown = OnShutdown, DisconnectedFromServer = OnDisconnected, HostMigration = OnHostMigration, ReliableDataReceived = OnData, SceneLoadStart = OnSceneLoadStart }; runner.AddCallbacks((INetworkRunnerCallbacks[])(object)new INetworkRunnerCallbacks[1] { (INetworkRunnerCallbacks)_callbacks }); if (runner.IsClient && !runner.IsServer) { Request(runner, force: true); } } } internal static void Tick(GameLogics logic) { Bind(); NetworkRunner runner = _runner; if (!((Object)(object)runner == (Object)null)) { if (runner.IsServer) { Broadcast(runner, logic); } else if (runner.IsClient && !IsFresh()) { Request(runner, force: false); } } } internal static bool TryGet(out bool visible, out int seconds) { visible = false; seconds = 0; if (!IsFresh()) { return false; } visible = _visible; seconds = _seconds; return true; } internal static bool IsPacket(ArraySegment data) { byte[] array = data.Array; if (array == null || data.Count < 4) { return false; } int offset = data.Offset; if (array[offset] == 66 && array[offset + 1] == 82 && array[offset + 2] == 84) { return array[offset + 3] == 83; } return false; } private static NetworkRunner GetRunner() { Mainframe code = Mainframe.code; if ((Object)(object)code == (Object)null || (Object)(object)code.Networking == (Object)null) { return null; } return code.Networking.CurrentRunner; } private static void Broadcast(NetworkRunner runner, GameLogics logic) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) if (Clients.Count == 0) { return; } byte[] array = CreateState(logic); foreach (PlayerRef activePlayer in runner.ActivePlayers) { if (activePlayer != runner.LocalPlayer && Clients.Contains(activePlayer)) { runner.SendReliableDataToPlayer(activePlayer, array); } } } private static void SendState(NetworkRunner runner, PlayerRef player) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) if (!(player == runner.LocalPlayer)) { runner.SendReliableDataToPlayer(player, CreateState(GameLogics.code)); } } private static byte[] CreateState(GameLogics logic) { if (!RaidCountdown.TryGetHost(logic, out var seconds)) { return CreatePacket(MessageType.Hidden, 0); } return CreatePacket(MessageType.Countdown, seconds); } private static void Request(NetworkRunner runner, bool force) { float time = Time.time; if (force || (_requestCount < 3 && !(time - _lastRequestAt < 2f))) { runner.SendReliableDataToServer(CreatePacket(MessageType.Hello, 0)); _lastRequestAt = time; _requestCount++; } } private static void OnData(NetworkRunner runner, PlayerRef player, ArraySegment data) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) if (runner != _runner || !TryRead(data, out var type, out var seconds)) { return; } if (runner.IsServer) { if (type == MessageType.Hello && Clients.Add(player)) { SendState(runner, player); } } else if (runner.IsClient && type != MessageType.Hello) { _hasState = true; _visible = type == MessageType.Countdown; _seconds = (_visible ? seconds : 0); _receivedAt = Time.time; _requestCount = 0; } } private static void OnPlayerLeft(NetworkRunner runner, PlayerRef player) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (runner == _runner && runner.IsServer) { Clients.Remove(player); } } private static void OnShutdown(NetworkRunner runner, ShutdownReason reason) { Drop(runner); } private static void OnDisconnected(NetworkRunner runner) { Drop(runner); } private static void OnHostMigration(NetworkRunner runner, HostMigrationToken token) { if (runner == _runner) { Reset(); } } private static void OnSceneLoadStart(NetworkRunner runner) { if (runner == _runner) { ClearState(); _lastRequestAt = -2f; _requestCount = 0; } } private static bool IsFresh() { if (_hasState) { return Time.time - _receivedAt <= 5f; } return false; } private static bool TryRead(ArraySegment data, out MessageType type, out int seconds) { type = MessageType.Hello; seconds = 0; if (data.Count != 12 || !IsPacket(data)) { return false; } byte[] array = data.Array; int offset = data.Offset; if (array[offset + 4] != 1) { return false; } type = (MessageType)array[offset + 5]; if (type != MessageType.Hello && type != MessageType.Hidden && type != MessageType.Countdown) { return false; } seconds = array[offset + 8] | (array[offset + 9] << 8) | (array[offset + 10] << 16) | (array[offset + 11] << 24); if (type == MessageType.Countdown) { return seconds >= 0; } return true; } private static byte[] CreatePacket(MessageType type, int seconds) { return new byte[12] { 66, 82, 84, 83, 1, (byte)type, 0, 0, (byte)seconds, (byte)(seconds >> 8), (byte)(seconds >> 16), (byte)(seconds >> 24) }; } private static void Unbind() { NetworkRunner runner = _runner; FusionCallbacksHandler callbacks = _callbacks; _runner = null; _callbacks = null; Reset(); if ((Object)(object)runner != (Object)null && callbacks != null) { runner.RemoveCallbacks((INetworkRunnerCallbacks[])(object)new INetworkRunnerCallbacks[1] { (INetworkRunnerCallbacks)callbacks }); } } private static void Drop(NetworkRunner runner) { if (runner == _runner) { _runner = null; _callbacks = null; Reset(); } } private static void Reset() { Clients.Clear(); ClearState(); _lastRequestAt = -2f; _requestCount = 0; } private static void ClearState() { _hasState = false; _visible = false; _seconds = 0; _receivedAt = 0f; } } } namespace BaseRaidTimer.Patches { [HarmonyPatch(typeof(GameLogics), "CS")] internal static class GameLogicsCsPatch { private static void Postfix(GameLogics __instance) { RaidSync.Tick(__instance); } } [HarmonyPatch(typeof(GameLogics), "Start")] internal static class GameLogicsStartPatch { private static void Postfix() { RaidSync.Bind(); } } [HarmonyPatch(typeof(NetworkRunnerCallbackManager), "OnReliableDataReceived", new Type[] { typeof(NetworkRunner), typeof(PlayerRef), typeof(ArraySegment) })] internal static class ReliableDataLogPatch { private static bool Prefix(ArraySegment data) { return !RaidSync.IsPacket(data); } } [HarmonyPatch(typeof(UICombat), "Update")] internal static class UICombatUpdatePatch { private static void Postfix(TextMeshProUGUI ___tmpHour) { RaidTimerDisplay.Append(___tmpHour); } } } namespace BaseRaidTimer.Localization { internal static class I18n { private const string FileName = "BaseRaidTimer.Localization.json"; private static readonly ModLocalizer _localizer = Load(); internal static ModLocalizer Localizer => _localizer; internal static string Text(string key, params object[] args) { return _localizer.GetLocalizedText(key, args); } private static ModLocalizer Load() { string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); ModLocalizer obj = LocalizationApi.For("IceBoxStudio.Sunkenland.BaseRaidTimer"); obj.RegisterJson(Path.Combine(directoryName, "BaseRaidTimer.Localization.json")); return obj; } } } namespace BaseRaidTimer.Configuration { internal static class ConfigManager { private const string Section = "Base Raid Timer"; private static ConfigEntry _enabled; internal static bool Enabled { get { if (_enabled != null) { return _enabled.Value; } return true; } } internal static void Bind(ConfigFile config) { _enabled = config.Bind("Base Raid Timer", "Enabled", true, I18n.Localizer.Config("config.enabled", 10, "Base Raid Timer", "config.section", 10, (AcceptableValueBase)null)); } } }