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 CharacterVault v1.0.2
IronLabs.CharacterVault.dll
Decompiled a day agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Logging; using HarmonyLib; using IronLabs.SharedLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("IronLabs.CharacterVault")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("IronLabs.CharacterVault")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("70BCF48B-1C23-4B2A-8A4F-F01CC21E3EC5")] [assembly: AssemblyFileVersion("1.0.2")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.2.22328")] namespace IronLabs.CharacterVault { internal sealed class GracefulShutdownCoordinator : IDisposable { internal const string SaveRequestRpc = "CharacterVault SaveRequest"; internal const string SaveStartedRpc = "CharacterVault SaveStarted"; private const string ExitFilePath = "character_vault.drp"; private const int MaximumConcurrentSaves = 4; private const int ShutdownTimeoutSeconds = 90; private readonly HashSet<ZNetPeer> _pendingPeers = new HashSet<ZNetPeer>(); private readonly HashSet<ZNetPeer> _requestedPeers = new HashSet<ZNetPeer>(); private readonly Queue<ZNetPeer> _queuedPeers = new Queue<ZNetPeer>(); private readonly Dictionary<ZNetPeer, string> _startedRequests = new Dictionary<ZNetPeer, string>(); private readonly FileSystemWatcher _exitFileWatcher; private readonly SynchronizationContext _unityContext; private Timer _timeoutTimer; private int _disposed; private int _exitRequestQueued; private volatile bool _watcherFailed; private bool _exitRequestPending; private bool _shutdownCommitted; private string _requestId; internal GracefulShutdownCoordinator(SynchronizationContext unityContext) { _unityContext = unityContext ?? throw new ArgumentNullException("unityContext"); try { _exitFileWatcher = CreateExitFileWatcher(); if (File.Exists("character_vault.drp")) { QueueExitRequest(); } } catch (Exception ex) { CharacterVaultPlugin.Log.LogError("Could not watch character_vault.drp; shutdown requests cannot be detected: " + ex.Message); } } private void TryStartFromExitFile() { if (!CanCoordinateShutdown()) { _exitRequestPending = true; return; } _exitRequestPending = false; try { if (File.Exists("character_vault.drp")) { string text = File.ReadAllText("character_vault.drp").Trim(); if (!int.TryParse(text, out var result) || result != Process.GetCurrentProcess().Id) { File.Delete("character_vault.drp"); CharacterVaultPlugin.Log.LogWarning("Ignored a stale or invalid character_vault.drp request for process '" + text + "'."); } else { File.Delete("character_vault.drp"); Start(ZNet.instance); } } } catch (Exception ex) { CharacterVaultPlugin.Log.LogError("Could not process character_vault.drp: " + ex.Message); } } internal void RecordSavedProfile(PlayerProfile profile) { if (_requestId == null) { return; } ZNetPeer val = FindSavedPeer(profile); if (val != null && _pendingPeers.Remove(val)) { _startedRequests.Remove(val); _requestedPeers.Remove(val); CharacterVaultPlugin.Log.LogMessage($"Confirmed graceful character save for {val.m_playerName} ({_pendingPeers.Count} remaining)."); ZNet.instance.Disconnect(val); RequestNextProfiles(); if (_pendingPeers.Count == 0) { Complete(); } } } internal void RecordSaveStarted(ZRpc peerRpc, string startedRequestId) { ZNet instance = ZNet.instance; ZNetPeer val = ((instance != null) ? ((IEnumerable<ZNetPeer>)instance.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer candidate) => candidate.m_rpc == peerRpc)) : null); if (val != null && _pendingPeers.Contains(val) && startedRequestId == _requestId) { _startedRequests[val] = startedRequestId; } } private static bool CanCoordinateShutdown() { if (Application.isBatchMode) { ZNet instance = ZNet.instance; if (instance == null) { return false; } return instance.IsServer(); } return false; } private void Start(ZNet network) { _requestId = Guid.NewGuid().ToString("N"); _timeoutTimer = new Timer(OnTimeoutElapsed, _requestId, TimeSpan.FromSeconds(90.0), Timeout.InfiniteTimeSpan); foreach (ZNetPeer item in network.GetPeers().Where(HasActiveCharacter)) { _pendingPeers.Add(item); _queuedPeers.Enqueue(item); } CharacterVaultPlugin.Log.LogMessage($"Shutdown requested; saving {_pendingPeers.Count} connected character(s)."); RequestNextProfiles(); CompleteIfFinished(); } internal void ProcessPendingExitRequest() { if (_exitRequestPending) { ProcessExitRequest(); } } private void RequestNextProfiles() { while (_requestedPeers.Count < 4 && _queuedPeers.Count > 0) { ZNetPeer val = _queuedPeers.Dequeue(); if (!_pendingPeers.Contains(val) || !HasActiveCharacter(val)) { _pendingPeers.Remove(val); continue; } _requestedPeers.Add(val); val.m_rpc.Invoke("CharacterVault SaveRequest", new object[1] { _requestId }); } } public void Dispose() { Interlocked.Exchange(ref _disposed, 1); _exitFileWatcher?.Dispose(); _timeoutTimer?.Dispose(); } private void ProcessExitRequest() { Interlocked.Exchange(ref _exitRequestQueued, 0); if (Volatile.Read(in _disposed) == 0 && !_shutdownCommitted) { if (_watcherFailed) { _watcherFailed = false; CharacterVaultPlugin.Log.LogError("The character_vault.drp watcher failed; shutdown requests may no longer be detected."); } if (_requestId == null) { TryStartFromExitFile(); } } } private FileSystemWatcher CreateExitFileWatcher() { FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(Path.GetFullPath("."), "character_vault.drp"); fileSystemWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite; fileSystemWatcher.Created += OnExitFileChanged; fileSystemWatcher.Changed += OnExitFileChanged; fileSystemWatcher.Renamed += OnExitFileRenamed; fileSystemWatcher.Error += OnWatcherError; fileSystemWatcher.EnableRaisingEvents = true; return fileSystemWatcher; } private void OnExitFileChanged(object sender, FileSystemEventArgs args) { QueueExitRequest(); } private void OnExitFileRenamed(object sender, RenamedEventArgs args) { QueueExitRequest(); } private void OnWatcherError(object sender, ErrorEventArgs args) { _watcherFailed = true; QueueExitRequest(); } private void QueueExitRequest() { if (Volatile.Read(in _disposed) == 0 && Interlocked.Exchange(ref _exitRequestQueued, 1) == 0) { _unityContext.Post(delegate { ProcessExitRequest(); }, null); } } private void OnTimeoutElapsed(object state) { string timedOutRequestId = (string)state; if (Volatile.Read(in _disposed) == 0) { _unityContext.Post(delegate { CompleteAfterTimeoutIfCurrent(timedOutRequestId); }, null); } } private void CompleteAfterTimeoutIfCurrent(string timedOutRequestId) { if (!_shutdownCommitted && timedOutRequestId == _requestId) { CompleteAfterTimeout(); } } private void CompleteIfFinished() { if (_pendingPeers.Count == 0 && !_shutdownCommitted) { Complete(); } } private ZNetPeer FindSavedPeer(PlayerProfile profile) { string value; return ((IEnumerable<ZNetPeer>)_pendingPeers).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer candidate) => _startedRequests.TryGetValue(candidate, out value) && value == _requestId && ServerCharactersIdentity.Matches(profile, candidate))); } private void Complete() { CharacterVaultPlugin.Log.LogMessage("All connected character profiles were written to disk; continuing the vanilla shutdown."); ContinueVanillaShutdown(); } private void CompleteAfterTimeout() { string arg = string.Join(", ", _pendingPeers.Select((ZNetPeer peer) => peer.m_playerName).ToArray()); CharacterVaultPlugin.Log.LogWarning($"The {90}-second shutdown save timeout expired with " + $"{_pendingPeers.Count} unsaved character(s): {arg}."); foreach (ZNetPeer pendingPeer in _pendingPeers) { ZNet.instance.Disconnect(pendingPeer); } CharacterVaultPlugin.Log.LogWarning("Continuing the vanilla server shutdown after the character save timeout."); ContinueVanillaShutdown(); } private void ContinueVanillaShutdown() { _timeoutTimer?.Dispose(); _timeoutTimer = null; _pendingPeers.Clear(); _requestedPeers.Clear(); _queuedPeers.Clear(); _startedRequests.Clear(); _requestId = null; _shutdownCommitted = true; CharacterVaultPlugin.Log.LogMessage("Starting the vanilla application shutdown."); CharacterVaultPlugin.Instance.QuitNextFrame(); } private static bool HasActiveCharacter(ZNetPeer peer) { if (peer?.m_rpc != null) { ISocket socket = peer.m_socket; if (socket != null && socket.IsConnected()) { return !string.IsNullOrWhiteSpace(peer.m_playerName); } } return false; } } [HarmonyPatch(typeof(ZNet), "Start")] internal static class PendingExitRequestPatch { private static void Postfix() { CharacterVaultPlugin.Coordinator?.ProcessPendingExitRequest(); } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] internal static class ProfileSaveRequestRpc { private static void Postfix(ZNet __instance, ZNetPeer peer) { if (!__instance.IsServer()) { peer.m_rpc.Register<string>("CharacterVault SaveRequest", (Action<ZRpc, string>)SaveProfile); } else { peer.m_rpc.Register<string>("CharacterVault SaveStarted", (Action<ZRpc, string>)CharacterVaultPlugin.Coordinator.RecordSaveStarted); } } private static void SaveProfile(ZRpc serverRpc, string requestId) { Game instance = Game.instance; if (((instance != null) ? instance.GetPlayerProfile() : null) != null) { CharacterVaultPlugin.Instance.Run(SaveWhenReady(serverRpc, requestId)); } } private static IEnumerator SaveWhenReady(ZRpc serverRpc, string requestId) { ServerCharactersTransferTracker.BeginShutdownRequest(); float deadline = Time.realtimeSinceStartup + 10f; while (ServerCharactersTransferTracker.IsSendingProfile) { if (Time.realtimeSinceStartup >= deadline) { CharacterVaultPlugin.Log.LogWarning("The active ServerCharacters transfer did not finish within 10 seconds; saving anyway."); ServerCharactersTransferTracker.Reset(); break; } yield return null; } CharacterVaultPlugin.Log.LogMessage("Saving the character for graceful shutdown request " + requestId + "."); serverRpc.Invoke("CharacterVault SaveStarted", new object[1] { requestId }); Game.instance.SavePlayerProfile(true); } } [HarmonyPatch(typeof(ZRpc), "Invoke")] internal static class ServerCharactersTransferTracker { private const string ProfileRpc = "ServerCharacters PlayerProfile"; internal static bool IsSendingProfile { get; private set; } internal static void BeginShutdownRequest() { if (IsSendingProfile) { CharacterVaultPlugin.Log.LogInfo("Waiting for the active ServerCharacters transfer before the shutdown save."); } } internal static void Reset() { IsSendingProfile = false; } private static void Prefix(string method, object[] parameters) { if (method != "ServerCharacters PlayerProfile" || parameters == null || parameters.Length != 1) { return; } object obj = parameters[0]; ZPackage val = (ZPackage)((obj is ZPackage) ? obj : null); if (val == null) { return; } int pos = val.GetPos(); try { if (val.Size() >= 16) { val.SetPos(0); val.ReadLong(); int num = val.ReadInt(); int num2 = val.ReadInt(); if (num2 > 0 && num >= 0 && num < num2) { IsSendingProfile = num < num2 - 1; } } } finally { val.SetPos(pos); } } } [HarmonyPatch(typeof(PlayerProfile), "SavePlayerToDisk")] internal static class SavedProfilePatch { private static void Postfix(PlayerProfile __instance, bool __result) { if (__result) { ZNet instance = ZNet.instance; if (instance != null && instance.IsServer()) { CharacterVaultPlugin.Coordinator.RecordSavedProfile(__instance); } } } } internal static class ServerCharactersIdentity { internal static bool Matches(PlayerProfile profile, ZNetPeer peer) { string hostName = peer.m_socket.GetHostName(); string b = (Regex.IsMatch(hostName, "^\\d+$") ? ("Steam_" + hostName) : hostName) + "_" + peer.m_playerName.ToLower(); return string.Equals(profile.m_filename, b, StringComparison.Ordinal); } } [BepInPlugin("IronLabs.CharacterVault", "IronLabs.CharacterVault", "1.0.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class CharacterVaultPlugin : IronLabsPlugin { private const string PluginGuid = "IronLabs.CharacterVault"; private const string PluginName = "IronLabs.CharacterVault"; private const string PluginVersion = "1.0.2"; internal static ModLog Log { get; private set; } internal static GracefulShutdownCoordinator Coordinator { get; private set; } internal static CharacterVaultPlugin Instance { get; private set; } private void Awake() { Instance = this; Log = InitializePlugin("IronLabs.CharacterVault"); Coordinator = new GracefulShutdownCoordinator(SynchronizationContext.Current); Log.LogInfo("IronLabs.CharacterVault 1.0.2 is loaded."); } internal void Run(IEnumerator routine) { ((MonoBehaviour)this).StartCoroutine(routine); } internal void QuitNextFrame() { ((MonoBehaviour)this).StartCoroutine(QuitAfterCurrentFrame()); } private static IEnumerator QuitAfterCurrentFrame() { yield return null; Application.Quit(); } private void OnDestroy() { Coordinator?.Dispose(); Coordinator = null; Instance = null; ShutdownPlugin(); Log = null; } } } namespace IronLabs.SharedLib { public abstract class IronLabsPlugin : BaseUnityPlugin { private Harmony _harmony; private bool _patchesApplied; protected ModLog InitializePlugin(string pluginGuid) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown ModLog modLog = new ModLog(((BaseUnityPlugin)this).Logger); Version version = ((object)this).GetType().Assembly.GetName().Version; modLog.LogInfo($"AssemblyVersion: {version}."); _harmony = new Harmony(pluginGuid); PatchOwnNamespace(modLog); return modLog; } protected void PatchOwnNamespace(ModLog log) { if (_patchesApplied) { log.LogDebug("Harmony patches are already active; skipping registration."); return; } string text = ((object)this).GetType().Namespace; Type[] types = Assembly.GetExecutingAssembly().GetTypes(); foreach (Type type in types) { if (type.Namespace == text) { _harmony.CreateClassProcessor(type).Patch(); } } _patchesApplied = true; log.LogDebug("Harmony patches were applied for the plugin namespace."); } protected void ShutdownPlugin() { if (_patchesApplied) { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _patchesApplied = false; } } } public sealed class ModLog { private readonly ManualLogSource _logger; public ModLog(ManualLogSource logger) { _logger = logger; } public void LogFatal(object message) { Write((LogLevel)1, message); } public void LogError(object message) { Write((LogLevel)2, message); } public void LogWarning(object message) { Write((LogLevel)4, message); } public void LogMessage(object message) { Write((LogLevel)8, message); } public void LogInfo(object message) { Write((LogLevel)16, message); } public void LogDebug(object message) { Write((LogLevel)32, message); } public void Log(LogLevel level, object message) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) Write(level, message); } private void Write(LogLevel level, object message) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) string arg = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); _logger.Log(level, (object)$"[{arg}] {message}"); } } }