Decompiled source of HavensMirror v1.0.0

HavensMirror.dll

Decompiled 3 days ago
using 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.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using HavensMirror.Config;
using HavensMirror.Dialogue;
using HavensMirror.Gallery;
using HavensMirror.Hotkeys;
using HavensMirror.Look;
using Microsoft.CodeAnalysis;
using SunhavenMods.Shared;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[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("HavensMirror")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+f51c0a9c6c1e97b11c7ea02b854111ea865a0fb4")]
[assembly: AssemblyProduct("HavensMirror")]
[assembly: AssemblyTitle("HavensMirror")]
[assembly: AssemblyVersion("1.0.0.0")]
[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.09.21";
	}
	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.09.21";
			}
			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.09.21",
				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 const string HavensMirror = "com.azraelgodking.havensmirror";
	}
	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 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 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";

		private static readonly char[] InvalidFileNameChars = BuildInvalidFileNameChars();

		private static char[] BuildInvalidFileNameChars()
		{
			char[] array = new char[41];
			for (int i = 0; i < 32; i++)
			{
				array[i] = (char)i;
			}
			"\"<>|:*?\\/".CopyTo(0, array, 32, 9);
			return array;
		}

		public static string SanitizeFileName(string name, string fallback = "unknown")
		{
			if (name == null)
			{
				return fallback;
			}
			char[] invalidFileNameChars = InvalidFileNameChars;
			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)
			{
				WriteAllTextDurable(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)
			{
				WriteAllBytesDurable(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";
			string text2 = filePath + backupSuffix;
			bool flag = false;
			try
			{
				writeTemp(text);
				if (File.Exists(filePath))
				{
					if (File.Exists(text2))
					{
						File.Delete(text2);
					}
					File.Move(filePath, text2);
					flag = true;
				}
				File.Move(text, filePath);
				TryDelete(text);
				return true;
			}
			catch (Exception)
			{
				if (flag && !File.Exists(filePath) && File.Exists(text2))
				{
					try
					{
						File.Move(text2, filePath);
					}
					catch
					{
					}
				}
				if (deleteTempInFinally && !flag)
				{
					TryDelete(text);
				}
				return false;
			}
		}

		private static void WriteAllTextDurable(string path, string content)
		{
			using FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
			using StreamWriter streamWriter = new StreamWriter(fileStream);
			streamWriter.Write(content);
			streamWriter.Flush();
			fileStream.Flush(flushToDisk: true);
		}

		private static void WriteAllBytesDurable(string path, byte[] content)
		{
			using FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
			fileStream.Write(content, 0, content.Length);
			fileStream.Flush(flushToDisk: true);
		}

		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 GameSaveCharacterName
	{
		public static string TryGetCurrent(string fallback = null, Action<string> logWarning = null)
		{
			try
			{
				Type type = AccessTools.TypeByName("Wish.GameSave");
				if (type == null)
				{
					return fallback;
				}
				object obj = AccessTools.Property(type, "CurrentCharacter")?.GetValue(null);
				if (obj != null)
				{
					string text = ReadCharacterName(obj);
					if (!string.IsNullOrEmpty(text))
					{
						return text;
					}
				}
				object obj2 = AccessTools.Property(type, "Instance")?.GetValue(null);
				if (obj2 != null)
				{
					object obj3 = AccessTools.Property(type, "CurrentSave")?.GetValue(obj2);
					if (obj3 != null)
					{
						object obj4 = AccessTools.Property(obj3.GetType(), "characterData")?.GetValue(obj3);
						if (obj4 != null)
						{
							string text2 = ReadCharacterName(obj4);
							if (!string.IsNullOrEmpty(text2))
							{
								return text2;
							}
						}
					}
				}
			}
			catch (Exception ex)
			{
				logWarning?.Invoke(ex.Message);
			}
			return fallback;
		}

		private static string ReadCharacterName(object characterOrData)
		{
			return AccessTools.Property(characterOrData.GetType(), "characterName")?.GetValue(characterOrData) as string;
		}
	}
	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 abstract class PersistentRunnerBase : MonoBehaviour
	{
		private bool _wasInGame;

		private float _lastHeartbeat;

		private string _lastSceneName = "";

		private float _lastSceneCheckTime;

		private const float SceneCheckInterval = 0.5f;

		protected virtual float HeartbeatInterval => 0f;

		protected virtual string RunnerName => ((object)this).GetType().Name;

		protected virtual void OnUpdate()
		{
		}

		protected virtual void OnMenuTransition()
		{
		}

		protected virtual void OnGameTransition()
		{
		}

		protected virtual void Log(string message)
		{
		}

		protected virtual void LogWarning(string message)
		{
		}

		public static T CreateRunner<T>() where T : PersistentRunnerBase
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			GameObject val = new GameObject("[" + typeof(T).Name + "]")
			{
				hideFlags = (HideFlags)61
			};
			Object.DontDestroyOnLoad((Object)val);
			SceneRootSurvivor.TryRegisterPersistentRunnerGameObject(val);
			return val.AddComponent<T>();
		}

		protected virtual void Awake()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			Scene activeScene = SceneManager.GetActiveScene();
			_lastSceneName = ((Scene)(ref activeScene)).name;
			_wasInGame = !SceneHelpers.IsMenuScene(_lastSceneName);
			Log("[" + RunnerName + "] Initialized in scene: " + _lastSceneName);
		}

		protected virtual void Update()
		{
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			if (HeartbeatInterval > 0f)
			{
				_lastHeartbeat += Time.deltaTime;
				if (_lastHeartbeat >= HeartbeatInterval)
				{
					_lastHeartbeat = 0f;
					Log("[" + RunnerName + "] Heartbeat - still alive");
				}
			}
			float unscaledTime = Time.unscaledTime;
			if (unscaledTime - _lastSceneCheckTime >= 0.5f)
			{
				_lastSceneCheckTime = unscaledTime;
				Scene activeScene = SceneManager.GetActiveScene();
				string name = ((Scene)(ref activeScene)).name;
				if (name != _lastSceneName)
				{
					_lastSceneName = name;
					HandleSceneChange(name);
				}
			}
			try
			{
				OnUpdate();
			}
			catch (Exception ex)
			{
				LogWarning("[" + RunnerName + "] Error in OnUpdate: " + ex.Message);
			}
		}

		private void HandleSceneChange(string sceneName)
		{
			bool flag = SceneHelpers.IsMenuScene(sceneName);
			if (_wasInGame && flag)
			{
				Log("[" + RunnerName + "] Menu transition detected");
				try
				{
					OnMenuTransition();
				}
				catch (Exception ex)
				{
					LogWarning("[" + RunnerName + "] Error in OnMenuTransition: " + ex.Message);
				}
			}
			else if (!_wasInGame && !flag)
			{
				Log("[" + RunnerName + "] Game transition detected");
				try
				{
					OnGameTransition();
				}
				catch (Exception ex2)
				{
					LogWarning("[" + RunnerName + "] Error in OnGameTransition: " + ex2.Message);
				}
			}
			_wasInGame = !flag;
		}

		protected virtual 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).ToLowerInvariant();
			if (!Application.isPlaying || text.Contains("menu") || text.Contains("title"))
			{
				Log("[" + RunnerName + "] OnDestroy during app quit/menu unload (expected).");
			}
			else
			{
				LogWarning("[" + RunnerName + "] OnDestroy outside quit/menu (unexpected).");
			}
		}
	}
	public static class SceneHelpers
	{
		private static readonly string[] MenuScenePatterns = new string[3] { "menu", "title", "bootstrap" };

		private static readonly string[] ExactMenuScenes = new string[2] { "MainMenu", "Bootstrap" };

		public static bool IsMenuScene(string sceneName)
		{
			if (string.IsNullOrEmpty(sceneName))
			{
				return true;
			}
			string[] exactMenuScenes = ExactMenuScenes;
			foreach (string text in exactMenuScenes)
			{
				if (sceneName == text)
				{
					return true;
				}
			}
			string text2 = sceneName.ToLowerInvariant();
			exactMenuScenes = MenuScenePatterns;
			foreach (string value in exactMenuScenes)
			{
				if (text2.Contains(value))
				{
					return true;
				}
			}
			return false;
		}

		public static bool IsCurrentSceneMenu()
		{
			//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();
			return IsMenuScene(((Scene)(ref activeScene)).name);
		}

		public static bool IsInGame()
		{
			return !IsCurrentSceneMenu();
		}

		public static string GetCurrentSceneName()
		{
			//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();
			return ((Scene)(ref activeScene)).name;
		}

		public static bool IsMainMenu()
		{
			//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();
			return ((Scene)(ref activeScene)).name == "MainMenu";
		}
	}
	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));
			}
		}
	}
}
namespace HavensMirror
{
	[BepInPlugin("com.azraelgodking.havensmirror", "Haven's Mirror", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		private static Harmony _harmony;

		private static MirrorPersistentRunner _runner;

		private bool _applicationQuitting;

		public static ManualLogSource Log { get; private set; }

		public static SpriteShelf Shelf { get; private set; }

		private void Awake()
		{
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			ConfigFile val = CreateNamedConfig();
			ConfigFileHelper.ReplacePluginConfig((BaseUnityPlugin)(object)this, val, (Action<string>)Log.LogWarning);
			MirrorOptions.Bind(val);
			Shelf = new SpriteShelf();
			if (MirrorOptions.Enabled == null || !MirrorOptions.Enabled.Value)
			{
				Log.LogInfo((object)"Haven's Mirror disabled in config.");
				ModDiagnostics.LogModStartup(Log, "com.azraelgodking.havensmirror", "Haven's Mirror", "1.0.0", "standalone", "disabled", false);
				return;
			}
			GalleryPaths.EnsureStarterLayout(delegate(string msg)
			{
				Log.LogInfo((object)msg);
			});
			int num = SaveSlotFolderSync.EnsureCharacterFolders();
			if (num > 0)
			{
				Log.LogInfo((object)$"[Gallery] Created {num} character folder(s) from saves.");
			}
			_harmony = new Harmony("com.azraelgodking.havensmirror");
			try
			{
				DialogueMirrorBinder.Apply(_harmony);
				NpcLookPatches.Apply(_harmony);
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Harmony setup failed (game version drift?): {arg}");
			}
			if ((Object)(object)_runner == (Object)null)
			{
				_runner = PersistentRunnerBase.CreateRunner<MirrorPersistentRunner>();
			}
			ReloadGallery(notify: false);
			VersionChecker.CheckForUpdate("com.azraelgodking.havensmirror", "1.0.0", Log, delegate(VersionChecker.VersionCheckResult result)
			{
				result.NotifyUpdateAvailable(Log);
			});
			Log.LogInfo((object)("Haven's Mirror v1.0.0 loaded. Gallery: " + GalleryPaths.GalleryRoot));
			ModDiagnostics.LogModStartup(Log, "com.azraelgodking.havensmirror", "Haven's Mirror", "1.0.0", ModHealthIntegrationSummary.Build(("DevTools", "com.azraelgodking.havendevtools")), "startup", false);
		}

		public static void ReloadGallery(bool notify)
		{
			if (Shelf == null)
			{
				Shelf = new SpriteShelf();
			}
			SaveSlotFolderSync.EnsureCharacterFolders();
			string characterName = TryReadCharacterName();
			string text = Shelf.LoadForCharacter(characterName);
			if (Shelf.HasAny)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogInfo((object)("[Gallery] " + text));
				}
			}
			else
			{
				ManualLogSource log2 = Log;
				if (log2 != null)
				{
					log2.LogDebug((object)("[Gallery] " + text));
				}
			}
			if (notify)
			{
				TryNotify("[Haven's Mirror] " + text);
			}
		}

