Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of TombstoneLock v1.0.5
TombstoneLock.dll
Decompiled 8 hours agousing System; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using HarmonyLib; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("TombstoneLock")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("TombstoneLock")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("7ECD0A4B-BBD9-4A43-BBD3-C4FFD289E99A")] [assembly: AssemblyFileVersion("1.0.5")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.5.0")] namespace TombstoneLock; internal sealed class PendingRequest { public long RequesterPlayerID; public string RequesterName; public ZDOID Tombstone; public DateTime Time; } public class ChatNotifier { private static void OpenChat() { Chat instance = Chat.instance; if (!((Object)(object)instance == (Object)null)) { AccessTools.Field(typeof(Chat), "m_hideTimer")?.SetValue(instance, 0f); object? obj = AccessTools.Field(typeof(Terminal), "m_chatWindow")?.GetValue(instance); object? obj2 = ((obj is Image) ? obj : null); if (obj2 != null) { ((Component)obj2).gameObject.SetActive(true); } object? obj3 = AccessTools.Field(typeof(Terminal), "m_input")?.GetValue(instance); TMP_InputField val = (TMP_InputField)((obj3 is TMP_InputField) ? obj3 : null); if ((Object)(object)val != (Object)null) { ((Component)val).gameObject.SetActive(true); val.ActivateInputField(); } } } public static void ShowInChat(string text, bool openChat = true) { Chat instance = Chat.instance; if ((Object)(object)instance != (Object)null) { OpenChat(); Localization instance2 = Localization.instance; ((Terminal)instance).AddString(((instance2 != null) ? instance2.Localize(text) : null) ?? text); } } } public class OnScreenNotifier { public static void Message(Player player, MessageType location, string message) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) Localization instance = Localization.instance; ((Character)player).Message(location, ((instance != null) ? instance.Localize(message) : null) ?? message, 0, (Sprite)null); } } [BepInPlugin("TombstoneLock", "TombstoneLock", "1.0.5")] public class TombstoneLockPlugin : BaseUnityPlugin { [HarmonyPatch(typeof(Game), "Start")] internal static class RpcRegistrationPatch { private static void Postfix() { MainManager.RegisterRpcs(); } } [HarmonyPatch(typeof(Localization), "SetupLanguage")] internal static class LocalizationSetupPatch { private static readonly MethodInfo AddWordMethod = AccessTools.Method(typeof(Localization), "AddWord", (Type[])null, (Type[])null); private static void Postfix(Localization __instance) { AddTranslations(__instance); } internal static void AddTranslations(Localization localization) { string selectedLanguage = localization.GetSelectedLanguage(); Assembly executingAssembly = Assembly.GetExecutingAssembly(); string text = "TombstoneLock.Translations." + selectedLanguage + ".properties"; if (executingAssembly.GetManifestResourceInfo(text) == null) { text = "TombstoneLock.Translations.English.properties"; } using Stream stream = executingAssembly.GetManifestResourceStream(text); if (stream == null) { Log.LogWarning((object)("Embedded translation not found: " + text)); return; } using StreamReader streamReader = new StreamReader(stream); string text2; while ((text2 = streamReader.ReadLine()) != null) { if (!string.IsNullOrWhiteSpace(text2) && !text2.TrimStart(Array.Empty<char>()).StartsWith("#")) { int num = text2.IndexOf('='); if (num <= 0) { Log.LogWarning((object)("Invalid translation line in " + text + ": " + text2)); continue; } string key = text2.Substring(0, num).Trim(); string text3 = text2.Substring(num + 1).Replace("\\n", "\n"); AddWord(localization, key, text3); } } } private static void AddWord(Localization localization, string key, string text) { AddWordMethod.Invoke(localization, new object[2] { key, text }); } } [HarmonyPatch(typeof(TombStone), "Awake")] internal static class TombStoneAwakePatch { private static void Postfix(TombStone __instance) { MainManager.AssignOwnerOnCreate(__instance); } } [HarmonyPatch(typeof(TombStone), "Interact")] internal static class TombStoneInteractPatch { private static bool Prefix(TombStone __instance, Humanoid character) { return MainManager.HandleInteract(__instance, character); } } [HarmonyPatch(typeof(Terminal), "InitTerminal")] internal static class TerminalCommandsPatch { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__1_0; internal void <Postfix>b__1_0(ConsoleEventArgs args) { MainManager.AcceptPendingRequest(); } } private static bool commandRegistered; private static void Postfix() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown if (commandRegistered) { return; } commandRegistered = true; object obj = <>c.<>9__1_0; if (obj == null) { ConsoleEvent val = delegate { MainManager.AcceptPendingRequest(); }; <>c.<>9__1_0 = val; obj = (object)val; } new ConsoleCommand("accept", "Accepts a pending tombstone unlock request.", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } } public const string PluginGuid = "TombstoneLock"; public const string PluginName = "TombstoneLock"; public const string PluginVersion = "1.0.5"; internal static ManualLogSource Log; private readonly Harmony harmony = new Harmony("TombstoneLock"); private void Awake() { Log = ((BaseUnityPlugin)this).Logger; harmony.PatchAll(Assembly.GetExecutingAssembly()); if (Localization.instance != null) { LocalizationSetupPatch.AddTranslations(Localization.instance); } Log.LogInfo((object)"TombstoneLock loaded"); } private void Update() { MainManager.Tick(); } } internal static class MainManager { public const string MsgRequestSent = "<b>$msg_cantopen\n\n$tombstonelock_request_sent</b>"; public const string MsgOwnerOffline = "<b>$msg_cantopen\n\n$tombstonelock_owner_offline</b>"; public const string MsgCannotSendRequestNow = "<b>$msg_cantopen\n\n$tombstonelock_cannot_send_request_now</b>"; public const string MsgOwnerNotification = "$tombstonelock_owner_notification"; public const string MsgNoPendingRequest = "$tombstonelock_no_pending_request"; public const string MsgRequestAccepted = "$tombstonelock_request_accepted"; public const string MsgRequestExpired = "$tombstonelock_request_expired"; public const string MsgRequestExpiredRequester = "$tombstonelock_request_expired_requester"; public const string MsgFriendConfirmation = "$tombstonelock_friend_confirmation"; private const string OwnerKey = "ownerID"; private const string LockDayKey = "lockDay"; private const int UnlockAfterDays = 10; internal const int RequestExpirySeconds = 30; private const int AntiSpamCooldownSeconds = 120; private const string RequestRpc = "RPC_RequestTombstoneUnlock"; private const string ConfirmRpc = "RPC_ConfirmTombstoneUnlock"; private const string ExpiredRpc = "RPC_TombstoneRequestExpired"; private static PendingRequest pendingRequest; private static DateTime lastSentRequestTime = DateTime.MinValue; private static ZRoutedRpc registeredInstance; internal static void RegisterRpcs() { if (ZRoutedRpc.instance == null) { LogDebug("RegisterRpcs: ZRoutedRpc.instance is null (networking not ready yet), skipping."); } else if (registeredInstance != ZRoutedRpc.instance) { ZRoutedRpc.instance.Register<long, string, ZDOID>("RPC_RequestTombstoneUnlock", (Action<long, long, string, ZDOID>)RPC_RequestTombstoneUnlock); ZRoutedRpc.instance.Register<long, ZDOID, string>("RPC_ConfirmTombstoneUnlock", (Action<long, long, ZDOID, string>)RPC_ConfirmTombstoneUnlock); ZRoutedRpc.instance.Register<long>("RPC_TombstoneRequestExpired", (Action<long, long>)RPC_TombstoneRequestExpired); registeredInstance = ZRoutedRpc.instance; LogInfo("Network RPCs registered for this session."); } } private static void LogInfo(string message) { ManualLogSource log = TombstoneLockPlugin.Log; if (log != null) { log.LogInfo((object)("[TombstoneLock] " + message)); } } private static void LogDebug(string message) { ManualLogSource log = TombstoneLockPlugin.Log; if (log != null) { log.LogDebug((object)("[TombstoneLock] " + message)); } } private static void LogWarning(string message) { ManualLogSource log = TombstoneLockPlugin.Log; if (log != null) { log.LogWarning((object)("[TombstoneLock] " + message)); } } internal static void AssignOwnerOnCreate(TombStone tombstone) { //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) ZNetView component = ((Component)tombstone).GetComponent<ZNetView>(); ZDO val = ((component != null) ? component.GetZDO() : null); if (val == null || !component.IsOwner()) { LogDebug("AssignOwnerOnCreate: no valid ZDO or not the ZDO owner, skipping (this is normal for remote tombstones)."); return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { LogDebug("AssignOwnerOnCreate: no local player, skipping."); } else if (val.GetLong("ownerID", 0L) == 0L) { long num = CurrentDay(); val.Set("ownerID", localPlayer.GetPlayerID()); val.Set("lockDay", num); LogInfo($"Locked new tombstone {val.m_uid} to owner {localPlayer.GetPlayerID()} on in-game day {num}."); } else { LogDebug(string.Format("AssignOwnerOnCreate: tombstone {0} already has owner {1}, leaving as is.", val.m_uid, val.GetLong("ownerID", 0L))); } } private static long CurrentDay() { if (!((Object)(object)EnvMan.instance != (Object)null)) { return -1L; } return EnvMan.instance.GetDay(); } private static bool IsLockExpiredByAge(ZDO zdo) { long num = zdo.GetLong("lockDay", -1L); if (num < 0) { return false; } long num2 = CurrentDay(); if (num2 >= 0) { return num2 - num >= 10; } return false; } internal static bool HandleInteract(TombStone tombstone, Humanoid character) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) Player val = (Player)(object)((character is Player) ? character : null); if ((Object)(object)val == (Object)null) { return true; } ZNetView component = ((Component)tombstone).GetComponent<ZNetView>(); ZDO val2 = ((component != null) ? component.GetZDO() : null); if (val2 == null) { LogDebug("HandleInteract: tombstone has no ZDO, letting the base game handle it."); return true; } long num = val2.GetLong("ownerID", 0L); long playerID = val.GetPlayerID(); LogDebug($"HandleInteract: tombstone {val2.m_uid}, owner={num}, interactingPlayer={playerID}."); if (num == 0L || playerID == num) { LogDebug("HandleInteract: allowed (tombstone unlocked or interacting player is the owner)."); return true; } if (IsLockExpiredByAge(val2)) { LogInfo($"HandleInteract: tombstone {val2.m_uid} auto-unlocked (locked for >= {10} in-game days)."); return true; } if ((DateTime.Now - lastSentRequestTime).TotalSeconds < 120.0) { int num2 = 2; LogDebug($"HandleInteract: blocked, unlock request on cooldown ({num2} min not elapsed since last request)."); string message = string.Format(Localization.instance.Localize("<b>$msg_cantopen\n\n$tombstonelock_cannot_send_request_now</b>"), num2); OnScreenNotifier.Message(val, (MessageType)2, message); return false; } if (IsPlayerOnline(num)) { lastSentRequestTime = DateTime.Now; OnScreenNotifier.Message(val, (MessageType)2, "<b>$msg_cantopen\n\n$tombstonelock_request_sent</b>"); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "RPC_RequestTombstoneUnlock", new object[3] { playerID, val.GetPlayerName(), val2.m_uid }); LogInfo($"HandleInteract: owner {num} online, broadcast unlock request for tombstone {val2.m_uid}."); } else { OnScreenNotifier.Message(val, (MessageType)2, "<b>$msg_cantopen\n\n$tombstonelock_owner_offline</b>"); LogInfo($"HandleInteract: owner {num} appears offline, no request sent for tombstone {val2.m_uid}."); } return false; } internal static void AcceptPendingRequest() { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { LogDebug("AcceptPendingRequest: no local player, ignoring /accept."); return; } PurgeExpiredRequest(); PendingRequest pendingRequest = MainManager.pendingRequest; if (pendingRequest == null) { LogInfo("AcceptPendingRequest: /accept used but there is no pending request (none received or it expired)."); ChatNotifier.ShowInChat("$tombstonelock_no_pending_request"); return; } MainManager.pendingRequest = null; if (ZRoutedRpc.instance == null) { LogWarning("AcceptPendingRequest: ZRoutedRpc.instance is null, cannot broadcast the unlock confirmation."); return; } ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "RPC_ConfirmTombstoneUnlock", new object[3] { pendingRequest.RequesterPlayerID, pendingRequest.Tombstone, localPlayer.GetPlayerName() }); LogInfo($"AcceptPendingRequest: accepted request from {pendingRequest.RequesterName} ({pendingRequest.RequesterPlayerID}), broadcast unlock for tombstone {pendingRequest.Tombstone}."); ChatNotifier.ShowInChat("$tombstonelock_request_accepted"); } internal static void PurgeExpiredRequest() { if (pendingRequest != null && (DateTime.Now - pendingRequest.Time).TotalSeconds > 30.0) { LogDebug($"PurgeExpiredRequest: dropped expired request from {pendingRequest.RequesterPlayerID}."); pendingRequest = null; } } internal static void Tick() { if (pendingRequest != null && !((DateTime.Now - pendingRequest.Time).TotalSeconds <= 30.0)) { long requesterPlayerID = pendingRequest.RequesterPlayerID; pendingRequest = null; LogInfo($"Tick: pending request from {requesterPlayerID} expired after {30}s; notifying both players."); if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "RPC_TombstoneRequestExpired", new object[1] { requesterPlayerID }); } ChatNotifier.ShowInChat("$tombstonelock_request_expired", openChat: false); } } private static void RPC_RequestTombstoneUnlock(long sender, long requesterPlayerID, string requesterName, ZDOID tombstoneUid) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } ZDOMan instance = ZDOMan.instance; ZDO val = ((instance != null) ? instance.GetZDO(tombstoneUid) : null); if (val != null && localPlayer.GetPlayerID() == val.GetLong("ownerID", 0L)) { PurgeExpiredRequest(); if (pendingRequest == null) { string text = (string.IsNullOrEmpty(requesterName) ? "A friend" : requesterName); pendingRequest = new PendingRequest { RequesterPlayerID = requesterPlayerID, RequesterName = text, Tombstone = tombstoneUid, Time = DateTime.Now }; ChatNotifier.ShowInChat(string.Format(Localization.instance.Localize("$tombstonelock_owner_notification"), text)); } } } private static void RPC_ConfirmTombstoneUnlock(long sender, long requesterPlayerID, ZDOID tombstoneUid, string ownerName) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || localPlayer.GetPlayerID() != requesterPlayerID) { return; } GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.FindInstance(tombstoneUid) : null); if (!((Object)(object)val == (Object)null)) { ZNetView component = val.gameObject.GetComponent<ZNetView>(); if (!((Object)(object)component == (Object)null) && component.IsValid()) { component.ClaimOwnership(); component.GetZDO().Set("ownerID", 0L); OnScreenNotifier.Message(localPlayer, (MessageType)2, string.Format(Localization.instance.Localize("$tombstonelock_friend_confirmation"), ownerName)); } } } private static void RPC_TombstoneRequestExpired(long sender, long requesterPlayerID) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && localPlayer.GetPlayerID() == requesterPlayerID) { ChatNotifier.ShowInChat("$tombstonelock_request_expired_requester", openChat: false); } } private static bool IsPlayerOnline(long playerID) { if ((Object)(object)Player.m_localPlayer != (Object)null && Player.m_localPlayer.GetPlayerID() == playerID) { return true; } foreach (Player allPlayer in Player.GetAllPlayers()) { if (!((Object)(object)allPlayer == (Object)null) && allPlayer.GetPlayerID() == playerID) { return true; } } return false; } }