Decompiled source of SenpaisChest v3.1.1
SenpaisChest.dll
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
using 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.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using I2.Loc; using Microsoft.CodeAnalysis; using PSS; using SenpaisChest.ChestLabels; using SenpaisChest.ChestLabels.Extensions; using SenpaisChest.Config; using SenpaisChest.Data; using SenpaisChest.Integration; using SenpaisChest.UI; using SunHavenMuseumUtilityTracker; using SunHavenMuseumUtilityTracker.Data; using SunhavenMods.Shared; using SunhavenTodo; using SunhavenTodo.Data; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Networking; using UnityEngine.SceneManagement; using UnityEngine.UI; using Wish; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("SenpaisChest")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+79057c8571b97b27a9323b455b8f88a51829b688")] [assembly: AssemblyProduct("SenpaisChest")] [assembly: AssemblyTitle("SenpaisChest")] [assembly: InternalsVisibleTo("HavenDevTools")] [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.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 SunhavenMods.Shared { public static class ConfigFileHelper { public static ConfigFile CreateNamedConfig(string pluginGuid, string configFileName, Action<string> logWarning = null) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown string text = Path.Combine(Paths.ConfigPath, configFileName); string text2 = Path.Combine(Paths.ConfigPath, pluginGuid + ".cfg"); try { if (!File.Exists(text) && File.Exists(text2)) { File.Copy(text2, text); } } catch (Exception ex) { logWarning?.Invoke("[Config] Migration to " + configFileName + " failed: " + ex.Message); } return new ConfigFile(text, true); } public static bool ReplacePluginConfig(BaseUnityPlugin plugin, ConfigFile newConfig, Action<string> logWarning = null) { if ((Object)(object)plugin == (Object)null || newConfig == null) { return false; } try { Type typeFromHandle = typeof(BaseUnityPlugin); PropertyInfo property = typeFromHandle.GetProperty("Config", BindingFlags.Instance | BindingFlags.Public); if (property != null && property.CanWrite) { property.SetValue(plugin, newConfig, null); return true; } FieldInfo field = typeFromHandle.GetField("<Config>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); if (field != null) { field.SetValue(plugin, newConfig); return true; } FieldInfo[] fields = typeFromHandle.GetFields(BindingFlags.Instance | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(ConfigFile)) { fieldInfo.SetValue(plugin, newConfig); return true; } } } catch (Exception ex) { logWarning?.Invoke("[Config] ReplacePluginConfig failed: " + ex.Message); } return false; } } public static class SharedCodeRevision { public const string Value = "2026.07.28"; } public sealed class ModHealthReport { public string PluginGuid { get; set; } public string DisplayName { get; set; } public string PluginVersion { get; set; } public string SharedCodeRevision { get; set; } public string IntegrationSummary { get; set; } public string Mode { get; set; } public string CharacterName { get; set; } public bool? DataLoaded { get; set; } public string LastPersistenceOutcome { get; set; } public string LastError { get; set; } public DateTime ReportedUtc { get; set; } public ModHealthReport Clone() { return new ModHealthReport { PluginGuid = PluginGuid, DisplayName = DisplayName, PluginVersion = PluginVersion, SharedCodeRevision = SharedCodeRevision, IntegrationSummary = IntegrationSummary, Mode = Mode, CharacterName = CharacterName, DataLoaded = DataLoaded, LastPersistenceOutcome = LastPersistenceOutcome, LastError = LastError, ReportedUtc = ReportedUtc }; } public void AppendDiagnosticDump(StringBuilder sb) { if (sb != null) { sb.AppendLine("guid: " + (PluginGuid ?? "—")); sb.AppendLine("name: " + (DisplayName ?? "—")); sb.AppendLine("version: " + (PluginVersion ?? "—")); sb.AppendLine("shared: " + (SharedCodeRevision ?? "—")); sb.AppendLine("integrations: " + (IntegrationSummary ?? "—")); sb.AppendLine("mode: " + (Mode ?? "—")); sb.AppendLine("character: " + (string.IsNullOrEmpty(CharacterName) ? "—" : CharacterName)); sb.AppendLine("data loaded: " + (DataLoaded.HasValue ? DataLoaded.Value.ToString() : "—")); sb.AppendLine("last save: " + (LastPersistenceOutcome ?? "—")); sb.AppendLine("last error: " + (LastError ?? "—")); sb.AppendLine("reported: " + ((ReportedUtc == default(DateTime)) ? "—" : ReportedUtc.ToLocalTime().ToString("u"))); } } } public static class ModDiagnostics { private static readonly Dictionary<string, ModHealthReport> ReportsByPluginGuid = new Dictionary<string, ModHealthReport>(StringComparer.OrdinalIgnoreCase); private static readonly object Lock = new object(); public static void ReportStartup(ModHealthReport report) { if (report == null || string.IsNullOrWhiteSpace(report.PluginGuid)) { return; } report.ReportedUtc = DateTime.UtcNow; if (string.IsNullOrEmpty(report.SharedCodeRevision)) { report.SharedCodeRevision = "2026.07.28"; } lock (Lock) { ReportsByPluginGuid[report.PluginGuid] = report.Clone(); } } public static void ReportRuntime(string pluginGuid, Action<ModHealthReport> update) { if (string.IsNullOrWhiteSpace(pluginGuid) || update == null) { return; } lock (Lock) { if (!ReportsByPluginGuid.TryGetValue(pluginGuid, out ModHealthReport value)) { value = new ModHealthReport { PluginGuid = pluginGuid }; ReportsByPluginGuid[pluginGuid] = value; } update(value); value.ReportedUtc = DateTime.UtcNow; } } public static ModHealthReport GetReport(string pluginGuid) { if (string.IsNullOrWhiteSpace(pluginGuid)) { return null; } lock (Lock) { ModHealthReport value; return ReportsByPluginGuid.TryGetValue(pluginGuid, out value) ? value.Clone() : null; } } public static IReadOnlyList<ModHealthReport> GetAllReports() { lock (Lock) { return ReportsByPluginGuid.Values.Select((ModHealthReport r) => r.Clone()).ToList(); } } public static string FormatHealthLogLine(ModHealthReport report) { if (report == null) { return "[Health] (empty report)"; } string text = (string.IsNullOrEmpty(report.DisplayName) ? report.PluginGuid : report.DisplayName); string text2 = (string.IsNullOrEmpty(report.PluginVersion) ? "?" : report.PluginVersion); string text3 = (string.IsNullOrEmpty(report.SharedCodeRevision) ? "?" : report.SharedCodeRevision); string text4 = (string.IsNullOrEmpty(report.IntegrationSummary) ? "—" : report.IntegrationSummary); string text5 = (string.IsNullOrEmpty(report.Mode) ? "—" : report.Mode); string text6 = (string.IsNullOrEmpty(report.CharacterName) ? "—" : report.CharacterName); string text7 = ((!report.DataLoaded.HasValue) ? "—" : (report.DataLoaded.Value ? "loaded" : "none")); string text8 = (string.IsNullOrEmpty(report.LastPersistenceOutcome) ? "—" : report.LastPersistenceOutcome); return "[Health] " + text + " v" + text2 + " | shared " + text3 + " | integrations: " + text4 + " | mode: " + text5 + " | character: " + text6 + " | data: " + text7 + " | save: " + text8; } public static void LogStartupHealth(ManualLogSource log, ModHealthReport report) { ReportStartup(report); if (log != null) { log.LogInfo((object)FormatHealthLogLine(report)); } } public static void LogModStartup(ManualLogSource log, string pluginGuid, string displayName, string pluginVersion, string integrationSummary, string mode = "startup", bool? dataLoaded = false, string lastPersistenceOutcome = "—") { LogStartupHealth(log, new ModHealthReport { PluginGuid = pluginGuid, DisplayName = displayName, PluginVersion = pluginVersion, SharedCodeRevision = "2026.07.28", IntegrationSummary = integrationSummary, Mode = mode, DataLoaded = dataLoaded, LastPersistenceOutcome = lastPersistenceOutcome }); } } public static class SuitePluginGuids { public const string DevTools = "com.azraelgodking.havendevtools"; public const string SenpaisChest = "com.azraelgodking.senpaischest"; public const string BirthdayReminder = "com.azraelgodking.squirrelsbirthdayreminder"; public const string HavensBirthright = "com.azraelgodking.havensbirthright"; public const string Smut = "com.azraelgodking.sunhavenmuseumutilitytracker"; public const string SunhavenTodo = "com.azraelgodking.sunhaventodo"; public const string TheVault = "com.azraelgodking.thevault"; public const string HavensAlmanac = "com.azraelgodking.havensalmanac"; public const string FasterRaces = "com.azraelgodking.fasterraces"; public const string TrinketFortune = "com.azraelgodking.trinketfortune"; public const string CropOptimizer = "com.azraelgodking.cropoptimizer"; public const string HavensRespec = "com.azraelgodking.havensrespec"; public const string GiftingAssistant = "com.azraelgodking.giftingassistant"; } public static class ModHealthIntegrationSummary { public static string Build(params (string label, string pluginGuid)[] integrations) { if (integrations == null || integrations.Length == 0) { return "standalone"; } Dictionary<string, PluginInfo> pluginInfos = Chainloader.PluginInfos; if (pluginInfos == null) { return "standalone"; } List<string> list = new List<string>(integrations.Length); for (int i = 0; i < integrations.Length; i++) { var (text, text2) = integrations[i]; if (!string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(text2) && pluginInfos.ContainsKey(text2)) { list.Add(text); } } if (list.Count != 0) { return string.Join(", ", list); } return "standalone"; } } public enum CharacterSaveSource { None, Primary, Backup } public enum CharacterSaveAbsenceReason { NoFiles, FilesPresentButUnusable } public static class CharacterSaveStore { public const string BackupSuffix = ".bak"; public const string VaultBackupSuffix = ".backup"; public const string TempSuffix = ".tmp"; public static string SanitizeFileName(string name, string fallback = "unknown") { if (string.IsNullOrWhiteSpace(name)) { return fallback; } char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { name = name.Replace(oldChar, '_'); } name = name.Trim(); if (!string.IsNullOrEmpty(name)) { return name; } return fallback; } public static string GetFilePath(string directory, string characterName, string fileSuffix, string fallback = "unknown") { EnsureDirectory(directory); return Path.Combine(directory, SanitizeFileName(characterName, fallback) + fileSuffix); } public static void EnsureDirectory(string directory) { if (string.IsNullOrEmpty(directory)) { throw new ArgumentException("Directory is required.", "directory"); } if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } } public static CharacterSaveAbsenceReason GetAbsenceReason(string filePath, string backupSuffix = ".bak") { if (string.IsNullOrEmpty(filePath)) { return CharacterSaveAbsenceReason.NoFiles; } bool num = File.Exists(filePath); bool flag = File.Exists(filePath + backupSuffix); if (!(num || flag)) { return CharacterSaveAbsenceReason.NoFiles; } return CharacterSaveAbsenceReason.FilesPresentButUnusable; } public static bool WriteAtomic(string filePath, string content, string backupSuffix = ".bak", bool deleteTempInFinally = true) { if (content == null) { throw new ArgumentNullException("content"); } return WriteAtomicCore(filePath, delegate(string tempPath) { File.WriteAllText(tempPath, content); }, backupSuffix, deleteTempInFinally); } public static bool WriteAtomicBytes(string filePath, byte[] content, string backupSuffix = ".bak", bool deleteTempInFinally = true) { if (content == null) { throw new ArgumentNullException("content"); } return WriteAtomicCore(filePath, delegate(string tempPath) { File.WriteAllBytes(tempPath, content); }, backupSuffix, deleteTempInFinally); } public static T LoadWithBackup<T>(string filePath, Func<string, T> tryDeserialize, out CharacterSaveSource source, string backupSuffix = ".bak", Action<string, Exception> onReadFailure = null) where T : class { source = CharacterSaveSource.None; if (string.IsNullOrEmpty(filePath) || tryDeserialize == null) { return null; } if (File.Exists(filePath)) { string text = TryReadAllText(filePath, onReadFailure); if (text != null) { try { T val = tryDeserialize(text); if (val != null) { source = CharacterSaveSource.Primary; return val; } } catch (Exception arg) { onReadFailure?.Invoke(filePath, arg); } } } string text2 = filePath + backupSuffix; if (File.Exists(text2)) { string text3 = TryReadAllText(text2, onReadFailure); if (text3 != null) { try { T val2 = tryDeserialize(text3); if (val2 != null) { source = CharacterSaveSource.Backup; return val2; } } catch (Exception arg2) { onReadFailure?.Invoke(text2, arg2); } } } return null; } public static string LoadTextWithBackup(string filePath, out CharacterSaveSource source, string backupSuffix = ".bak", Action<string, Exception> onReadFailure = null) { source = CharacterSaveSource.None; if (string.IsNullOrEmpty(filePath)) { return null; } if (File.Exists(filePath)) { string text = TryReadAllText(filePath, onReadFailure); if (text != null) { source = CharacterSaveSource.Primary; return text; } } string path = filePath + backupSuffix; if (File.Exists(path)) { string text2 = TryReadAllText(path, onReadFailure); if (text2 != null) { source = CharacterSaveSource.Backup; return text2; } } return null; } public static bool LooksLikeJsonObject(string text) { if (!string.IsNullOrWhiteSpace(text)) { return text.TrimStart(Array.Empty<char>()).StartsWith("{", StringComparison.Ordinal); } return false; } private static bool WriteAtomicCore(string filePath, Action<string> writeTemp, string backupSuffix, bool deleteTempInFinally) { if (string.IsNullOrEmpty(filePath) || writeTemp == null) { return false; } string text = filePath + ".tmp"; try { writeTemp(text); if (File.Exists(filePath)) { string text2 = filePath + backupSuffix; if (File.Exists(text2)) { File.Delete(text2); } File.Move(filePath, text2); } File.Move(text, filePath); return true; } finally { if (deleteTempInFinally) { TryDelete(text); } } } private static string TryReadAllText(string path, Action<string, Exception> onReadFailure) { try { if (!File.Exists(path)) { return null; } return File.ReadAllText(path); } catch (Exception arg) { onReadFailure?.Invoke(path, arg); return null; } } private static void TryDelete(string path) { try { if (File.Exists(path)) { File.Delete(path); } } catch { } } } public static class VersionChecker { public class VersionCheckResult { public bool Success { get; set; } public bool UpdateAvailable { get; set; } public string CurrentVersion { get; set; } public string LatestVersion { get; set; } public string ModName { get; set; } public string NexusUrl { get; set; } public string Changelog { get; set; } public string ErrorMessage { get; set; } } public class ModHealthSnapshot { public string PluginGuid { get; set; } public DateTime LastCheckUtc { get; set; } public int ExceptionCount { get; set; } public string LastError { get; set; } } private class VersionCheckRunner : MonoBehaviour { private ManualLogSource _pluginLog; public void StartCheck(string pluginGuid, string currentVersion, ManualLogSource pluginLog, Action<VersionCheckResult> onComplete) { _pluginLog = pluginLog; ((MonoBehaviour)this).StartCoroutine(CheckVersionCoroutine(pluginGuid, currentVersion, onComplete)); } private void LogInfo(string message) { ManualLogSource pluginLog = _pluginLog; if (pluginLog != null) { pluginLog.LogInfo((object)("[VersionChecker] " + message)); } } private void LogWarningMsg(string message) { ManualLogSource pluginLog = _pluginLog; if (pluginLog != null) { pluginLog.LogWarning((object)("[VersionChecker] " + message)); } } private void LogErrorMsg(string message) { ManualLogSource pluginLog = _pluginLog; if (pluginLog != null) { pluginLog.LogError((object)("[VersionChecker] " + message)); } } private IEnumerator CheckVersionCoroutine(string pluginGuid, string currentVersion, Action<VersionCheckResult> onComplete) { VersionCheckResult result = new VersionCheckResult { CurrentVersion = currentVersion }; UnityWebRequest www = UnityWebRequest.Get("https://azraelgodking.github.io/SunhavenMod/versions.json"); try { www.timeout = 10; yield return www.SendWebRequest(); if ((int)www.result == 2 || (int)www.result == 3) { result.Success = false; result.ErrorMessage = "Network error: " + www.error; RecordHealthError(pluginGuid, result.ErrorMessage); LogWarningMsg(result.ErrorMessage); onComplete?.Invoke(result); Object.Destroy((Object)(object)((Component)this).gameObject); yield break; } try { string text = www.downloadHandler.text; Match match = GetModPattern(pluginGuid).Match(text); if (!match.Success) { result.Success = false; result.ErrorMessage = "Mod '" + pluginGuid + "' not found in versions.json"; RecordHealthError(pluginGuid, result.ErrorMessage); LogWarningMsg(result.ErrorMessage); onComplete?.Invoke(result); Object.Destroy((Object)(object)((Component)this).gameObject); yield break; } string value = match.Groups[1].Value; result.LatestVersion = ExtractJsonString(value, "version"); result.ModName = ExtractJsonString(value, "name"); result.NexusUrl = ExtractJsonString(value, "nexus"); result.Changelog = ExtractJsonString(value, "changelog"); if (string.IsNullOrEmpty(result.LatestVersion)) { result.Success = false; result.ErrorMessage = "Could not parse version from response"; RecordHealthError(pluginGuid, result.ErrorMessage); LogWarningMsg(result.ErrorMessage); onComplete?.Invoke(result); Object.Destroy((Object)(object)((Component)this).gameObject); yield break; } result.Success = true; result.UpdateAvailable = CompareVersions(currentVersion, result.LatestVersion) < 0; if (result.UpdateAvailable) { LogInfo("Update available for " + result.ModName + ": " + currentVersion + " -> " + result.LatestVersion); } else { LogInfo(result.ModName + " is up to date (v" + currentVersion + ")"); } } catch (Exception ex) { result.Success = false; result.ErrorMessage = "Parse error: " + ex.Message; RecordHealthError(pluginGuid, result.ErrorMessage); LogErrorMsg(result.ErrorMessage); } } finally { ((IDisposable)www)?.Dispose(); } onComplete?.Invoke(result); Object.Destroy((Object)(object)((Component)this).gameObject); } private string ExtractJsonString(string json, string key) { Match match = ExtractFieldRegex.Match(json); while (match.Success) { if (string.Equals(match.Groups["key"].Value, key, StringComparison.Ordinal)) { return match.Groups["value"].Value; } match = match.NextMatch(); } return null; } } private const string VersionsUrl = "https://azraelgodking.github.io/SunhavenMod/versions.json"; private static readonly Dictionary<string, ModHealthSnapshot> HealthByPluginGuid = new Dictionary<string, ModHealthSnapshot>(StringComparer.OrdinalIgnoreCase); private static readonly object HealthLock = new object(); private static readonly Dictionary<string, Regex> ModPatternCache = new Dictionary<string, Regex>(StringComparer.Ordinal); private static readonly object ModPatternCacheLock = new object(); private static readonly Regex ExtractFieldRegex = new Regex("\"(?<key>[^\"]+)\"\\s*:\\s*(?:\"(?<value>[^\"]*)\"|null)", RegexOptions.Compiled); public static void CheckForUpdate(string pluginGuid, string currentVersion, ManualLogSource logger = null, Action<VersionCheckResult> onComplete = null) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) TouchHealth(pluginGuid); VersionCheckRunner versionCheckRunner = new GameObject("VersionChecker").AddComponent<VersionCheckRunner>(); Object.DontDestroyOnLoad((Object)(object)((Component)versionCheckRunner).gameObject); SceneRootSurvivor.TryRegisterPersistentRunnerGameObject(((Component)versionCheckRunner).gameObject); versionCheckRunner.StartCheck(pluginGuid, currentVersion, logger, onComplete); } public static ModHealthSnapshot GetHealthSnapshot(string pluginGuid) { if (string.IsNullOrWhiteSpace(pluginGuid)) { return null; } lock (HealthLock) { if (!HealthByPluginGuid.TryGetValue(pluginGuid, out ModHealthSnapshot value)) { return null; } return new ModHealthSnapshot { PluginGuid = value.PluginGuid, LastCheckUtc = value.LastCheckUtc, ExceptionCount = value.ExceptionCount, LastError = value.LastError }; } } public static int CompareVersions(string v1, string v2) { if (string.IsNullOrEmpty(v1) || string.IsNullOrEmpty(v2)) { return 0; } v1 = v1.TrimStart('v', 'V'); v2 = v2.TrimStart('v', 'V'); int num = v1.IndexOfAny(new char[2] { '-', '+' }); if (num >= 0) { v1 = v1.Substring(0, num); } int num2 = v2.IndexOfAny(new char[2] { '-', '+' }); if (num2 >= 0) { v2 = v2.Substring(0, num2); } string[] array = v1.Split(new char[1] { '.' }); string[] array2 = v2.Split(new char[1] { '.' }); int num3 = Math.Max(array.Length, array2.Length); for (int i = 0; i < num3; i++) { int result; int num4 = ((i < array.Length && int.TryParse(array[i], out result)) ? result : 0); int result2; int num5 = ((i < array2.Length && int.TryParse(array2[i], out result2)) ? result2 : 0); if (num4 < num5) { return -1; } if (num4 > num5) { return 1; } } return 0; } private static void TouchHealth(string pluginGuid) { if (string.IsNullOrWhiteSpace(pluginGuid)) { return; } lock (HealthLock) { if (!HealthByPluginGuid.TryGetValue(pluginGuid, out ModHealthSnapshot value)) { value = new ModHealthSnapshot { PluginGuid = pluginGuid }; HealthByPluginGuid[pluginGuid] = value; } value.LastCheckUtc = DateTime.UtcNow; } } private static void RecordHealthError(string pluginGuid, string errorMessage) { if (string.IsNullOrWhiteSpace(pluginGuid)) { return; } lock (HealthLock) { if (!HealthByPluginGuid.TryGetValue(pluginGuid, out ModHealthSnapshot value)) { value = new ModHealthSnapshot { PluginGuid = pluginGuid }; HealthByPluginGuid[pluginGuid] = value; } value.LastCheckUtc = DateTime.UtcNow; value.ExceptionCount++; value.LastError = errorMessage; } } private static Regex GetModPattern(string pluginGuid) { lock (ModPatternCacheLock) { if (!ModPatternCache.TryGetValue(pluginGuid, out Regex value)) { value = new Regex("\"" + Regex.Escape(pluginGuid) + "\"\\s*:\\s*\\{([^}]+)\\}", RegexOptions.Compiled | RegexOptions.Singleline); ModPatternCache[pluginGuid] = value; } return value; } } } public static class VersionCheckerExtensions { public static void NotifyUpdateAvailable(this VersionChecker.VersionCheckResult result, ManualLogSource logger = null) { if (!result.UpdateAvailable) { return; } string text = result.ModName + " update available: v" + result.LatestVersion; try { Type type = ReflectionHelper.FindWishType("NotificationStack"); if (type != null) { Type type2 = ReflectionHelper.FindType("SingletonBehaviour`1", "Wish"); if (type2 != null) { object obj = type2.MakeGenericType(type).GetProperty("Instance")?.GetValue(null); if (obj != null) { MethodInfo method = type.GetMethod("SendNotification", new Type[5] { typeof(string), typeof(int), typeof(int), typeof(bool), typeof(bool) }); if (method != null) { method.Invoke(obj, new object[5] { text, 0, 1, false, true }); return; } } } } } catch (Exception ex) { if (logger != null) { logger.LogWarning((object)("Failed to send native notification: " + ex.Message)); } } if (logger != null) { logger.LogWarning((object)("[UPDATE AVAILABLE] " + text)); } if (!string.IsNullOrEmpty(result.NexusUrl) && logger != null) { logger.LogWarning((object)("Download at: " + result.NexusUrl)); } } } public static class SceneRootSurvivor { private static readonly object Lock = new object(); private static readonly List<string> NoKillSubstrings = new List<string>(); private static Harmony _harmony; public static void TryRegisterPersistentRunnerGameObject(GameObject go) { if (!((Object)(object)go == (Object)null)) { TryAddNoKillListSubstring(((Object)go).name); } } public static void TryAddNoKillListSubstring(string nameSubstring) { if (string.IsNullOrEmpty(nameSubstring)) { return; } lock (Lock) { bool flag = false; for (int i = 0; i < NoKillSubstrings.Count; i++) { if (string.Equals(NoKillSubstrings[i], nameSubstring, StringComparison.OrdinalIgnoreCase)) { flag = true; break; } } if (!flag) { NoKillSubstrings.Add(nameSubstring); } } EnsurePatched(); } private static void EnsurePatched() { //IL_0078: 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_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown //IL_00a3: Expected O, but got Unknown if (_harmony != null) { return; } lock (Lock) { if (_harmony == null) { MethodInfo methodInfo = AccessTools.Method(typeof(Scene), "GetRootGameObjects", Type.EmptyTypes, (Type[])null); if (!(methodInfo == null)) { string text = typeof(SceneRootSurvivor).Assembly.GetName().Name ?? "Unknown"; Harmony val = new Harmony("SunhavenMods.SceneRootSurvivor." + text); val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(SceneRootSurvivor), "OnGetRootGameObjectsPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _harmony = val; } } } } private static void OnGetRootGameObjectsPostfix(ref GameObject[] __result) { if (__result == null || __result.Length == 0) { return; } List<string> list; lock (Lock) { if (NoKillSubstrings.Count == 0) { return; } list = new List<string>(NoKillSubstrings); } List<GameObject> list2 = new List<GameObject>(__result); for (int i = 0; i < list.Count; i++) { string noKill = list[i]; list2.RemoveAll((GameObject a) => (Object)(object)a != (Object)null && ((Object)a).name.IndexOf(noKill, StringComparison.OrdinalIgnoreCase) >= 0); } __result = list2.ToArray(); } } public static class ReflectionHelper { public static readonly BindingFlags AllBindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy; public static Type FindType(string typeName, params string[] namespaces) { Assembly[] assemblies; if (namespaces != null && namespaces.Length != 0) { string[] array = namespaces; for (int i = 0; i < array.Length; i++) { Type type = AccessTools.TypeByName(array[i] + "." + typeName); if (type != null) { return type; } } assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { array = namespaces; foreach (string text in array) { try { Type type2 = assembly.GetType(text + "." + typeName, throwOnError: false); if (type2 != null) { return type2; } } catch { } } } assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly2 in assemblies) { try { Type type3 = assembly2.GetTypes().FirstOrDefault((Type t) => namespaces.Any((string ns) => string.Equals(t.FullName, ns + "." + typeName, StringComparison.Ordinal) || (t.Name == typeName && string.Equals(t.Namespace, ns, StringComparison.Ordinal)))); if (type3 != null) { return type3; } } catch (ReflectionTypeLoadException ex) { Type type4 = ex.Types?.FirstOrDefault((Type t) => t != null && namespaces.Any((string ns) => string.Equals(t.FullName, ns + "." + typeName, StringComparison.Ordinal) || (t.Name == typeName && string.Equals(t.Namespace, ns, StringComparison.Ordinal)))); if (type4 != null) { return type4; } } } return null; } Type type5 = AccessTools.TypeByName(typeName); if (type5 != null) { return type5; } assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly3 in assemblies) { try { type5 = assembly3.GetTypes().FirstOrDefault((Type t) => t.Name == typeName || t.FullName == typeName); if (type5 != null) { return type5; } } catch (ReflectionTypeLoadException) { } } return null; } public static Type FindModPlugin(string assemblyName) { if (string.IsNullOrWhiteSpace(assemblyName)) { return null; } Type type = FindType("Plugin", assemblyName); if (type != null && string.Equals(type.Assembly.GetName().Name, assemblyName, StringComparison.OrdinalIgnoreCase)) { return type; } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { if (!string.Equals(assembly.GetName().Name, assemblyName, StringComparison.OrdinalIgnoreCase)) { continue; } Type type2 = assembly.GetType(assemblyName + ".Plugin", throwOnError: false); if (type2 != null) { return type2; } try { Type type3 = assembly.GetTypes().FirstOrDefault((Type t) => t.IsClass && !t.IsAbstract && t.Name == "Plugin"); if (type3 != null) { return type3; } } catch (ReflectionTypeLoadException ex) { Type type4 = ex.Types?.FirstOrDefault((Type t) => t != null && t.IsClass && !t.IsAbstract && t.Name == "Plugin"); if (type4 != null) { return type4; } } } return null; } public static MethodInfo GetStaticMethod(Type type, string methodName) { return type?.GetMethod(methodName, AllBindingFlags); } public static Type FindWishType(string typeName) { return FindType(typeName, "Wish"); } public static object GetStaticValue(Type type, string memberName) { if (type == null) { return null; } try { PropertyInfo property = type.GetProperty(memberName, AllBindingFlags); if (property != null && property.GetMethod != null && property.GetIndexParameters().Length == 0) { return property.GetValue(null); } } catch (AmbiguousMatchException) { return null; } FieldInfo field = type.GetField(memberName, AllBindingFlags); if (field != null) { return field.GetValue(null); } return null; } public static object GetSingletonInstance(Type type) { if (type == null) { return null; } string[] array = new string[5] { "Instance", "instance", "_instance", "Singleton", "singleton" }; foreach (string memberName in array) { object staticValue = GetStaticValue(type, memberName); if (staticValue != null) { return staticValue; } } return null; } public static object GetInstanceValue(object instance, string memberName) { if (instance == null) { return null; } Type type = instance.GetType(); while (type != null) { PropertyInfo property = type.GetProperty(memberName, AllBindingFlags); if (property != null && property.GetMethod != null) { return property.GetValue(instance); } FieldInfo field = type.GetField(memberName, AllBindingFlags); if (field != null) { return field.GetValue(instance); } type = type.BaseType; } return null; } public static bool SetInstanceValue(object instance, string memberName, object value) { if (instance == null) { return false; } Type type = instance.GetType(); while (type != null) { PropertyInfo property = type.GetProperty(memberName, AllBindingFlags); if (property != null && property.SetMethod != null) { property.SetValue(instance, value); return true; } FieldInfo field = type.GetField(memberName, AllBindingFlags); if (field != null) { field.SetValue(instance, value); return true; } type = type.BaseType; } return false; } public static object InvokeMethod(object instance, string methodName, params object[] args) { if (instance == null) { return null; } Type type = instance.GetType(); Type[] array = args?.Select((object a) => a?.GetType() ?? typeof(object)).ToArray() ?? Type.EmptyTypes; MethodInfo methodInfo = AccessTools.Method(type, methodName, array, (Type[])null); if (methodInfo == null) { methodInfo = type.GetMethod(methodName, AllBindingFlags); } if (methodInfo == null) { return null; } return methodInfo.Invoke(instance, args); } public static object InvokeStaticMethod(Type type, string methodName, params object[] args) { if (type == null) { return null; } Type[] array = args?.Select((object a) => a?.GetType() ?? typeof(object)).ToArray() ?? Type.EmptyTypes; MethodInfo methodInfo = AccessTools.Method(type, methodName, array, (Type[])null); if (methodInfo == null) { methodInfo = type.GetMethod(methodName, AllBindingFlags); } if (methodInfo == null) { return null; } return methodInfo.Invoke(null, args); } public static FieldInfo[] GetAllFields(Type type) { if (type == null) { return Array.Empty<FieldInfo>(); } FieldInfo[] fields = type.GetFields(AllBindingFlags); IEnumerable<FieldInfo> second; if (!(type.BaseType != null) || !(type.BaseType != typeof(object))) { second = Enumerable.Empty<FieldInfo>(); } else { IEnumerable<FieldInfo> allFields = GetAllFields(type.BaseType); second = allFields; } return fields.Concat(second).Distinct().ToArray(); } public static PropertyInfo[] GetAllProperties(Type type) { if (type == null) { return Array.Empty<PropertyInfo>(); } PropertyInfo[] properties = type.GetProperties(AllBindingFlags); IEnumerable<PropertyInfo> second; if (!(type.BaseType != null) || !(type.BaseType != typeof(object))) { second = Enumerable.Empty<PropertyInfo>(); } else { IEnumerable<PropertyInfo> allProperties = GetAllProperties(type.BaseType); second = allProperties; } return (from p in properties.Concat(second) group p by p.Name into g select g.First()).ToArray(); } public static T TryGetValue<T>(object instance, string memberName, T defaultValue = default(T)) { try { object instanceValue = GetInstanceValue(instance, memberName); if (instanceValue is T result) { return result; } if (instanceValue != null && typeof(T).IsAssignableFrom(instanceValue.GetType())) { return (T)instanceValue; } return defaultValue; } catch { return defaultValue; } } } public static class ItemSearch { private static readonly ManualLogSource _log = Logger.CreateLogSource("ItemSearch"); private static object _dbInstance; private static FieldInfo _dictField; public static string FormatDisplay(string name, int itemId) { if (string.IsNullOrEmpty(name)) { return $"#{itemId}"; } return $"{name} (#{itemId})"; } public static List<KeyValuePair<int, string>> SearchItems(string query, int maxResults = 50) { List<KeyValuePair<int, string>> list = new List<KeyValuePair<int, string>>(); if (string.IsNullOrEmpty(query) || query.Trim().Length < 2) { return list; } try { Dictionary<int, ItemSellInfo> itemDictionary = GetItemDictionary(); if (itemDictionary == null) { return list; } string text = query.Trim().ToLowerInvariant(); int result; bool flag = int.TryParse(query.Trim(), out result); List<KeyValuePair<int, string>> list2 = new List<KeyValuePair<int, string>>(); List<KeyValuePair<int, string>> list3 = new List<KeyValuePair<int, string>>(); List<KeyValuePair<int, string>> list4 = new List<KeyValuePair<int, string>>(); foreach (KeyValuePair<int, ItemSellInfo> item2 in itemDictionary) { int key = item2.Key; string text2 = item2.Value?.name; if (!string.IsNullOrEmpty(text2)) { KeyValuePair<int, string> item = new KeyValuePair<int, string>(key, text2); string text3 = text2.ToLowerInvariant(); if (flag && key == result) { list2.Add(item); } else if (text3 == text) { list2.Add(item); } else if (text3.StartsWith(text)) { list3.Add(item); } else if (text3.Contains(text)) { list4.Add(item); } else if (flag && key.ToString().Contains(query.Trim())) { list4.Add(item); } } } list3.Sort((KeyValuePair<int, string> a, KeyValuePair<int, string> b) => string.Compare(a.Value, b.Value, StringComparison.OrdinalIgnoreCase)); list4.Sort((KeyValuePair<int, string> a, KeyValuePair<int, string> b) => string.Compare(a.Value, b.Value, StringComparison.OrdinalIgnoreCase)); list.AddRange(list2); list.AddRange(list3); list.AddRange(list4); if (list.Count > maxResults) { list.RemoveRange(maxResults, list.Count - maxResults); } } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogDebug((object)("[ItemSearch] SearchItems error: " + ex.Message)); } } return list; } public static string GetItemName(int itemId) { try { Dictionary<int, ItemSellInfo> itemDictionary = GetItemDictionary(); if (itemDictionary == null || !itemDictionary.ContainsKey(itemId)) { return null; } return itemDictionary[itemId]?.name; } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogDebug((object)$"[ItemSearch] GetItemName({itemId}): {ex.Message}"); } return null; } } public static ItemSellInfo GetItemSellInfo(int itemId) { try { Dictionary<int, ItemSellInfo> itemDictionary = GetItemDictionary(); if (itemDictionary != null && itemDictionary.TryGetValue(itemId, out var value)) { return value; } } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogDebug((object)$"[ItemSearch] GetItemSellInfo({itemId}): {ex.Message}"); } } return null; } public static List<KeyValuePair<int, string>> GetAllItems() { List<KeyValuePair<int, string>> list = new List<KeyValuePair<int, string>>(); try { Dictionary<int, ItemSellInfo> itemDictionary = GetItemDictionary(); if (itemDictionary == null) { return list; } foreach (KeyValuePair<int, ItemSellInfo> item in itemDictionary) { string value = item.Value?.name; if (!string.IsNullOrEmpty(value)) { list.Add(new KeyValuePair<int, string>(item.Key, value)); } } list.Sort((KeyValuePair<int, string> a, KeyValuePair<int, string> b) => string.Compare(a.Value, b.Value, StringComparison.OrdinalIgnoreCase)); } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogDebug((object)("[ItemSearch] GetAllItems: " + ex.Message)); } } return list; } private static Dictionary<int, ItemSellInfo> GetItemDictionary() { try { if (_dbInstance != null) { object dbInstance = _dbInstance; Object val = (Object)((dbInstance is Object) ? dbInstance : null); if (val == null || !(val == (Object)null)) { goto IL_005b; } } _dbInstance = null; _dictField = null; _dbInstance = GetSingletonInstance("Wish.ItemInfoDatabase"); if (_dbInstance != null) { _dictField = _dbInstance.GetType().GetField("allItemSellInfos", BindingFlags.Instance | BindingFlags.Public); } goto IL_005b; IL_005b: if (_dbInstance == null || _dictField == null) { return null; } return _dictField.GetValue(_dbInstance) as Dictionary<int, ItemSellInfo>; } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogDebug((object)("[ItemSearch] GetItemDictionary: " + ex.Message)); } return null; } } private static object GetSingletonInstance(string typeName) { try { Type type = AccessTools.TypeByName("Wish.SingletonBehaviour`1"); if (type == null) { return null; } Type type2 = AccessTools.TypeByName(typeName); if (type2 == null) { return null; } return type.MakeGenericType(type2).GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy)?.GetValue(null); } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogDebug((object)("[ItemSearch] GetSingletonInstance(" + typeName + "): " + ex.Message)); } return null; } } } public static class TextInputFocusGuard { private const float DefaultPollIntervalSeconds = 0.25f; private static float _nextPollTime = -1f; private static bool _cachedDefer; private static bool _tmpTypeLookupDone; private static Type _tmpInputFieldType; private static bool _qcLookupDone; private static Type _qcType; private static PropertyInfo _qcInstanceProp; private static PropertyInfo _qcIsActiveProp; private static FieldInfo _qcIsActiveField; public static bool ShouldDeferModHotkeys(ManualLogSource debugLog = null, float pollIntervalSeconds = 0.25f) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _nextPollTime) { return _cachedDefer; } _nextPollTime = realtimeSinceStartup + Mathf.Max(0.05f, pollIntervalSeconds); bool flag = false; try { if (GUIUtility.keyboardControl != 0) { flag = true; } if (!flag) { EventSystem current = EventSystem.current; GameObject val = ((current != null) ? current.currentSelectedGameObject : null); if ((Object)(object)val != (Object)null) { if ((Object)(object)val.GetComponent<InputField>() != (Object)null) { flag = true; } else if (TryGetTmpInputField(val)) { flag = true; } } } if (!flag && IsQuantumConsoleActiveInternal(debugLog)) { flag = true; } } catch (Exception ex) { if (debugLog != null) { debugLog.LogDebug((object)("[TextInputFocusGuard] " + ex.Message)); } } _cachedDefer = flag; return flag; } private static bool TryGetTmpInputField(GameObject go) { if (!_tmpTypeLookupDone) { _tmpTypeLookupDone = true; _tmpInputFieldType = AccessTools.TypeByName("TMPro.TMP_InputField"); } if (_tmpInputFieldType == null) { return false; } return (Object)(object)go.GetComponent(_tmpInputFieldType) != (Object)null; } public static bool IsQuantumConsoleActive(ManualLogSource debugLog = null) { return IsQuantumConsoleActiveInternal(debugLog); } public static bool IsUnityUiTextInputFocused() { try { EventSystem current = EventSystem.current; GameObject val = ((current != null) ? current.currentSelectedGameObject : null); if ((Object)(object)val == (Object)null) { return false; } if ((Object)(object)val.GetComponent<InputField>() != (Object)null) { return true; } return TryGetTmpInputField(val); } catch { return false; } } private static bool IsQuantumConsoleActiveInternal(ManualLogSource debugLog) { try { if (!_qcLookupDone) { _qcLookupDone = true; _qcType = AccessTools.TypeByName("QFSW.QC.QuantumConsole"); if (_qcType != null) { _qcInstanceProp = AccessTools.Property(_qcType, "Instance"); _qcIsActiveProp = AccessTools.Property(_qcType, "IsActive"); _qcIsActiveField = AccessTools.Field(_qcType, "isActive") ?? AccessTools.Field(_qcType, "_isActive"); } } if (_qcType == null) { return false; } object obj = _qcInstanceProp?.GetValue(null); if (obj == null) { return false; } if (_qcIsActiveProp != null && _qcIsActiveProp.PropertyType == typeof(bool)) { return (bool)_qcIsActiveProp.GetValue(obj); } if (_qcIsActiveField != null && _qcIsActiveField.FieldType == typeof(bool)) { return (bool)_qcIsActiveField.GetValue(obj); } } catch (Exception ex) { if (debugLog != null) { debugLog.LogDebug((object)("[TextInputFocusGuard] Quantum Console focus check failed: " + ex.Message)); } } return false; } } internal static class MinimalJsonParser { internal static void WriteJsonString(StringBuilder sb, string value) { sb.Append('"'); if (value != null) { foreach (char c in value) { switch (c) { case '"': sb.Append("\\\""); break; case '\\': sb.Append("\\\\"); break; case '\n': sb.Append("\\n"); break; case '\r': sb.Append("\\r"); break; case '\t': sb.Append("\\t"); break; case '\b': sb.Append("\\b"); break; case '\f': sb.Append("\\f"); break; default: sb.Append(c); break; } } } sb.Append('"'); } internal static void SkipWhitespace(string json, ref int pos) { while (pos < json.Length && char.IsWhiteSpace(json[pos])) { pos++; } } internal static object ParseValue(string json, ref int pos) { SkipWhitespace(json, ref pos); if (pos >= json.Length) { return null; } char c = json[pos]; switch (c) { case '"': return ParseString(json, ref pos); case '{': return ParseObject(json, ref pos); case '[': return ParseArray(json, ref pos); case 't': return ParseLiteral(json, ref pos, "true", true); case 'f': return ParseLiteral(json, ref pos, "false", false); case 'n': return ParseLiteral(json, ref pos, "null", null); default: if (!char.IsDigit(c)) { return null; } goto case '-'; case '-': return ParseNumber(json, ref pos); } } internal static Dictionary<string, object> ParseObject(string json, ref int pos) { SkipWhitespace(json, ref pos); if (pos >= json.Length || json[pos] != '{') { return null; } pos++; Dictionary<string, object> dictionary = new Dictionary<string, object>(); SkipWhitespace(json, ref pos); if (pos < json.Length && json[pos] == '}') { pos++; return dictionary; } while (pos < json.Length) { SkipWhitespace(json, ref pos); string text = ParseString(json, ref pos); if (text == null) { break; } SkipWhitespace(json, ref pos); if (pos >= json.Length || json[pos] != ':') { break; } pos++; SkipWhitespace(json, ref pos); dictionary[text] = ParseValue(json, ref pos); SkipWhitespace(json, ref pos); if (pos >= json.Length || json[pos] != ',') { break; } pos++; } SkipWhitespace(json, ref pos); if (pos < json.Length && json[pos] == '}') { pos++; } return dictionary; } internal static List<object> ParseArray(string json, ref int pos) { SkipWhitespace(json, ref pos); if (pos >= json.Length || json[pos] != '[') { return null; } pos++; List<object> list = new List<object>(); SkipWhitespace(json, ref pos); if (pos < json.Length && json[pos] == ']') { pos++; return list; } while (pos < json.Length) { SkipWhitespace(json, ref pos); list.Add(ParseValue(json, ref pos)); SkipWhitespace(json, ref pos); if (pos >= json.Length || json[pos] != ',') { break; } pos++; } SkipWhitespace(json, ref pos); if (pos < json.Length && json[pos] == ']') { pos++; } return list; } internal static string ParseString(string json, ref int pos) { SkipWhitespace(json, ref pos); if (pos >= json.Length || json[pos] != '"') { return null; } pos++; StringBuilder stringBuilder = new StringBuilder(); while (pos < json.Length) { char c = json[pos]; if (c == '\\' && pos + 1 < json.Length) { pos++; switch (json[pos]) { case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; case '/': stringBuilder.Append('/'); break; case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'u': { if (pos + 4 < json.Length && ushort.TryParse(json.Substring(pos + 1, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result)) { pos += 4; if (result >= 55296 && result <= 56319 && pos + 5 < json.Length && json[pos] == '\\' && json[pos + 1] == 'u' && ushort.TryParse(json.Substring(pos + 2, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result2) && result2 >= 56320 && result2 <= 57343) { stringBuilder.Append(char.ConvertFromUtf32(char.ConvertToUtf32((char)result, (char)result2))); pos += 6; } else { stringBuilder.Append((char)result); } } else { stringBuilder.Append('u'); } break; } default: stringBuilder.Append(json[pos]); break; } pos++; } else { if (c == '"') { pos++; return stringBuilder.ToString(); } stringBuilder.Append(c); pos++; } } return stringBuilder.ToString(); } internal static object ParseNumber(string json, ref int pos) { int num = pos; bool flag = false; if (pos < json.Length && json[pos] == '-') { pos++; } while (pos < json.Length && char.IsDigit(json[pos])) { pos++; } if (pos < json.Length && json[pos] == '.') { flag = true; pos++; while (pos < json.Length && char.IsDigit(json[pos])) { pos++; } } if (pos < json.Length && (json[pos] == 'e' || json[pos] == 'E')) { flag = true; pos++; if (pos < json.Length && (json[pos] == '+' || json[pos] == '-')) { pos++; } while (pos < json.Length && char.IsDigit(json[pos])) { pos++; } } string s = json.Substring(num, pos - num); if (flag && double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return result; } if (!flag && long.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2)) { return result2; } return 0L; } internal static object ParseLiteral(string json, ref int pos, string literal, object result) { if (pos + literal.Length <= json.Length && json.Substring(pos, literal.Length) == literal) { pos += literal.Length; return result; } pos++; return null; } internal static int ToInt(object val) { if (val is long num) { return (int)num; } if (val is double num2) { return (int)num2; } if (val is int) { return (int)val; } return 0; } } public static class ModLocalization { private static readonly string[] SupportedLanguageCodes = new string[16] { "en", "da", "de", "es", "fr", "it", "ja", "ko", "nl", "pt", "pt-BR", "ru", "sv", "zh-CN", "zh-TW", "uk" }; private static readonly Dictionary<string, string> LanguageAlias = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { { "pt-br", "pt-BR" }, { "pt_br", "pt-BR" }, { "zh-cn", "zh-CN" }, { "zh_cn", "zh-CN" }, { "zh-tw", "zh-TW" }, { "zh_tw", "zh-TW" } }; private static string _modId; private static Dictionary<string, Dictionary<string, string>> _tables; private static ManualLogSource _log; private static bool _initialized; private static bool _forceEnglish; public static string CurrentLanguage { get; private set; } = "en"; public static bool ForceEnglish => _forceEnglish; public static bool IsReady { get { if (_initialized && _tables != null) { return _tables.Count > 0; } return false; } } public static event Action<string> LanguageChanged { add { LanguageChangeWatcher.LanguageChanged += value; } remove { LanguageChangeWatcher.LanguageChanged -= value; } } public static void Init(string modId, Dictionary<string, Dictionary<string, string>> tables, Harmony harmony, ManualLogSource log) { _modId = modId ?? string.Empty; _tables = tables ?? new Dictionary<string, Dictionary<string, string>>(); _log = log; _initialized = true; RefreshCurrentLanguage(); LanguageChangeWatcher.EnsurePatched(harmony); } public static void SetForceEnglish(bool forceEnglish) { _forceEnglish = forceEnglish; ApplyEffectiveLanguage(); } internal static void OnGameLanguageChanged(string languageCode) { if (_tables == null || _forceEnglish) { return; } string text = NormalizeLanguageCode(languageCode); if (!string.Equals(CurrentLanguage, text, StringComparison.OrdinalIgnoreCase)) { CurrentLanguage = text; ManualLogSource log = _log; if (log != null) { log.LogDebug((object)("[" + _modId + "] Language changed to " + CurrentLanguage)); } } } public static void RefreshCurrentLanguage() { ApplyEffectiveLanguage(); } private static void ApplyEffectiveLanguage() { if (_forceEnglish) { CurrentLanguage = "en"; } else { RefreshCurrentLanguageFromGame(); } } private static void RefreshCurrentLanguageFromGame() { try { string currentLanguageCode = LocalizationManager.CurrentLanguageCode; if (!string.IsNullOrWhiteSpace(currentLanguageCode)) { CurrentLanguage = NormalizeLanguageCode(currentLanguageCode); } } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("[" + _modId + "] Failed to read LocalizationManager.CurrentLanguageCode: " + ex.Message)); } CurrentLanguage = "en"; } } public static string T(string key) { if (!TryT(key, out string value)) { return key; } return value; } public static string T(string key, params object[] args) { string text = T(key); if (args == null || args.Length == 0) { return text; } try { return string.Format(CultureInfo.InvariantCulture, text, args); } catch (FormatException ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("[" + _modId + "] Format failed for key '" + key + "': " + ex.Message)); } return text; } } public static bool TryT(string key, out string value) { value = null; if (string.IsNullOrEmpty(key)) { return false; } if (_tables == null || !_tables.TryGetValue(key, out Dictionary<string, string> value2) || value2 == null) { return false; } if (TryGetForLanguage(value2, CurrentLanguage, out value)) { return true; } if (!string.Equals(CurrentLanguage, "en", StringComparison.OrdinalIgnoreCase) && TryGetForLanguage(value2, "en", out value)) { return true; } return false; } private static bool TryGetForLanguage(Dictionary<string, string> translations, string languageCode, out string value) { value = null; if (translations == null) { return false; } string text = NormalizeLanguageCode(languageCode); if (translations.TryGetValue(text, out value) && !string.IsNullOrEmpty(value)) { return true; } foreach (KeyValuePair<string, string> translation in translations) { if (string.Equals(translation.Key, text, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(translation.Value)) { value = translation.Value; return true; } } return false; } public static string NormalizeLanguageCode(string code) { if (string.IsNullOrWhiteSpace(code)) { return "en"; } string text = code.Trim(); if (LanguageAlias.TryGetValue(text, out string value)) { return value; } string[] supportedLanguageCodes = SupportedLanguageCodes; foreach (string text2 in supportedLanguageCodes) { if (string.Equals(text2, text, StringComparison.OrdinalIgnoreCase)) { return text2; } } return "en"; } public static Dictionary<string, Dictionary<string, string>> ParseStringsJson(string json) { Dictionary<string, Dictionary<string, string>> dictionary = new Dictionary<string, Dictionary<string, string>>(StringComparer.Ordinal); if (string.IsNullOrWhiteSpace(json)) { return dictionary; } int pos = 0; Dictionary<string, object> dictionary2 = MinimalJsonParser.ParseObject(json, ref pos); if (dictionary2 == null) { return dictionary; } foreach (KeyValuePair<string, object> item in dictionary2) { if (!(item.Value is Dictionary<string, object> dictionary3)) { continue; } Dictionary<string, string> dictionary4 = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair<string, object> item2 in dictionary3) { if (item2.Value is string value) { dictionary4[NormalizeLanguageCode(item2.Key)] = value; } } if (dictionary4.Count > 0) { dictionary[item.Key] = dictionary4; } } return dictionary; } public static Dictionary<string, Dictionary<string, string>> LoadEmbeddedStrings(Assembly assembly, string resourceName, ManualLogSource log = null) { try { using Stream stream = assembly.GetManifestResourceStream(resourceName); if (stream == null) { if (log != null) { log.LogError((object)("Localization resource not found: " + resourceName)); } return new Dictionary<string, Dictionary<string, string>>(); } using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8); return ParseStringsJson(streamReader.ReadToEnd()); } catch (Exception ex) { if (log != null) { log.LogError((object)("Failed to load localization resource '" + resourceName + "': " + ex.Message)); } return new Dictionary<string, Dictionary<string, string>>(); } } public static void Shutdown() { _log = null; } } public static class LanguageChangeWatcher { private static bool _patched; public static event Action<string> LanguageChanged; public static void EnsurePatched(Harmony harmony) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown if (_patched || harmony == null) { return; } try { MethodInfo methodInfo = AccessTools.Method(typeof(LanguageChangeWatcher), "OnSetLanguageAndCode", (Type[])null, (Type[])null); harmony.Patch((MethodBase)AccessTools.Method(typeof(LocalizationManager), "SetLanguageAndCode", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _patched = true; } catch (Exception innerException) { throw new InvalidOperationException("Failed to patch LocalizationManager.SetLanguageAndCode", innerException); } } private static void OnSetLanguageAndCode(string LanguageName, string LanguageCode) { string text = ModLocalization.NormalizeLanguageCode(string.IsNullOrWhiteSpace(LanguageCode) ? LocalizationManager.CurrentLanguageCode : LanguageCode); ModLocalization.OnGameLanguageChanged(text); LanguageChangeWatcher.LanguageChanged?.Invoke(text); } internal static void RaiseLanguageChanged(string languageCode) { string obj = ModLocalization.NormalizeLanguageCode(languageCode); LanguageChangeWatcher.LanguageChanged?.Invoke(obj); } } public static class LocalizationBootstrap { public static ConfigEntry<bool> BindForceEnglish(ConfigFile config) { ConfigEntry<bool> entry = config.Bind<bool>("Localization", "ForceEnglish", false, "Keep this mod's UI in English and ignore Sun Haven's in-game language setting."); ApplyForceEnglish(entry.Value); entry.SettingChanged += delegate { ApplyForceEnglish(entry.Value); }; return entry; } private static void ApplyForceEnglish(bool forceEnglish) { ModLocalization.SetForceEnglish(forceEnglish); LanguageChangeWatcher.RaiseLanguageChanged(ModLocalization.CurrentLanguage); } public static void Init(string pluginGuid, Harmony harmony, ManualLogSource log, Assembly assembly = null) { if ((object)assembly == null) { assembly = Assembly.GetCallingAssembly(); } Dictionary<string, Dictionary<string, string>> tables = ModLocalization.LoadEmbeddedStrings(assembly, pluginGuid + ".Localization.strings.json", log); ModLocalization.Init(pluginGuid, tables, harmony, log); } public static void EnsureInitialized(string pluginGuid, Harmony harmony, ManualLogSource log, Assembly assembly = null) { if (!ModLocalization.IsReady) { Init(pluginGuid, harmony, log, assembly); } } } } namespace SenpaisChest { [BepInPlugin("com.azraelgodking.senpaischest", "Senpai's Chest", "3.1.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { private static SmartChestManager _staticManager; private static SmartChestSaveSystem _staticSaveSystem; private static SmartChestUI _staticUI; private static SmartChestConfig _staticConfig; private static MuseumTodoIntegration _museumTodoIntegration; internal static Chest CurrentInteractingChest; private static string _gameplaySessionCharacter; private static bool _applicationQuitting; private Harmony _harmony; private SmartChestManager _manager; private SmartChestSaveSystem _saveSystem; private SmartChestUI _ui; private SmartChestConfig _config; private static GameObject _persistentRunner; private static SmartChestPersistentRunner _updateRunner; private bool _wasInMenuScene = true; private static GameObject _pendingChestPanel; private static Chest _pendingChest; private static int _pendingChestButtonFrames; private const string SenpaisChestConfigPanelName = "SenpaisChest_ConfigPanel"; public static Plugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } public static ConfigFile ConfigFile { get; private set; } private void Awake() { //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Expected O, but got Unknown //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Expected O, but got Unknown //IL_026e: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; ConfigFile = CreateNamedConfig(); ConfigFileHelper.ReplacePluginConfig((BaseUnityPlugin)(object)this, ConfigFile, (Action<string>)Log.LogWarning); Log.LogInfo((object)"Loading Senpai's Chest v3.1.1"); CreatePersistentRunner(); try { _config = new SmartChestConfig(); _config.Initialize(ConfigFile); _staticConfig = _config; _config.UIScale.SettingChanged += delegate { float scale = Mathf.Clamp(_config.UIScale.Value, 0.5f, 2.5f); _staticUI?.SetScale(scale); }; _manager = new SmartChestManager(); _saveSystem = new SmartChestSaveSystem(_manager); _staticManager = _manager; _staticSaveSystem = _saveSystem; GameObject val = new GameObject("SenpaisChest_UI"); Object.DontDestroyOnLoad((Object)(object)val); _ui = val.AddComponent<SmartChestUI>(); _ui.Initialize(_manager); _ui.SetScale(Mathf.Clamp(_config.UIScale.Value, 0.5f, 2.5f)); _staticUI = _ui; _harmony = new Harmony("com.azraelgodking.senpaischest"); LocalizationBootstrap.BindForceEnglish(ConfigFile); LocalizationBootstrap.Init("com.azraelgodking.senpaischest", _harmony, Log); ApplyPatches(); InitializeIntegrations(); SceneManager.sceneLoaded += OnSceneLoaded; if (_config.CheckForUpdates.Value) { VersionChecker.CheckForUpdate("com.azraelgodking.senpaischest", "3.1.1", Log, delegate(VersionChecker.VersionCheckResult result) { result.NotifyUpdateAvailable(Log); }); } Log.LogInfo((object)"Senpai's Chest loaded successfully!"); ModDiagnostics.LogModStartup(Log, "com.azraelgodking.senpaischest", "Senpai's Chest", "3.1.1", ModHealthIntegrationSummary.Build(("DevTools", "com.azraelgodking.havendevtools"), ("SMUT", "com.azraelgodking.sunhavenmuseumutilitytracker"), ("Todo", "com.azraelgodking.sunhaventodo")), "startup", false); Log.LogInfo((object)string.Format("Press {0}{1} to configure a chest while interacting with it", _config.RequireCtrlModifier.Value ? "Ctrl+" : "", _config.ToggleKey.Value)); if (_config.EnableChestLabels.Value) { Log.LogInfo((object)"Chest Labels enabled - labels shown above Chest and BankChest (not Hoppers/Animal Feeders)"); } } catch (Exception arg) { Log.LogError((object)string.Format("Failed to load {0}: {1}", "Senpai's Chest", arg)); } } private void CreatePersistentRunner() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown if ((Object)(object)_persistentRunner != (Object)null) { Log.LogInfo((object)"PersistentRunner already exists"); return; } _persistentRunner = new GameObject("SenpaisChest_PersistentRunner"); Object.DontDestroyOnLoad((Object)(object)_persistentRunner); ((Object)_persistentRunner).hideFlags = (HideFlags)61; SceneRootSurvivor.TryRegisterPersistentRunnerGameObject(_persistentRunner); _updateRunner = _persistentRunner.AddComponent<SmartChestPersistentRunner>(); Log.LogInfo((object)"Created hidden PersistentRunner"); } private static ConfigFile CreateNamedConfig() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown string text = Path.Combine(Paths.ConfigPath, "SenpaisChest.cfg"); string text2 = Path.Combine(Paths.ConfigPath, "com.azraelgodking.senpaischest.cfg"); try { if (!File.Exists(text) && File.Exists(text2)) { File.Copy(text2, text); } } catch (Exception ex) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("[Config] Migration to SenpaisChest.cfg failed: " + ex.Message)); } } return new ConfigFile(text, true); } public static void EnsureUIComponentsExist() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Expected O, but got Unknown try { if ((Object)(object)_persistentRunner == (Object)null || (Object)(object)_updateRunner == (Object)null) { ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"[EnsureUI] Recreating PersistentRunner..."); } _persistentRunner = new GameObject("SenpaisChest_PersistentRunner"); Object.DontDestroyOnLoad((Object)(object)_persistentRunner); ((Object)_persistentRunner).hideFlags = (HideFlags)61; SceneRootSurvivor.TryRegisterPersistentRunnerGameObject(_persistentRunner); _updateRunner = _persistentRunner.AddComponent<SmartChestPersistentRunner>(); } if ((Object)(object)_staticUI == (Object)null) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogInfo((object)"[EnsureUI] Recreating SmartChestUI..."); } GameObject val = new GameObject("SenpaisChest_UI"); Object.DontDestroyOnLoad((Object)val); _staticUI = val.AddComponent<SmartChestUI>(); _staticUI.Initialize(_staticManager); _staticUI.SetScale(Mathf.Clamp(_staticConfig.UIScale.Value, 0.5f, 2.5f)); } } catch (Exception ex) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogError((object)("[EnsureUI] Error recreating UI: " + ex.Message)); } } } private void InitializeIntegrations() { try { _museumTodoIntegration?.Dispose(); _museumTodoIntegration = null; Dictionary<string, PluginInfo> pluginInfos = Chainloader.PluginInfos; bool flag = pluginInfos.ContainsKey("com.azraelgodking.sunhavenmuseumutilitytracker"); bool flag2 = pluginInfos.ContainsKey("com.azraelgodking.sunhaventodo"); if (flag && flag2) { _museumTodoIntegration = new MuseumTodoIntegration(); return; } if (!flag) { Log.LogInfo((object)"[Integrations] S.M.U.T. not found"); } if (!flag2) { Log.LogInfo((object)"[Integrations] SunhavenTodo not found"); } Log.LogInfo((object)"[Integrations] Museum todo integration disabled (requires both S.M.U.T. and Todo)"); } catch (Exception ex) { Log.LogWarning((object)("[Integrations] Error initializing: " + ex.Message)); } } private void ApplyPatches() { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O, but got Unknown //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Expected O, but got Unknown //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Expected O, but got Unknown //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Expected O, but got Unknown //IL_02f3: Unknown result type (might be due to invalid IL or missing references) //IL_0301: Expected O, but got Unknown //IL_0371: Unknown result type (might be due to invalid IL or missing references) //IL_037f: Expected O, but got Unknown //IL_03f2: Unknown result type (might be due to invalid IL or missing references) //IL_03ff: Expected O, but got Unknown try { PatchMethod(typeof(Player), "InitializeAsOwner", typeof(Plugin), "OnPlayerInitialized"); PatchMethod(typeof(Chest), "Interact", typeof(Plugin), "OnChestInteract", new Type[1] { typeof(int) }); MethodInfo methodInfo = AccessTools.Method(typeof(Chest), "EndInteract", new Type[1] { typeof(int) }, (Type[])null); if (methodInfo != null) { HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(Plugin), "OnChestEndInteract_Prefix", (Type[])null, (Type[])null)); _harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"Patched Chest.EndInteract (prefix)"); } else { Log.LogWarning((object)"Could not find method Chest.EndInteract"); } Type type = AccessTools.TypeByName("Wish.UIHandler"); Type type2 = AccessTools.TypeByName("Wish.IExternalUIHandler"); if (type != null && type2 != null) { MethodInfo methodInfo2 = AccessTools.Method(type, "OpenUI", new Type[5] { typeof(GameObject), typeof(Transform), typeof(bool), typeof(bool), type2 }, (Type[])null); if (methodInfo2 != null) { HarmonyMethod val2 = new HarmonyMethod(AccessTools.Method(typeof(Plugin), "OnUIHandlerOpenUI_Postfix", (Type[])null, (Type[])null)); _harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"Patched UIHandler.OpenUI (postfix)"); } else { Log.LogWarning((object)"Could not find method UIHandler.OpenUI"); } } else { Log.LogWarning((object)"Could not find Wish.UIHandler or Wish.IExternalUIHandler for chest button integration"); } MethodInfo methodInfo3 = AccessTools.Method(typeof(Input), "GetKeyDown", new Type[1] { typeof(KeyCode) }, (Type[])null); if (methodInfo3 != null) { _harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(AccessTools.Method(typeof(Plugin), "OnInputGetKeyDown_Prefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"Patched Input.GetKeyDown (prefix)"); } MethodInfo methodInfo4 = AccessTools.Method(typeof(Input), "GetKey", new Type[1] { typeof(KeyCode) }, (Type[])null); if (methodInfo4 != null) { _harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(AccessTools.Method(typeof(Plugin), "OnInputGetKey_Prefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"Patched Input.GetKey (prefix)"); } MethodInfo methodInfo5 = AccessTools.Method(typeof(Input), "GetButtonDown", new Type[1] { typeof(string) }, (Type[])null); if (methodInfo5 != null) { _harmony.Patch((MethodBase)methodInfo5, new HarmonyMethod(AccessTools.Method(typeof(Plugin), "OnInputGetButtonDown_Prefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"Patched Input.GetButtonDown (prefix)"); } Type type3 = AccessTools.TypeByName("Wish.PlayerInput"); if (type3 != null) { MethodInfo methodInfo6 = AccessTools.Method(type3, "GetButtonDown", new Type[1] { typeof(string) }, (Type[])null); if (methodInfo6 != null) { _harmony.Patch((MethodBase)methodInfo6, new HarmonyMethod(AccessTools.Method(typeof(Plugin), "OnPlayerInputGetButtonDown_Prefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"Patched PlayerInput.GetButtonDown(string) (prefix)"); } else { Log.LogWarning((object)"Could not find method PlayerInput.GetButtonDown(string)"); } } else { Log.LogWarning((object)"Could not find Wish.PlayerInput type for UICancel blocking"); } MethodInfo methodInfo7 = AccessTools.Method(typeof(Chest), "OnDisable", (Type[])null, (Type[])null); if (methodInfo7 != null) { _harmony.Patch((MethodBase)methodInfo7, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(Plugin), "OnChestOnDisable_Postfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"Patched Chest.OnDisable (cleanup when chest removed from world)"); } ApplyChestLabelPatches(); Log.LogInfo((object)"Harmony patches applied successfully"); } catch (Exception arg) { Log.LogError((object)$"Failed to apply patches: {arg}"); } } private void ApplyChestLabelPatches() { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Expected O, but got Unknown //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Expected O, but got Unknown Type? typeFromHandle = typeof(ChestLabelPatch); MethodInfo methodInfo = AccessTools.Method(typeFromHandle, "SetMeta_Postfix", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeFromHandle, "OnEnable_Postfix", (Type[])null, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(typeFromHandle, "InteractionPoint_Postfix", (Type[])null, (Type[])null); string[] array = new string[3] { "SetMeta", "ReceiveNewMeta", "SaveMeta" }; foreach (string text in array) { MethodInfo methodInfo4 = AccessTools.Method(typeof(Chest), text, (Type[])null, (Type[])null); if (methodInfo4 != null) { _harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)("Patched Chest." + text + " (labels)")); } } MethodInfo methodInfo5 = AccessTools.Method(typeof(Chest), "OnEnable", (Type[])null, (Type[])null); if (methodInfo5 != null) { _harmony.Patch((MethodBase)methodInfo5, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"Patched Chest.OnEnable (labels)"); } MethodInfo methodInfo6 = AccessTools.Method(typeof(Chest), "get_InteractionPoint", (Type[])null, (Type[])null); if (methodInfo6 != null) { _harmony.Patch((MethodBase)methodInfo6, (HarmonyMethod)null, new HarmonyMethod(methodInfo3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"Patched Chest.get_InteractionPoint (labels)"); } } private void PatchMethod(Type targetType, string methodName, Type patchType, string patchMethodName, Type[] parameters = null) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown MethodInfo methodInfo = ((parameters != null) ? AccessTools.Method(targetType, methodName, parameters, (Type[])null) : AccessTools.Method(targetType, methodName, (Type[])null, (Type[])null)); if (methodInfo == null) { Log.LogWarning((object)("Could not find method " + targetType.Name + "." + methodName)); return; } MethodInfo methodInfo2 = AccessTools.Method(patchType, patchMethodName, (Type[])null, (Type[])null); _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)("Patched " + targetType.Name + "." + methodName)); } private static void OnPlayerInitialized(Player __instance) { try { if ((Object)(object)__instance != (Object)(object)Player.Instance) { return; } EnsureUIComponentsExist(); string text = null; CharacterData currentCharacter = GameSave.CurrentCharacter; if (currentCharacter != null) { text = currentCharacter.characterName; } if (string.IsNullOrEmpty(text)) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)"Player initialized but no character name found"); } return; } ManualLogSource log2 = Log; if (log2 != null) { log2.LogInfo((object)("Player initialized: " + text)); } if (string.Equals(_gameplaySessionCharacter, text, StringComparison.Ordinal)) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogDebug((object)("[SenpaisChest] Skip disk reload on player init — '" + text + "' already active")); } return; } _museumTodoIntegration?.Reset(); _staticManager?.SetCharacterName(text); if (string.Equals(_gameplaySessionCharacter, text, StringComparison.Ordinal)) { ManualLogSource log4 = Log; if (log4 != null) { log4.LogDebug((object)("[SenpaisChest] Skip disk reload on InitializeAsOwner — '" + text + "' already active")); } } else { SmartChestSaveData data = _staticSaveSystem?.Load(text); _staticManager?.LoadData(data); _gameplaySessionCharacter = text; } } catch (Exception arg) { ManualLogSource log5 = Log; if (log5 != null) { log5.LogError((object)$"Error in OnPlayerInitialized: {arg}"); } } } private static void OnChestInteract(Chest __instance, int interactType) { if (interactType != 0) { return; } CurrentInteractingChest = __instance; try { FieldInfo field = ((object)__instance).GetType().GetField("ui", BindingFlags.Instance | BindingFlags.NonPublic); if (!(field != null)) { return; } object? value = field.GetValue(__instance); GameObject val = (GameObject)((value is GameObject) ? value : null); if (val != null && (Object)(object)val != (Object)null) { _pendingChestPanel = val; _pendingChest = __instance; _pendingChestButtonFrames = 3; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)$"[SenpaisChest] Scheduled embedded panel from Chest.Interact (will add in 3 frames) - UI active: {val.activeInHierarchy}"); } } } catch (Exception ex) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogDebug((object)("[SenpaisChest] Could not schedule chest button: " + ex.Message)); } } } private static void OnChestOnDisable_Postfix(Chest __instance) { try { if (_applicationQuitting || !Application.isPlaying || (Object)(object)__instance == (Object)null) { return; } string chestId = SmartChestManager.GetChestId(__instance); if (!string.IsNullOrEmpty(chestId)) { ManualLogSource log = Log; if (log != null) { log.LogDebug((object)("[SenpaisChest] OnDisable for chest " + chestId + " — rules kept (not treated as removal)")); } } } catch (Exception ex) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogError((object)("[SenpaisChest] OnChestOnDisable_Postfix: " + ex.Message)); } } } private static bool OnInputGetKeyDown_Prefix(KeyCode key, ref bool __result) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 if ((int)key != 8) { return true; } if (!SmartChestConfig.StaticBlockInputWhenTyping) { return true; } if (!SmartChestUI.ShouldBlockGameInput(_staticUI)) { return true; } __result = false; return false; } private static bool OnInputGetKey_Prefix(KeyCode key, ref bool __result) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 if ((int)key != 8) { return true; } if (!SmartChestConfig.StaticBlockInputWhenTyping) { return true; } if (!SmartChestUI.ShouldBlockGameInput(_staticUI)) { return true; } __result = false; return false; } private static bool OnInputGetButtonDown_Prefix(string buttonName, ref bool __result) { if (string.IsNullOrEmpty(buttonName)) { return true; } if (!buttonName.Equals("Cancel", StringComparison.OrdinalIgnoreCase)) { return true; } if (!SmartChestConfig.StaticBlockInputWhenTyping) { return true; } if (!SmartChestUI.ShouldBlockGameInput(_staticUI)) { return true; } __result = false; return false; } private static bool OnPlayerInputGetButtonDown_Prefix(string button, ref bool __result) { if (!SmartChestConfig.StaticBlockInputWhenTyping) { return true; } if (!SmartChestUI.ShouldBlockGameInput(_staticUI)) { return true; } if (button == "UICancel" || button == "Close") { __result = false; return false; } return true; } private static bool OnChestEndInteract_Prefix(Chest __instance, int interactType) { if ((Object)(object)CurrentInteractingChest == (Object)(object)__instance && (Object)(object)_staticUI != (Object)null && _staticUI.IsVisible && !_staticUI.IsEmbedded) { return false; } if ((Object)(object)CurrentInteractingChest == (Object)(object)__instance) { CurrentInteractingChest = null; _staticUI?.Hide(); } return true; } private static void OnUIHandlerOpenUI_Postfix(object __instance, GameObject ui, Transform parent, bool playAudio, bool animate, object externalUIHandler) { if ((Object)(object)ui == (Object)null || externalUIHandler == null) { return; } Chest val = (Chest)((externalUIHandler is Chest) ? externalUIHandler : null); if (val == null) { return; } try { _pendingChestPanel = ui; _pendingChest = val; _pendingChestButtonFrames = 3; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)$"[SenpaisChest] Scheduled embedded panel (will add in 3 frames) - UI active: {ui.activeInHierarchy}"); } } catch (Exception ex) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("[SenpaisChest] Failed to schedule embedded panel: " + ex.Message)); } } } internal static void ProcessPendingChestButton() { //IL_014b: Unknown result type (might be due to invalid IL or missing references) if (_pendingChestButtonFrames <= 0 || (Object)(object)_pendingChestPanel == (Object)null || (Object)(object)_pendingChest == (Object)null) { return; } _pendingChestButtonFrames--; if (_pendingChestButtonFrames != 0) { return; } GameObject pendingChestPanel = _pendingChestPanel; Chest pendingChest = _pendingChest; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)$"[SenpaisChest] Processing pending panel: panel={(Object)(object)pendingChestPanel != (Object)null}, active={((pendingChestPanel != null) ? new bool?(pendingChestPanel.activeInHierarchy) : ((bool?)null))}, chest={(Object)(object)pendingChest != (Object)null}"); } if ((Object)(object)pendingChestPanel == (Object)null || !pendingChestPanel.activeInHierarchy) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogInfo((object)$"[SenpaisChest] Skip add panel: panel null or inactive (panel={(Object)(object)pendingChestPanel != (Object)null}, active={((pendingChestPanel != null) ? new bool?(pendingChestPanel.activeInHierarchy) : ((bool?)null))})"); } _pendingChestPanel = null; _pendingChest = null; return; } Canvas componentInParent = pendingChestPanel.GetComponentInParent<Canvas>(); ManualLogSource log3 = Log; if (log3 != null) { log3.LogInfo((object)$"[SenpaisChest] Chest UI state - Canvas: {(Object)(object)componentInParent != (Object)null}, Canvas active: {((componentInParent != null) ? new bool?(((Component)componentInParent).gameObject.activeInHierarchy) : ((bool?)null))}, RenderMode: {((componentInParent != null) ? new RenderMode?(componentInParent.renderMode) : ((RenderMode?)null))}"); } try { AddSenpaisChestNotePanel(pendingChestPanel, pendingChest); } catch (Exception ex) { ManualLogSource log4 = Log; if (log4 != null) { log4.LogWarning((object)("[SenpaisChest] Failed to add embedded panel: " + ex.Message + "\n" + ex.StackTrace)); } } finally { _pendingChestPanel = null; _pendingChest = null; } } private static void AddSenpaisChestNotePanel(GameObject chestUiRoot, Chest chest) { //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Expected O, but got Unknown //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)chestUiRoot == (Object)null || !chestUiRoot.activeInHierarchy) { ManualLogSource log = Log; if (log != null) { log.LogDebug((object)$"[SenpaisChest] Cannot add panel: chestUiRoot is null or inactive (null={(Object)(object)chestUiRoot == (Object)null}, active={((chestUiRoot != null) ? new bool?(chestUiRoot.activeInHierarchy) : ((bool?)null))})"); } return; } Transform val = chestUiRoot.transform.Find("SenpaisChest_ConfigPanel"); if ((Object)(object)val != (Object)null) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogInfo((object)$"[SenpaisChest] Panel already exists, reusing - active: {((Component)val).gameObject.activeInHierarchy}"); } SmartChestUI uI = GetUI(); if ((Object)(object)uI != (Object)null) { uI.ShowNoteForChest(chest, ((Component)val).gameObject); } return; } if ((Object)(object)chestUiRoot.GetComponentInParent<Canvas>() == (Object)null) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogWarning((object)"[SenpaisChest] Chest UI root is not under a Canvas, cannot create embedded panel"); } return; } Transform transform = chestUiRoot.transform; try { GameObject val2 = new GameObject("SenpaisChest_ConfigPanel"); val2.transform.SetParent(transform, false); val2.transform.SetAsLastSibling(); val2.SetActive(true); RectTransform val3 = val2.AddComponent<RectTransform>(); val3.anchorMin = new Vector2(0.5f, 0f); val3.anchorMax = new Vector2(0.5f, 0f); val3.pivot = new Vector2(0.5f, 0f); val3.anchoredPosition = Vector2.zero; val3.sizeDelta = new Vector2(340f, 34f); ManualLogSource log4 = Log; if (log4 != null) { log4.LogInfo((object)$"[SenpaisChest] Panel RectTransform - anchorMin={val3.anchorMin}, anchorMax={val3.anchorMax}, sizeDelta={val3.sizeDelta}, rect={val3.rect}"); } CanvasGroup obj = val2.AddComponent<CanvasGroup>(); obj.blocksRaycasts = false; obj.interactable = false; obj.alpha = 1f; LayoutRebuilder.ForceRebuildLayoutImmediate(val3); Rect rect = val3.rect; if (!(((Rect)(ref rect)).width <= 0f)) { rect = val3.rect; if (!(((Rect)(ref rect)).height <= 0f)) { SmartChestUI uI2 = GetUI(); if ((Object)(object)uI2 != (Object)null) { uI2.ShowNoteForChest(chest, val2); ManualLogSource log5 = Log; if (log5 != null) { log5.LogInfo((object)$"[SenpaisChest] Added note panel (Press F9 to configure) - chestUiRoot active: {chestUiRoot.activeInHierarchy}, panel active: {val2.activeInHierarchy}, rect: {val3.rect}"); } } else { ManualLogSource log6 = Log; if (log6 != null) { log6.LogWarning((object)"[SenpaisChest] UI component is null, cannot show embedded panel"); } } return; } } ManualLogSource log7 = Log; if (log7 != null) { rect = val3.rect; object arg = ((Rect)(ref rect)).width; rect = val3.rect; log7.LogWarning((object)$"[SenpaisChest] Panel rect is invalid (width={arg}, height={((Rect)(ref rect)).height}), waiting one more frame"); } _pendingChestPanel = chestUiRoot; _pendingChest = chest; _pendingChestButtonFrames = 1; } catch (Exception arg2) { ManualLogSource log8 = Log; if (log8 != null) { log8.LogError((object)$"[SenpaisChest] Exception creating embedded panel: {arg2}"); } } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { bool flag = ((Scene)(ref scene)).name == "MainMenu" || ((Scene)(ref scene)).name == "Menu"; if (flag && !_wasInMenuScene) { Log.LogInfo((object)"Returned to menu, saving data..."); _staticSaveSystem?.Save(); _gameplaySessionCharacter = null; } else if (!flag) { string currentCharacterName = GetCurrentCharacterName(); if (!string.IsNullOrEmpty(currentCharacterName)) { if (string.Equals(_gameplaySessionCharacter, currentCharacterName, StringComparison.Ordinal)) { ManualLogSource log = Log; if (log != null) { log.LogDebug((object)("[SenpaisChest] Skip disk reload on scene '" + ((Scene)(ref scene)).name + "' — '" + currentCharacterName + "' already active")); } } else { _staticManager?.SetCharacterName(currentCharacterName); SmartChestSaveData data = _staticSaveSystem?.Load(currentCharacterName); _staticManager?.LoadData(data); _gameplaySessionCharacter = currentCharacterName; ManualLogSource log2 = Log; if (log2 != null) { log2.LogDebug((object)("[SenpaisChest] Loaded smart chest data from disk for '" + currentCharacterName + "' on scene load")); } } } } _wasInMenuScene = flag; } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; _museumTodoIntegration?.Dispose(); _museumTodoIntegration = null; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"Plugin OnDestroy called — static references preserved"); } _staticSaveSystem?.Save(); } private void OnApplicationQuit() { _applicationQuitting = true; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"Application quitting — saving data"); } _staticSaveSystem?.Save(); } internal static SmartChestManager GetManager() { return _staticManager; } internal static SmartChestSaveSystem GetSaveSystem() { return _staticSaveSystem; } internal static string GetCurrentCharacterName() { try { CharacterData currentCharacter = GameSave.CurrentCharacter; return (currentCharacter != null) ? currentCharacter.characterName : null; } catch (Exception ex) { ManualLogSource log = Log; if (log != null) { log.LogDebug((object)("[SenpaisChest] GetCurrentCharacterName: " + ex.Message)); } return null; } } internal static SmartChestUI GetUI() { return _staticUI; } internal static SmartChestConfig GetConfig() { return _staticConfig; } internal static MuseumTodoIntegration GetMuseumTodoIntegration() { return _museumTodoIntegration; } } public class SmartChestPersistentRunner : MonoBehaviour { private float _scanTimer; private float _autoSaveTimer; private const float AUTO_SAVE_INTERVAL = 300f; private int _lastCountdownSecond = -1; private void Update() { Plugin.ProcessPendingChestButton(); SmartChestConfig config = Plugin.GetConfig(); SmartChestManager manager = Plugin.GetManager(); if (config == null || manager == null) { return; } float unscaledDeltaTime = Time.unscaledDeltaTime; float scanInterval = config.GetScanInterval(); _scanTimer += unscaledDeltaTime; int num = (int)Math.Ceiling(scanInterval - _scanTimer); if (num >= 1 && num <= 10 && num != _lastCountdownSecond) { _lastCountdownSecond = num; if (SmartChestConfig.StaticEnableScanCountdownDebug) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)$"[Scan] Next scan in {num}..."); } } } if (_scanTimer >= scanInterval) { _scanTimer = 0f; _lastCountdownSecond = -1; try { manager.ExecuteScan(config.MaxItemsPerScan.Value, config.EnableNotifications.Value); Plugin.GetMuseumTodoIntegration()?.OnScanComplete(); } catch (Exception arg) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogError((object)$"Error during scan: {arg}"); } } } _autoSaveTimer += unscaledDeltaTime; if (_autoSaveTimer >= 300f) { _autoSaveTimer = 0f; if (manager.IsDirty) { Plugin.GetSaveSystem()?.Save(); } } DetectHotkey(config); } private void DetectHotkey(SmartChestConfig config) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (TextInputFocusGuard.ShouldDeferModHotkeys(Plugin.Log)) { return; } KeyCode staticToggleKey = SmartChestConfig.StaticToggleKey; bool staticRequireCtrl = SmartChestConfig.StaticRequireCtrl; if (Input.GetKeyDown(staticToggleKey) && (!staticRequireCtrl || Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305))) { SmartChestUI uI = Plugin.GetUI(); if (!((Object)(object)uI == (Object)null) && (Object)(object)Plugin.CurrentInteractingChest != (Object)null) { uI.ToggleForChest(Plugin.CurrentInteractingChest); } } } private void OnDestroy() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); string text = (((Scene)(ref activeScene)).name ?? string.Empty)