		private static string TryReadCharacterName()
		{
			return GameSaveCharacterName.TryGetCurrent(null, delegate(string msg)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogDebug((object)("[Gallery] Character name resolve failed: " + msg));
				}
			});
		}

		private static void TryNotify(string message)
		{
			try
			{
				Type type = ReflectionHelper.FindWishType("NotificationStack");
				object obj = ((type != null) ? ReflectionHelper.GetSingletonInstance(type) : null);
				if (obj == null)
				{
					return;
				}
				MethodInfo methodInfo = AccessTools.Method(type, "SendNotification", new Type[5]
				{
					typeof(string),
					typeof(int),
					typeof(int),
					typeof(bool),
					typeof(bool)
				}, (Type[])null) ?? AccessTools.Method(type, "SendNotification", (Type[])null, (Type[])null);
				if (methodInfo == null)
				{
					return;
				}
				ParameterInfo[] parameters = methodInfo.GetParameters();
				if (parameters.Length == 1 && parameters[0].ParameterType == typeof(string))
				{
					methodInfo.Invoke(obj, new object[1] { message });
					return;
				}
				object[] array = new object[parameters.Length];
				array[0] = message;
				for (int i = 1; i < parameters.Length; i++)
				{
					Type parameterType = parameters[i].ParameterType;
					if (parameterType == typeof(int))
					{
						array[i] = 0;
					}
					else if (parameterType == typeof(bool))
					{
						array[i] = false;
					}
					else if (parameters[i].HasDefaultValue)
					{
						array[i] = parameters[i].DefaultValue;
					}
					else
					{
						array[i] = GetDefault(parameterType);
					}
				}
				methodInfo.Invoke(obj, array);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogDebug((object)("[Notify] " + ex.Message));
				}
			}
		}

		private static object GetDefault(Type type)
		{
			if (type == typeof(bool))
			{
				return false;
			}
			if (type == typeof(int))
			{
				return 0;
			}
			if (type == typeof(float))
			{
				return 0f;
			}
			if (type.IsValueType)
			{
				return Activator.CreateInstance(type);
			}
			return null;
		}

		private static ConfigFile CreateNamedConfig()
		{
			return ConfigFileHelper.CreateNamedConfig("com.azraelgodking.havensmirror", "HavensMirror.cfg", delegate(string msg)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogWarning((object)msg);
				}
			});
		}

		private void OnApplicationQuit()
		{
			_applicationQuitting = true;
		}

		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;
			string text2 = text.ToLowerInvariant();
			if (_applicationQuitting || !Application.isPlaying || text2.Contains("menu") || text2.Contains("title"))
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogInfo((object)("[Lifecycle] Plugin OnDestroy during expected teardown (scene: " + text + ")"));
				}
				Shelf?.Dispose();
				Shelf = null;
				Harmony harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
				_harmony = null;
			}
			else
			{
				ManualLogSource log2 = Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("[Lifecycle] Plugin OnDestroy outside expected teardown (scene: " + text + ") — keeping Harmony patches, Shelf, and MirrorPersistentRunner"));
				}
			}
		}
	}
	public static class PluginInfo
	{
		public const string PLUGIN_GUID = "com.azraelgodking.havensmirror";

		public const string PLUGIN_NAME = "Haven's Mirror";

		public const string PLUGIN_VERSION = "1.0.0";
	}
}
namespace HavensMirror.Look
{
	public static class ForcedLookResolver
	{
		public static bool TryParseForced(out LookSlot slot)
		{
			slot = LookSlot.Spring;
			string text = MirrorOptions.ForcedLook?.Value;
			if (string.IsNullOrWhiteSpace(text))
			{
				return false;
			}
			string text2 = text.Trim();
			foreach (LookSlot value in Enum.GetValues(typeof(LookSlot)))
			{
				if (string.Equals(value.ToString(), text2, StringComparison.OrdinalIgnoreCase))
				{
					slot = value;
					return true;
				}
			}
			switch (text2.ToLowerInvariant())
			{
			case "fall":
				slot = LookSlot.Autumn;
				return true;
			case "wedding":
			case "ceremony":
			case "marriage":
				slot = LookSlot.Vows;
				return true;
			case "swim":
			case "swimsuit":
			case "beach":
				slot = LookSlot.Shore;
				return true;
			case "spooky":
			case "halloween":
				slot = LookSlot.Costume;
				return true;
			case "default":
			case "normal":
				slot = LookSlot.Spring;
				return true;
			default:
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[Look] Unknown ForcedLook '" + text + "' — ignored."));
				}
				return false;
			}
			}
		}

		public static LookSlot ResolveActiveLook(bool vows, bool shore, bool costume)
		{
			if (TryParseForced(out var slot))
			{
				if (slot != LookSlot.Vows && slot != LookSlot.Shore)
				{
					_ = 6;
				}
				return slot;
			}
			if (vows)
			{
				return LookSlot.Vows;
			}
			if (shore)
			{
				return LookSlot.Shore;
			}
			if (costume)
			{
				return LookSlot.Costume;
			}
			return ResolveSeasonalLook();
		}

		public static LookSlot ResolveSeasonalLook()
		{
			if (TryParseForced(out var slot) && slot != LookSlot.Vows && slot != LookSlot.Shore && slot != LookSlot.Costume)
			{
				return slot;
			}
			string text = TryReadSeasonName();
			if (string.IsNullOrEmpty(text))
			{
				return LookSlot.Spring;
			}
			switch (text.ToLowerInvariant())
			{
			case "summer":
				return LookSlot.Summer;
			case "fall":
			case "autumn":
				return LookSlot.Autumn;
			case "winter":
				return LookSlot.Winter;
			default:
				return LookSlot.Spring;
			}
		}

		public static int ResolveSeasonAnimatorIndex()
		{
			return ResolveSeasonalLook() switch
			{
				LookSlot.Summer => 1, 
				LookSlot.Autumn => 2, 
				LookSlot.Winter => 3, 
				_ => 0, 
			};
		}

		public static void ApplyForcedFlags(ref bool vows, ref bool shore, ref bool costume)
		{
			if (TryParseForced(out var slot))
			{
				switch (slot)
				{
				case LookSlot.Vows:
					vows = true;
					shore = false;
					costume = false;
					break;
				case LookSlot.Shore:
					shore = true;
					vows = false;
					costume = false;
					break;
				case LookSlot.Costume:
					costume = true;
					vows = false;
					shore = false;
					break;
				}
			}
		}

		private static string TryReadSeasonName()
		{
			try
			{
				Type type = ReflectionHelper.FindWishType("DayCycle");
				if (type != null)
				{
					object singletonInstance = ReflectionHelper.GetSingletonInstance(type);
					if (singletonInstance != null)
					{
						object obj = (AccessToolsProp(singletonInstance.GetType(), "Season") ?? AccessToolsProp(singletonInstance.GetType(), "season"))?.GetValue(singletonInstance, null);
						if (obj != null)
						{
							return obj.ToString();
						}
					}
				}
				Type type2 = ReflectionHelper.FindWishType("GameDate");
				if (type2 != null)
				{
					FieldInfo fieldInfo = AccessTools.Field(type2, "Season") ?? AccessTools.Field(type2, "season");
					if (fieldInfo != null && fieldInfo.IsStatic)
					{
						object value = fieldInfo.GetValue(null);
						if (value != null)
						{
							return value.ToString();
						}
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogDebug((object)("[Look] Season read failed: " + ex.Message));
				}
			}
			return null;
		}

		private static PropertyInfo AccessToolsProp(Type type, string name)
		{
			return AccessTools.Property(type, name);
		}
	}
	public static class NpcLookPatches
	{
		public static void Apply(Harmony harmony)
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Expected O, but got Unknown
			Type type = ReflectionHelper.FindWishType("NPCAI");
			if (type == null)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)"[Reflect] Wish.NPCAI not found — seasonal outfit override inactive.");
				}
				return;
			}
			MethodInfo methodInfo = AccessTools.Method(type, "LoadSeasonAnimators", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)"[Reflect] NPCAI.LoadSeasonAnimators not found.");
				}
				return;
			}
			try
			{
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(NpcLookPatches), "LoadSeasonAnimatorsPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				ManualLogSource log3 = Plugin.Log;
				if (log3 != null)
				{
					log3.LogInfo((object)"Patched NPCAI.LoadSeasonAnimators for ForcedLook season override.");
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log4 = Plugin.Log;
				if (log4 != null)
				{
					log4.LogWarning((object)("[Reflect] NPCAI.LoadSeasonAnimators patch failed: " + ex.Message));
				}
			}
		}

		private static bool LoadSeasonAnimatorsPrefix(object __instance)
		{
			if (MirrorOptions.Enabled == null || !MirrorOptions.Enabled.Value)
			{
				return true;
			}
			if (!ForcedLookResolver.TryParseForced(out var slot))
			{
				return true;
			}
			if (slot == LookSlot.Vows || slot == LookSlot.Shore || slot == LookSlot.Costume)
			{
				return true;
			}
			try
			{
				Type type = __instance.GetType();
				FieldInfo obj = AccessTools.Field(type, "seasonAnimatorLoader") ?? AccessTools.Field(type, "_seasonAnimatorLoader");
				FieldInfo fieldInfo = AccessTools.Field(type, "hasSeasonalSprites") ?? AccessTools.Field(type, "_hasSeasonalSprites");
				FieldInfo layerField = AccessTools.Field(type, "currentAnimatorLayer") ?? AccessTools.Field(type, "_currentAnimatorLayer");
				FieldInfo facingField = AccessTools.Field(type, "_facingDirection") ?? AccessTools.Field(type, "facingDirection");
				FieldInfo meshField = AccessTools.Field(type, "_meshGenerator") ?? AccessTools.Field(type, "meshGenerator");
				FieldInfo fieldInfo2 = AccessTools.Field(type, "_isBSDSluggedNPC") ?? AccessTools.Field(type, "isBSDSluggedNPC");
				bool flag = default(bool);
				int num;
				if (fieldInfo2 != null)
				{
					object value = fieldInfo2.GetValue(__instance);
					if (value is bool)
					{
						flag = (bool)value;
						num = 1;
					}
					else
					{
						num = 0;
					}
				}
				else
				{
					num = 0;
				}
				if (((uint)num & (flag ? 1u : 0u)) != 0)
				{
					AccessTools.Method(type, "GetSluggedAnimator", (Type[])null, (Type[])null)?.Invoke(__instance, null);
				}
				object obj2 = obj?.GetValue(__instance);
				if (obj2 == null)
				{
					return false;
				}
				int num2 = 0;
				bool flag2 = default(bool);
				int num3;
				if (fieldInfo != null)
				{
					object value = fieldInfo.GetValue(__instance);
					if (value is bool)
					{
						flag2 = (bool)value;
						num3 = 1;
					}
					else
					{
						num3 = 0;
					}
				}
				else
				{
					num3 = 0;
				}
				if (((uint)num3 & (flag2 ? 1u : 0u)) != 0)
				{
					num2 = ForcedLookResolver.ResolveSeasonAnimatorIndex();
				}
				MethodInfo methodInfo = AccessTools.Method(obj2.GetType(), "LoadAnimator", (Type[])null, (Type[])null);
				if (methodInfo == null)
				{
					return true;
				}
				object obj3 = CreateAnimatorCallback(__instance, layerField, facingField, meshField);
				if (obj3 != null && methodInfo.GetParameters().Length >= 2)
				{
					methodInfo.Invoke(obj2, new object[2] { num2, obj3 });
				}
				else
				{
					if (methodInfo.GetParameters().Length != 1)
					{
						return true;
					}
					methodInfo.Invoke(obj2, new object[1] { num2 });
				}
				return false;
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogDebug((object)("[Look] LoadSeasonAnimatorsPrefix fallback to vanilla: " + ex.Message));
				}
				return true;
			}
		}

		private static object CreateAnimatorCallback(object npc, FieldInfo layerField, FieldInfo facingField, FieldInfo meshField)
		{
			try
			{
				Type npcType = npc.GetType();
				MethodInfo delay = AccessTools.Method(npcType, "DelayOneFrame", new Type[1] { typeof(Action) }, (Type[])null);
				if (delay == null)
				{
					return null;
				}
				Action body = delegate
				{
					try
					{
						object obj2 = (AccessTools.Property(npcType, "animator") ?? AccessTools.Property(npcType, "Animator"))?.GetValue(npc, null);
						if (obj2 != null)
						{
							object? obj3 = AccessTools.Property(obj2.GetType(), "gameObject")?.GetValue(obj2, null);
							GameObject val = (GameObject)((obj3 is GameObject) ? obj3 : null);
							object obj4 = AccessTools.Property(obj2.GetType(), "runtimeAnimatorController")?.GetValue(obj2, null);
							if (!((Object)(object)val == (Object)null) && val.activeInHierarchy && obj4 != null)
							{
								int num = 0;
								PropertyInfo propertyInfo = AccessTools.Property(obj2.GetType(), "layerCount");
								if (propertyInfo != null)
								{
									num = (int)propertyInfo.GetValue(obj2, null);
								}
								int num2 = 0;
								if (layerField != null && layerField.GetValue(npc) is int num3)
								{
									num2 = num3;
								}
								MethodInfo methodInfo = AccessTools.Method(obj2.GetType(), "SetLayerWeight", new Type[2]
								{
									typeof(int),
									typeof(float)
								}, (Type[])null);
								if (methodInfo != null)
								{
									for (int i = 0; i < num; i++)
									{
										methodInfo.Invoke(obj2, new object[2]
										{
											i,
											(i == num2) ? 1f : 0f
										});
									}
								}
								object mesh = meshField?.GetValue(npc);
								MethodInfo setDefault = ((mesh != null) ? AccessTools.Method(mesh.GetType(), "SetDefault", (Type[])null, (Type[])null) : null);
								Action action = delegate
								{
									try
									{
										setDefault?.Invoke(mesh, null);
									}
									catch
									{
									}
								};
								if (delay != null)
								{
									delay.Invoke(npc, new object[1] { action });
								}
								object obj5 = facingField?.GetValue(npc);
								MethodInfo methodInfo2 = AccessTools.Method(obj2.GetType(), "SetInteger", new Type[2]
								{
									typeof(string),
									typeof(int)
								}, (Type[])null);
								if (methodInfo2 != null && obj5 != null)
								{
									methodInfo2.Invoke(obj2, new object[2]
									{
										"Direction",
										Convert.ToInt32(obj5)
									});
								}
							}
						}
					}
					catch (Exception ex)
					{
						ManualLogSource log = Plugin.Log;
						if (log != null)
						{
							log.LogDebug((object)("[Look] Animator callback: " + ex.Message));
						}
					}
				};
				return (Action)delegate
				{
					try
					{
						delay.Invoke(npc, new object[1] { body });
					}
					catch (Exception ex)
					{
						ManualLogSource log = Plugin.Log;
						if (log != null)
						{
							log.LogDebug((object)("[Look] DelayOneFrame: " + ex.Message));
						}
					}
				};
			}
			catch
			{
				return null;
			}
		}
	}
}
namespace HavensMirror.Hotkeys
{
	public sealed class GalleryFolderSyncLoop : MonoBehaviour
	{
		private const float IntervalSeconds = 5f;

		private float _nextScan;

		private int _passes;

		private void OnEnable()
		{
			_nextScan = 0f;
			_passes = 0;
		}

		private void Update()
		{
			if (MirrorOptions.Enabled == null || !MirrorOptions.Enabled.Value || Time.unscaledTime < _nextScan)
			{
				return;
			}
			_nextScan = Time.unscaledTime + 5f;
			_passes++;
			int num = SaveSlotFolderSync.EnsureCharacterFolders();
			if (num > 0)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)$"[Gallery] Save-slot sync created {num} folder(s).");
				}
			}
			if (_passes > 24 && num == 0)
			{
				_nextScan = Time.unscaledTime + 30f;
			}
		}
	}
	public sealed class GalleryReloadLoop : MonoBehaviour
	{
		private void Update()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			if (MirrorOptions.Enabled == null || !MirrorOptions.Enabled.Value)
			{
				return;
			}
			ConfigEntry<KeyCode> reloadKey = MirrorOptions.ReloadKey;
			KeyCode val = (KeyCode)((reloadKey != null) ? ((int)reloadKey.Value) : 0);
			if ((int)val != 0 && Input.GetKeyDown(val))
			{
				ConfigEntry<KeyCode> reloadModifier = MirrorOptions.ReloadModifier;
				KeyCode val2 = (KeyCode)((reloadModifier != null) ? ((int)reloadModifier.Value) : 0);
				if ((int)val2 == 0 || Input.GetKey(val2))
				{
					Plugin.ReloadGallery(MirrorOptions.NotifyOnReload?.Value ?? true);
				}
			}
		}
	}
	public sealed class MirrorPersistentRunner : PersistentRunnerBase
	{
		private const float SyncIntervalSeconds = 5f;

		private const float SyncSlowIntervalSeconds = 30f;

		private float _nextScan;

		private int _passes;

		protected override string RunnerName => "MirrorPersistentRunner";

		protected override void OnUpdate()
		{
			if (MirrorOptions.Enabled != null && MirrorOptions.Enabled.Value)
			{
				CheckReloadHotkey();
				TickFolderSync();
			}
		}

		protected override void OnGameTransition()
		{
			Plugin.ReloadGallery(notify: false);
		}

		protected override void Log(string message)
		{
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogInfo((object)message);
			}
		}

		protected override void LogWarning(string message)
		{
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogWarning((object)message);
			}
		}

		private void CheckReloadHotkey()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			ConfigEntry<KeyCode> reloadKey = MirrorOptions.ReloadKey;
			KeyCode val = (KeyCode)((reloadKey != null) ? ((int)reloadKey.Value) : 0);
			if ((int)val != 0 && Input.GetKeyDown(val))
			{
				ConfigEntry<KeyCode> reloadModifier = MirrorOptions.ReloadModifier;
				KeyCode val2 = (KeyCode)((reloadModifier != null) ? ((int)reloadModifier.Value) : 0);
				if ((int)val2 == 0 || Input.GetKey(val2))
				{
					Plugin.ReloadGallery(MirrorOptions.NotifyOnReload?.Value ?? true);
				}
			}
		}

		private void TickFolderSync()
		{
			if (Time.unscaledTime < _nextScan)
			{
				return;
			}
			_nextScan = Time.unscaledTime + 5f;
			_passes++;
			int num = SaveSlotFolderSync.EnsureCharacterFolders();
			if (num > 0)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)$"[Gallery] Save-slot sync created {num} folder(s).");
				}
			}
			if (_passes > 24 && num == 0)
			{
				_nextScan = Time.unscaledTime + 30f;
			}
		}
	}
}
namespace HavensMirror.Gallery
{
	public static class GalleryPaths
	{
		public static string PluginDirectory
		{
			get
			{
				string location = Assembly.GetExecutingAssembly().Location;
				object obj;
				if (!string.IsNullOrEmpty(location))
				{
					obj = Path.GetDirectoryName(location);
					if (obj == null)
					{
						return ".";
					}
				}
				else
				{
					obj = ".";
				}
				return (string)obj;
			}
		}

		public static string GalleryRoot
		{
			get
			{
				string text = MirrorOptions.GalleryFolderName?.Value;
				if (string.IsNullOrWhiteSpace(text))
				{
					text = "gallery";
				}
				return Path.Combine(PluginDirectory, text.Trim());
			}
		}

		public static string SharedFolderName
		{
			get
			{
				string text = MirrorOptions.SharedFolderName?.Value;
				if (!string.IsNullOrWhiteSpace(text))
				{
					return text.Trim();
				}
				return "_shared";
			}
		}

		public static string SharedFolder => Path.Combine(GalleryRoot, SharedFolderName);

		public static string CharacterFolder(string characterName)
		{
			string path = SanitizeFolderName(characterName);
			return Path.Combine(GalleryRoot, path);
		}

		public static string FileNameFor(LookSlot slot)
		{
			return slot switch
			{
				LookSlot.Summer => "summer.png", 
				LookSlot.Autumn => "autumn.png", 
				LookSlot.Winter => "winter.png", 
				LookSlot.Vows => "vows.png", 
				LookSlot.Shore => "shore.png", 
				LookSlot.Costume => "costume.png", 
				_ => "spring.png", 
			};
		}

		public static string SanitizeFolderName(string name)
		{
			if (string.IsNullOrWhiteSpace(name))
			{
				return SharedFolderName;
			}
			string text = CharacterSaveStore.SanitizeFileName(name.Trim(), SharedFolderName);
			if (!string.IsNullOrEmpty(text))
			{
				return text;
			}
			return SharedFolderName;
		}

		public static bool FolderHasPortraitFiles(string folder)
		{
			if (string.IsNullOrEmpty(folder) || !Directory.Exists(folder))
			{
				return false;
			}
			foreach (LookSlot value in Enum.GetValues(typeof(LookSlot)))
			{
				if (File.Exists(Path.Combine(folder, FileNameFor(value))))
				{
					return true;
				}
			}
			return false;
		}

		public static void EnsureStarterLayout(Action<string> logInfo = null)
		{
			if (MirrorOptions.CreateStarterFolders == null || !MirrorOptions.CreateStarterFolders.Value)
			{
				return;
			}
			try
			{
				CharacterSaveStore.EnsureDirectory(GalleryRoot);
				CharacterSaveStore.EnsureDirectory(SharedFolder);
				File.WriteAllText(Path.Combine(SharedFolder, "HOWTO.txt"), "Haven's Mirror\n==============\n\nWhat happens\n------------\n1. You install the mod.\n2. When the game runs, the mod reads your character saves and creates\n   an empty folder for each one under gallery/ (named like the character).\n3. Empty folders are fine — they are ignored until you add PNGs.\n4. Drop PNG bust images into a character folder (or into this _shared folder).\n5. Talk to someone — if that folder has images, your bust appears.\n\nWhere images go\n---------------\n  gallery/<YourCharacterName>/   (created automatically from saves)\n  gallery/_shared/               (this folder — used if a character folder is empty)\n\nFile names (PNG)\n----------------\n  spring.png    everyday / spring (default)\n  summer.png\n  autumn.png\n  winter.png\n  vows.png      wedding / ceremony\n  shore.png     swimsuit / beach\n  costume.png   halloween / costume\n\nYou do not need every file. Missing names reuse the first PNG in that folder.\n\nReload\n------\nAfter adding or changing images, press Ctrl+F8 in game (configurable).\nNo restart needed.\n");
				logInfo?.Invoke("Gallery ready at '" + GalleryRoot + "'.");
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[Gallery] Could not create starter folders: " + ex.Message));
				}
			}
		}
	}
	public enum LookSlot
	{
		Spring,
		Summer,
		Autumn,
		Winter,
		Vows,
		Shore,
		Costume
	}
	public static class SaveSlotFolderSync
	{
		private static int _lastKnownSlotCount = -1;

		private static string _lastSignature = string.Empty;

		public static int EnsureCharacterFolders()
		{
			if (MirrorOptions.CreateStarterFolders != null && !MirrorOptions.CreateStarterFolders.Value)
			{
				return 0;
			}
			List<string> list = CollectCharacterNamesFromSaves();
			if (list.Count == 0)
			{
				return 0;
			}
			CharacterSaveStore.EnsureDirectory(GalleryPaths.GalleryRoot);
			int num = 0;
			foreach (string item in list)
			{
				string text = GalleryPaths.CharacterFolder(item);
				if (Directory.Exists(text))
				{
					continue;
				}
				try
				{
					Directory.CreateDirectory(text);
					num++;
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogInfo((object)("[Gallery] Created character folder '" + text + "'."));
					}
				}
				catch (Exception ex)
				{
					ManualLogSource log2 = Plugin.Log;
					if (log2 != null)
					{
						log2.LogWarning((object)("[Gallery] Could not create folder for '" + item + "': " + ex.Message));
					}
				}
			}
			string text2 = string.Join("|", list);
			if (num > 0 || list.Count != _lastKnownSlotCount || text2 != _lastSignature)
			{
				_lastKnownSlotCount = list.Count;
				_lastSignature = text2;
				if (num == 0 && list.Count > 0)
				{
					ManualLogSource log3 = Plugin.Log;
					if (log3 != null)
					{
						log3.LogDebug((object)$"[Gallery] Save-slot sync: {list.Count} character folder(s) present.");
					}
				}
			}
			return num;
		}

		public static List<string> CollectCharacterNamesFromSaves()
		{
			List<string> list = new List<string>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			try
			{
				Type type = ReflectionHelper.FindWishType("GameSave");
				if (type == null)
				{
					return list;
				}
				object obj = ReflectionHelper.GetSingletonInstance(type) ?? AccessTools.Property(type, "Instance")?.GetValue(null);
				if (obj == null)
				{
					return list;
				}
				if (!((AccessTools.Property(obj.GetType(), "Saves")?.GetValue(obj, null) ?? AccessTools.Field(obj.GetType(), "Saves")?.GetValue(obj) ?? AccessTools.Property(type, "Saves")?.GetValue(null)) is IEnumerable enumerable))
				{
					return list;
				}
				foreach (object item in enumerable)
				{
					if (item == null)
					{
						continue;
					}
					string text = ReadCharacterNameFromSaveData(item);
					if (!string.IsNullOrWhiteSpace(text))
					{
						text = text.Trim();
						if (hashSet.Add(text))
						{
							list.Add(text);
						}
					}
				}
				string text2 = GameSaveCharacterName.TryGetCurrent();
				if (!string.IsNullOrWhiteSpace(text2) && hashSet.Add(text2.Trim()))
				{
					list.Add(text2.Trim());
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogDebug((object)("[Gallery] Save-slot scan failed: " + ex.Message));
				}
			}
			list.Sort(StringComparer.OrdinalIgnoreCase);
			return list;
		}

		private static string ReadCharacterNameFromSaveData(object saveData)
		{
			if (saveData == null)
			{
				return null;
			}
			object obj = AccessTools.Property(saveData.GetType(), "characterData")?.GetValue(saveData, null) ?? AccessTools.Field(saveData.GetType(), "characterData")?.GetValue(saveData);
			if (obj != null)
			{
				string text = (AccessTools.Property(obj.GetType(), "characterName")?.GetValue(obj, null) ?? AccessTools.Field(obj.GetType(), "characterName")?.GetValue(obj)) as string;
				if (!string.IsNullOrWhiteSpace(text))
				{
					return text;
				}
			}
			if ((AccessTools.Property(saveData.GetType(), "characterName")?.GetValue(saveData, null) ?? AccessTools.Field(saveData.GetType(), "characterName")?.GetValue(saveData)) is string text2 && !string.IsNullOrWhiteSpace(text2))
			{
				return text2;
			}
			if ((AccessTools.Property(saveData.GetType(), "fileName")?.GetValue(saveData, null) ?? AccessTools.Field(saveData.GetType(), "fileName")?.GetValue(saveData)) is string text3 && !string.IsNullOrWhiteSpace(text3))
			{
				string text4 = text3.Trim();
				if (text4.EndsWith(".save", StringComparison.OrdinalIgnoreCase))
				{
					text4 = text4.Substring(0, text4.Length - 5);
				}
				if (!string.IsNullOrWhiteSpace(text4))
				{
					return text4;
				}
			}
			return null;
		}
	}
	public sealed class SpriteShelf : IDisposable
	{
		private readonly Dictionary<LookSlot, Sprite> _sprites = new Dictionary<LookSlot, Sprite>();

		private readonly List<Texture2D> _textures = new List<Texture2D>();

		private string _loadedFrom;

		public bool HasAny => _sprites.Count > 0;

		public string LoadedFrom => _loadedFrom;

		public void Clear()
		{
			foreach (KeyValuePair<LookSlot, Sprite> sprite in _sprites)
			{
				if ((Object)(object)sprite.Value != (Object)null)
				{
					Object.Destroy((Object)(object)sprite.Value);
				}
			}
			_sprites.Clear();
			foreach (Texture2D texture in _textures)
			{
				if ((Object)(object)texture != (Object)null)
				{
					Object.Destroy((Object)(object)texture);
				}
			}
			_textures.Clear();
			_loadedFrom = null;
		}

		public void Dispose()
		{
			Clear();
		}

		public string LoadForCharacter(string characterName)
		{
			Clear();
			string[] array = new string[2]
			{
				GalleryPaths.CharacterFolder(characterName),
				GalleryPaths.SharedFolder
			};
			foreach (string text in array)
			{
				if (!GalleryPaths.FolderHasPortraitFiles(text))
				{
					continue;
				}
				Sprite val = null;
				foreach (LookSlot value in Enum.GetValues(typeof(LookSlot)))
				{
					string path = Path.Combine(text, GalleryPaths.FileNameFor(value));
					Sprite val2 = TryLoadPng(path);
					if (!((Object)(object)val2 == (Object)null))
					{
						_sprites[value] = val2;
						if ((Object)(object)val == (Object)null)
						{
							val = val2;
						}
					}
				}
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				foreach (LookSlot value2 in Enum.GetValues(typeof(LookSlot)))
				{
					if (!_sprites.ContainsKey(value2))
					{
						_sprites[value2] = val;
					}
				}
				_loadedFrom = text;
				return "Loaded portraits from '" + text + "'.";
			}
			return "No portrait PNGs yet (empty character folders are ignored; drop files in gallery/<name>/ or gallery/_shared/).";
		}

		public Sprite Get(LookSlot slot)
		{
			if (!_sprites.TryGetValue(slot, out Sprite value))
			{
				return null;
			}
			return value;
		}

		private Sprite TryLoadPng(string path)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected O, but got Unknown
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!File.Exists(path))
				{
					return null;
				}
				byte[] array = File.ReadAllBytes(path);
				if (array == null || array.Length == 0)
				{
					return null;
				}
				Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
				if (!ImageConversion.LoadImage(val, array))
				{
					Object.Destroy((Object)(object)val);
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogWarning((object)("[Gallery] Failed to decode PNG: " + path));
					}
					return null;
				}
				((Texture)val).filterMode = (FilterMode)1;
				((Texture)val).wrapMode = (TextureWrapMode)1;
				((Object)val).name = Path.GetFileNameWithoutExtension(path);
				Sprite obj = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f);
				((Object)obj).name = ((Object)val).name;
				_textures.Add(val);
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogInfo((object)$"[Gallery] Loaded {path} ({((Texture)val).width}x{((Texture)val).height}).");
				}
				return obj;
			}
			catch (Exception ex)
			{
				ManualLogSource log3 = Plugin.Log;
				if (log3 != null)
				{
					log3.LogWarning((object)("[Gallery] Error loading '" + path + "': " + ex.Message));
				}
				return null;
			}
		}
	}
}
namespace HavensMirror.Dialogue
{
	public static class DialogueMirrorBinder
	{
		private static bool _bound;

		private static FieldInfo _dialoguePanelField;

		private static FieldInfo _bustField;

		private static FieldInfo _npcNameField;

		private static readonly Dictionary<LookSlot, object> EmoteTables = new Dictionary<LookSlot, object>();

		private static readonly Dictionary<LookSlot, object> EmoteAddressables = new Dictionary<LookSlot, object>();

		public static void Apply(Harmony harmony)
		{
			if (_bound)
			{
				return;
			}
			Type type = ReflectionHelper.FindWishType("DialogueController");
			if (type == null)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)"[Reflect] Wish.DialogueController not found — player bust overlay inactive.");
				}
				return;
			}
			_dialoguePanelField = FindField(type, "_dialoguePanel", "dialoguePanel", "DialoguePanel");
			_bustField = FindField(type, "_bust", "bust", "Bust");
			_npcNameField = FindField(type, "npcName", "_npcName", "NpcName");
			TryPatchAwake(harmony, type);
			TryPatchBustVisualSetters(harmony, type);
			TryPatchInitialBustSelectors(harmony, type);
			TryPatchCancelDialogue(harmony, type);
			_bound = true;
		}

		private static void TryPatchAwake(Harmony harmony, Type dialogueType)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(dialogueType, "Awake", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)"[Reflect] DialogueController.Awake missing.");
				}
				return;
			}
			harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(DialogueMirrorBinder), "AwakePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			ManualLogSource log2 = Plugin.Log;
			if (log2 != null)
			{
				log2.LogInfo((object)"Patched DialogueController.Awake for mirror HUD attach.");
			}
		}

		private static void TryPatchBustVisualSetters(Harmony harmony, Type dialogueType)
		{
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Expected O, but got Unknown
			//IL_0098: Expected O, but got Unknown
			int num = 0;
			foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(dialogueType))
			{
				if (declaredMethod == null || declaredMethod.IsAbstract)
				{
					continue;
				}
				string text = declaredMethod.Name ?? string.Empty;
				if (text.IndexOf("SetDialogueBust", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("SetBustVisual", StringComparison.OrdinalIgnoreCase) < 0)
				{
					continue;
				}
				try
				{
					harmony.Patch((MethodBase)declaredMethod, new HarmonyMethod(typeof(DialogueMirrorBinder), "BustSetterPrefix", (Type[])null), new HarmonyMethod(typeof(DialogueMirrorBinder), "BustSetterPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					num++;
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogInfo((object)("Patched " + dialogueType.Name + "." + declaredMethod.Name + " for player bust present."));
					}
				}
				catch (Exception ex)
				{
					ManualLogSource log2 = Plugin.Log;
					if (log2 != null)
					{
						log2.LogWarning((object)("[Reflect] Could not patch " + declaredMethod.Name + ": " + ex.Message));
					}
				}
			}
			if (num == 0)
			{
				ManualLogSource log3 = Plugin.Log;
				if (log3 != null)
				{
					log3.LogWarning((object)"[Reflect] No DialogueController bust visual setters found.");
				}
			}
		}

		private static void TryPatchInitialBustSelectors(Harmony harmony, Type dialogueType)
		{
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Expected O, but got Unknown
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Expected O, but got Unknown
			string[] array = new string[2] { "SetInitialBust", "GetInitialBust" };
			foreach (string text in array)
			{
				foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(dialogueType))
				{
					if (declaredMethod == null || !string.Equals(declaredMethod.Name, text, StringComparison.Ordinal))
					{
						continue;
					}
					try
					{
						if (text == "SetInitialBust")
						{
							harmony.Patch((MethodBase)declaredMethod, new HarmonyMethod(typeof(DialogueMirrorBinder), "SetInitialBustPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
						}
						else
						{
							harmony.Patch((MethodBase)declaredMethod, new HarmonyMethod(typeof(DialogueMirrorBinder), "GetInitialBustPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
						}
						ManualLogSource log = Plugin.Log;
						if (log != null)
						{
							log.LogInfo((object)("Patched DialogueController." + text + " for forced look."));
						}
					}
					catch (Exception ex)
					{
						ManualLogSource log2 = Plugin.Log;
						if (log2 != null)
						{
							log2.LogWarning((object)("[Reflect] Could not patch " + text + ": " + ex.Message));
						}
					}
				}
			}
		}

		private static void AwakePostfix(object __instance)
		{
			try
			{
				if (__instance == null)
				{
					return;
				}
				CaptureEmoteTables(__instance);
				EnsureHudOn(__instance);
				if ((Object)(object)PlayerBustHud.Instance != (Object)null)
				{
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogInfo((object)"[Dialogue] PlayerBustHud attached on DialogueController.Awake.");
					}
				}
				else
				{
					ManualLogSource log2 = Plugin.Log;
					if (log2 != null)
					{
						log2.LogWarning((object)"[Dialogue] Awake: DialogueController host not found — HUD attach deferred.");
					}
				}
				SaveSlotFolderSync.EnsureCharacterFolders();
				Plugin.ReloadGallery(notify: false);
			}
			catch (Exception ex)
			{
				ManualLogSource log3 = Plugin.Log;
				if (log3 != null)
				{
					log3.LogWarning((object)("[Dialogue] Awake hook failed: " + ex.Message));
				}
			}
		}

		private static void BustSetterPrefix(object[] __args)
		{
			if (MirrorOptions.Enabled == null || !MirrorOptions.Enabled.Value)
			{
				return;
			}
			try
			{
				MutateLookFlagsInArgs(__args);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogDebug((object)("[Dialogue] BustSetterPrefix: " + ex.Message));
				}
			}
		}

		private static void BustSetterPostfix(object __instance, object[] __args)
		{
			if (MirrorOptions.Enabled == null || !MirrorOptions.Enabled.Value)
			{
				return;
			}
			try
			{
				bool vows = false;
				bool shore = false;
				bool costume = false;
				bool refresh = false;
				ReadLookFlagsFromArgs(__args, ref vows, ref shore, ref costume, ref refresh);
				EnsureHudOn(__instance);
				PlayerBustHud instance = PlayerBustHud.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogWarning((object)"[Dialogue] Present skipped — PlayerBustHud not attached.");
					}
				}
				else
				{
					instance.Present(vows, shore, costume);
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogWarning((object)("[Dialogue] BustSetterPostfix: " + ex.Message));
				}
			}
		}

		private static void TryPatchCancelDialogue(Harmony harmony, Type dialogueType)
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(dialogueType))
			{
				if (declaredMethod == null || !string.Equals(declaredMethod.Name, "CancelDialogue", StringComparison.Ordinal))
				{
					continue;
				}
				try
				{
					harmony.Patch((MethodBase)declaredMethod, (HarmonyMethod)null, new HarmonyMethod(typeof(DialogueMirrorBinder), "CancelDialoguePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogInfo((object)("Patched DialogueController." + declaredMethod.Name + " to hide player bust."));
					}
				}
				catch (Exception ex)
				{
					ManualLogSource log2 = Plugin.Log;
					if (log2 != null)
					{
						log2.LogWarning((object)("[Reflect] Could not patch CancelDialogue: " + ex.Message));
					}
				}
			}
		}

		private static void CancelDialoguePostfix()
		{
			try
			{
				PlayerBustHud.Instance?.Hide();
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogDebug((object)("[Dialogue] CancelDialogue hide: " + ex.Message));
				}
			}
		}

		private static void EnsureHudOn(object dialogueController)
		{
			GameObject val = null;
			Component val2 = (Component)((dialogueController is Component) ? dialogueController : null);
			if (val2 != null)
			{
				val = val2.gameObject;
			}
			if ((Object)(object)val == (Object)null && _dialoguePanelField != null && dialogueController != null)
			{
				object? value = _dialoguePanelField.GetValue(dialogueController);
				val = (GameObject)((value is GameObject) ? value : null);
			}
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			bool flag = (Object)(object)PlayerBustHud.Instance == (Object)null || (Object)(object)((Component)PlayerBustHud.Instance).gameObject == (Object)null;
			PlayerBustHud playerBustHud = PlayerBustHud.EnsureOn(val);
			if ((Object)(object)playerBustHud == (Object)null)
			{
				return;
			}
			if (_bustField != null && dialogueController != null)
			{
				object? value2 = _bustField.GetValue(dialogueController);
				Image val3 = (Image)((value2 is Image) ? value2 : null);
				if (val3 != null)
				{
					playerBustHud.BindNpcBust(val3);
				}
			}
			if (flag)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)"[Dialogue] PlayerBustHud attached on DialogueController host.");
				}
			}
		}

		private static bool SetInitialBustPrefix(object __instance, object[] __args)
		{
			if (MirrorOptions.Enabled == null || !MirrorOptions.Enabled.Value)
			{
				return true;
			}
			try
			{
				MutateLookFlagsInArgs(__args);
				string text = ReadNpcName(__instance);
				if (string.IsNullOrWhiteSpace(text))
				{
					return false;
				}
				if (!ForcedLookResolver.TryParseForced(out var _) && !ArgsRequestSpecialLook(__args))
				{
					return true;
				}
				LookSlot slot2 = ForcedLookResolver.ResolveActiveLook(ArgsHasFlag(__args, "marriage", "wedding", "vow"), ArgsHasFlag(__args, "swimsuit", "swim", "shore"), ArgsHasFlag(__args, "halloween", "costume"));
				if (TryAssignNpcSprite(__instance, slot2, text, __args))
				{
					return false;
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogDebug((object)("[Dialogue] SetInitialBustPrefix: " + ex.Message));
				}
			}
			return true;
		}

		private static bool GetInitialBustPrefix(object __instance, ref object __result, object[] __args)
		{
			if (MirrorOptions.Enabled == null || !MirrorOptions.Enabled.Value)
			{
				return true;
			}
			try
			{
				MutateLookFlagsInArgs(__args);
				string text = ReadNpcName(__instance);
				if (string.IsNullOrWhiteSpace(text))
				{
					return false;
				}
				if (!ForcedLookResolver.TryParseForced(out var _) && !ArgsRequestSpecialLook(__args))
				{
					return true;
				}
				LookSlot slot2 = ForcedLookResolver.ResolveActiveLook(ArgsHasFlag(__args, "marriage", "wedding", "vow"), ArgsHasFlag(__args, "swimsuit", "swim", "shore"), ArgsHasFlag(__args, "halloween", "costume"));
				if (TryResolveAddressable(__instance, slot2, text, out object assetRef))
				{
					__result = assetRef;
					return false;
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogDebug((object)("[Dialogue] GetInitialBustPrefix: " + ex.Message));
				}
			}
			return true;
		}

		private static void CaptureEmoteTables(object dialogueController)
		{
			EmoteTables.Clear();
			EmoteAddressables.Clear();
			TryCapture(dialogueController, LookSlot.Spring, EmoteTables, "_npcEmotes", "npcEmotes");
			TryCapture(dialogueController, LookSlot.Summer, EmoteTables, "_npcSummerEmotes", "npcSummerEmotes");
			TryCapture(dialogueController, LookSlot.Autumn, EmoteTables, "_npcFallEmotes", "npcFallEmotes");
			TryCapture(dialogueController, LookSlot.Winter, EmoteTables, "_npcWinterEmotes", "npcWinterEmotes");
			TryCapture(dialogueController, LookSlot.Vows, EmoteTables, "_npcWeddingEmotes", "npcWeddingEmotes");
			TryCapture(dialogueController, LookSlot.Shore, EmoteTables, "_npcSwimsuitEmotes", "npcSwimsuitEmotes");
			TryCapture(dialogueController, LookSlot.Spring, EmoteAddressables, "_npcEmotes2", "npcEmotes2");
			TryCapture(dialogueController, LookSlot.Summer, EmoteAddressables, "_npcSummerEmotes2", "npcSummerEmotes2");
			TryCapture(dialogueController, LookSlot.Autumn, EmoteAddressables, "_npcFallEmotes2", "npcFallEmotes2");
			TryCapture(dialogueController, LookSlot.Winter, EmoteAddressables, "_npcWinterEmotes2", "npcWinterEmotes2");
			TryCapture(dialogueController, LookSlot.Vows, EmoteAddressables, "_npcWeddingEmotes2", "npcWeddingEmotes2");
			TryCapture(dialogueController, LookSlot.Shore, EmoteAddressables, "_npcSwimsuitEmotes2", "npcSwimsuitEmotes2");
			TryCapture(dialogueController, LookSlot.Costume, EmoteAddressables, "_npcHalloweenEmotes2", "npcHalloweenEmotes2");
		}

		private static void TryCapture(object instance, LookSlot slot, Dictionary<LookSlot, object> bag, params string[] names)
		{
			FieldInfo fieldInfo = FindField(instance.GetType(), names);
			if (!(fieldInfo == null))
			{
				object value = fieldInfo.GetValue(instance);
				if (value != null)
				{
					bag[slot] = value;
				}
			}
		}

		private static bool TryAssignNpcSprite(object dialogueController, LookSlot slot, string npcName, object[] args)
		{
			if (!EmoteTables.TryGetValue(slot, out object value) || value == null)
			{
				return false;
			}
			if (!(value is IDictionary dictionary))
			{
				return false;
			}
			object obj = null;
			foreach (DictionaryEntry item in dictionary)
			{
				if (item.Key != null && string.Equals(item.Key.ToString(), npcName, StringComparison.OrdinalIgnoreCase))
				{
					obj = item.Value;
					break;
				}
			}
			if (obj == null)
			{
				return false;
			}
			Sprite val = null;
			if (obj is IList { Count: >0 } list)
			{
				object? obj2 = list[0];
				val = (Sprite)((obj2 is Sprite) ? obj2 : null);
			}
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			ImageFieldAssign(dialogueController, val, args);
			return true;
		}

		private static void ImageFieldAssign(object dialogueController, Sprite sprite, object[] args)
		{
			if (args != null)
			{
				foreach (object obj in args)
				{
					Image val = (Image)((obj is Image) ? obj : null);
					if (val != null)
					{
						val.sprite = sprite;
						return;
					}
				}
			}
			FieldInfo fieldInfo = FindField(dialogueController.GetType(), "_bust", "bust", "Bust");
			if (fieldInfo != null)
			{
				object? value = fieldInfo.GetValue(dialogueController);
				Image val2 = (Image)((value is Image) ? value : null);
				if (val2 != null)
				{
					val2.sprite = sprite;
				}
			}
		}

		private static bool TryResolveAddressable(object dialogueController, LookSlot slot, string npcName, out object assetRef)
		{
			assetRef = null;
			if (!EmoteAddressables.TryGetValue(slot, out object value) || value == null)
			{
				return false;
			}
			if (!(value is IDictionary dictionary))
			{
				return false;
			}
			object obj = null;
			foreach (DictionaryEntry item in dictionary)
			{
				if (item.Key != null && string.Equals(item.Key.ToString(), npcName, StringComparison.OrdinalIgnoreCase))
				{
					obj = item.Value;
					break;
				}
			}
			if (obj is Array { Length: >0 } array)
			{
				assetRef = array.GetValue(0);
				return assetRef != null;
			}
			return false;
		}

		private static string ReadNpcName(object dialogueController)
		{
			if (_npcNameField == null || dialogueController == null)
			{
				return null;
			}
			return _npcNameField.GetValue(dialogueController)?.ToString();
		}

		private static void MutateLookFlagsInArgs(object[] args)
		{
			if (args == null || !ForcedLookResolver.TryParseForced(out var slot) || (slot != LookSlot.Vows && slot != LookSlot.Shore && slot != LookSlot.Costume))
			{
				return;
			}
			List<int> list = new List<int>();
			for (int i = 0; i < args.Length; i++)
			{
				if (args[i] is bool)
				{
					list.Add(i);
				}
			}
			if (list.Count < 2)
			{
				return;
			}
			if (list.Count > 1)
			{
				args[list[1]] = false;
			}
			if (list.Count > 2)
			{
				args[list[2]] = false;
			}
			if (list.Count > 5)
			{
				args[list[5]] = false;
			}
			switch (slot)
			{
			case LookSlot.Vows:
				args[list[1]] = true;
				return;
			case LookSlot.Shore:
				if (list.Count > 2)
				{
					args[list[2]] = true;
					return;
				}
				break;
			}
			if (slot == LookSlot.Costume && list.Count > 5)
			{
				args[list[5]] = true;
			}
			else if (slot == LookSlot.Costume)
			{
				args[list[list.Count - 1]] = true;
			}
		}

		private static void ReadLookFlagsFromArgs(object[] args, ref bool vows, ref bool shore, ref bool costume, ref bool refresh)
		{
			if (args == null)
			{
				return;
			}
			List<bool> list = new List<bool>();
			for (int i = 0; i < args.Length; i++)
			{
				if (args[i] is bool item)
				{
					list.Add(item);
				}
			}
			if (list.Count > 1)
			{
				vows = list[1];
			}
			if (list.Count > 2)
			{
				shore = list[2];
			}
			if (list.Count > 4)
			{
				refresh = list[4];
			}
			if (list.Count > 5)
			{
				costume = list[5];
			}
			ForcedLookResolver.ApplyForcedFlags(ref vows, ref shore, ref costume);
		}

		private static bool ArgsRequestSpecialLook(object[] args)
		{
			bool vows = false;
			bool shore = false;
			bool costume = false;
			bool refresh = false;
			ReadLookFlagsFromArgs(args, ref vows, ref shore, ref costume, ref refresh);
			return vows || shore || costume;
		}

		private static bool ArgsHasFlag(object[] args, params string[] _)
		{
			bool vows = false;
			bool shore = false;
			bool costume = false;
			bool refresh = false;
			ReadLookFlagsFromArgs(args, ref vows, ref shore, ref costume, ref refresh);
			return vows || shore || costume;
		}

		private static FieldInfo FindField(Type type, params string[] names)
		{
			if (type == null || names == null)
			{
				return null;
			}
			foreach (string text in names)
			{
				FieldInfo fieldInfo = AccessTools.Field(type, text);
				if (fieldInfo != null)
				{
					return fieldInfo;
				}
			}
			return null;
		}
	}
	public sealed class PlayerBustHud : MonoBehaviour
	{
		private const float LeftMargin = 24f;

		private const float LeftFraction = 0.15f;

		private const float FallbackBustHeight = 250f;

		private const float FallbackAspect = 0.83f;

		private static PlayerBustHud _instance;

		private static bool _loggedLayout;

		private Image _image;

		private RectTransform _rect;

		private bool _ready;

		private Image _npcBust;

		private Coroutine _relayoutRoutine;

		public static PlayerBustHud Instance => _instance;

		public static PlayerBustHud EnsureOn(GameObject host)
		{
			if ((Object)(object)host == (Object)null)
			{
				return null;
			}
			if ((Object)(object)_instance != (Object)null && (Object)(object)((Component)_instance).gameObject != (Object)null)
			{
				return _instance;
			}
			PlayerBustHu