Decompiled source of Softcore Mode v0.1.0

OutwardSoftcoreMode.dll

Decompiled 5 hours ago
#define DEBUG
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.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Xml.Linq;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using OutwardModsCommunicator.EventBus;
using OutwardModsCommunicator.Managers;
using OutwardSoftcoreMode.BepInEx.Configs;
using OutwardSoftcoreMode.Events;
using OutwardSoftcoreMode.Services;
using SideLoader;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("OutwardSoftcoreMode")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("OutwardSoftcoreMode")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("c5450fe0-edcf-483f-b9ea-4b1ef9d36da7")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
namespace OutwardSoftcoreMode
{
	[BepInPlugin("gymmed.softcore_mode", "Softcore Mode", "0.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class OutwardSoftcoreMode : BaseUnityPlugin
	{
		public const string GUID = "gymmed.softcore_mode";

		public const string NAME = "Softcore Mode";

		public const string VERSION = "0.1.0";

		public static string prefix = "[Softcore-Mode]";

		public const string EVENTS_LISTENER_GUID = "gymmed.softcore_mode_*";

		internal static ManualLogSource Log;

		public static bool IsCurrentGameSoftcore;

		internal static int PendingSoftcoreCount;

		internal static HashSet<string> PendingManualBackupUIDs = new HashSet<string>();

		internal static HashSet<string> PendingCooldownUIDs = new HashSet<string>();

		public static ConfigEntry<int> MaxBackups => BackupsConfigs.MaxBackups;

		public static ConfigEntry<float> SaveCooldownHours => SaveCooldownConfigs.SaveCooldownHours;

		public static ConfigEntry<int> DeathChance => DeathChanceConfigs.DeathChance;

		public static ConfigEntry<bool> CooldownRandomizerEnabled => CooldownRandomizerConfigs.CooldownRandomizerEnabled;

		public static ConfigEntry<float> CooldownRandomizerMinHours => CooldownRandomizerConfigs.CooldownRandomizerMinHours;

		public static ConfigEntry<float> CooldownRandomizerMaxHours => CooldownRandomizerConfigs.CooldownRandomizerMaxHours;

		internal void Awake()
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			LogMessage("Hello world from Softcore Mode 0.1.0!");
			BackupsConfigs.Init((BaseUnityPlugin)(object)this);
			SaveCooldownConfigs.Init((BaseUnityPlugin)(object)this);
			DeathChanceConfigs.Init((BaseUnityPlugin)(object)this);
			CooldownRandomizerConfigs.Init((BaseUnityPlugin)(object)this);
			new Harmony("gymmed.softcore_mode").PatchAll();
			EventBusRegister.RegisterEvents();
		}

		internal void Update()
		{
			if (PendingCooldownUIDs.Count <= 0)
			{
				return;
			}
			float gameTimeF = EnvironmentConditions.GameTimeF;
			if (!(gameTimeF > 0f))
			{
				return;
			}
			foreach (string pendingCooldownUID in PendingCooldownUIDs)
			{
				SoftcoreSaveManager.SetLastBackupGameTime(pendingCooldownUID, gameTimeF);
				SoftcoreSaveManager.ClearRestoredFlag(pendingCooldownUID);
				DebugLog($"Cooldown set to {gameTimeF:F2} for restored {pendingCooldownUID} (deferred)");
			}
			PendingCooldownUIDs.Clear();
		}

		public static void LogMessage(string message)
		{
			Log.LogMessage((object)(prefix + " " + message));
		}

		[Conditional("DEBUG")]
		public static void DebugLog(string message)
		{
			Log.LogMessage((object)(prefix + " [DEBUG] " + message));
		}

		public static void LogSL(string message)
		{
			SL.Log(prefix + " " + message);
		}

		public static string GetProjectLocation()
		{
			return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
		}
	}
}
namespace OutwardSoftcoreMode.Services
{
	public static class BackupMetadataStore
	{
		private const string FILENAME = "SoftcoreSaveData.xml";

		private static string GetFilePath(string uid, string instanceTimestamp)
		{
			return Path.Combine(SoftcorePaths.GetBackupsDir(uid), instanceTimestamp, "SoftcoreSaveData.xml");
		}

		public static void WriteGameTime(string uid, string instanceTimestamp, float gameTime)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Expected O, but got Unknown
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Expected O, but got Unknown
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			string filePath = GetFilePath(uid, instanceTimestamp);
			Directory.CreateDirectory(Path.GetDirectoryName(filePath));
			XDocument val = new XDocument(new object[1] { (object)new XElement(XName.op_Implicit("SoftcoreBackupData"), (object)new XElement(XName.op_Implicit("GameTime"), (object)gameTime.ToString("F2"))) });
			val.Save(filePath);
		}

		private static bool TryReadGameTime(string uid, string instanceTimestamp, out float gameTime)
		{
			string filePath = GetFilePath(uid, instanceTimestamp);
			if (!File.Exists(filePath))
			{
				gameTime = -1f;
				return false;
			}
			try
			{
				XDocument val = XDocument.Load(filePath);
				XElement root = val.Root;
				XElement val2 = ((root != null) ? ((XContainer)root).Element(XName.op_Implicit("GameTime")) : null);
				if (val2 != null && float.TryParse(val2.Value, out gameTime))
				{
					return true;
				}
			}
			catch
			{
			}
			gameTime = -1f;
			return false;
		}

		public static float GetLatestBackupGameTime(string uid)
		{
			string backupsDir = SoftcorePaths.GetBackupsDir(uid);
			if (!Directory.Exists(backupsDir))
			{
				return -1f;
			}
			IOrderedEnumerable<DirectoryInfo> orderedEnumerable = from d in new DirectoryInfo(backupsDir).GetDirectories()
				orderby d.Name descending
				select d;
			foreach (DirectoryInfo item in orderedEnumerable)
			{
				if (TryReadGameTime(uid, item.Name, out var gameTime))
				{
					return gameTime;
				}
			}
			return -1f;
		}
	}
	public static class SoftcoreColors
	{
		public const string PurpleHex = "#A855F7";

		public static Color Purple => new Color(0.6588f, 0.3333f, 0.9686f);

		public static void DestroyLocalize(GameObject obj)
		{
			UILocalize[] componentsInChildren = obj.GetComponentsInChildren<UILocalize>(true);
			foreach (UILocalize val in componentsInChildren)
			{
				Object.Destroy((Object)(object)val);
			}
		}

		public static GameObject CreateSoftcoreLabel(GameObject template, Transform parent, string name)
		{
			GameObject val = Object.Instantiate<GameObject>(template, parent);
			((Object)val).name = name;
			DestroyLocalize(val);
			return val;
		}
	}
	public static class SoftcorePaths
	{
		public static string RootPath => Path.Combine(PathsManager.ConfigPath, "Softcore_Mode");

		public static string CharactersPath => Path.Combine(RootPath, "Characters");

		public static string BackupsPath => Path.Combine(RootPath, "Backups");

		public static string GetMetadataPath(string uid)
		{
			return Path.Combine(CharactersPath, uid + ".xml");
		}

		public static string GetBackupsDir(string uid)
		{
			return Path.Combine(BackupsPath, uid);
		}
	}
	public static class SoftcoreSaveManager
	{
		private static void EnsureDirectoriesExist()
		{
			Directory.CreateDirectory(SoftcorePaths.CharactersPath);
			Directory.CreateDirectory(SoftcorePaths.BackupsPath);
		}

		private static XDocument LoadMetadataOrNull(string uid)
		{
			string metadataPath = SoftcorePaths.GetMetadataPath(uid);
			if (!File.Exists(metadataPath))
			{
				return null;
			}
			try
			{
				return XDocument.Load(metadataPath);
			}
			catch
			{
				return null;
			}
		}

		private static void ModifyMetadata(string uid, Action<XElement> transform)
		{
			string metadataPath = SoftcorePaths.GetMetadataPath(uid);
			if (!File.Exists(metadataPath))
			{
				return;
			}
			try
			{
				XDocument val = XDocument.Load(metadataPath);
				transform(val.Root);
				val.Save(metadataPath);
			}
			catch (Exception ex)
			{
				OutwardSoftcoreMode.LogMessage("Failed to modify metadata: " + ex.Message);
			}
		}

		public static bool IsSoftcoreCharacter(string uid)
		{
			if (string.IsNullOrEmpty(uid))
			{
				return false;
			}
			return File.Exists(SoftcorePaths.GetMetadataPath(uid)) || Directory.Exists(SoftcorePaths.GetBackupsDir(uid));
		}

		public static string GetCharacterName(string uid)
		{
			XDocument obj = LoadMetadataOrNull(uid);
			object result;
			if (obj == null)
			{
				result = null;
			}
			else
			{
				XElement root = obj.Root;
				if (root == null)
				{
					result = null;
				}
				else
				{
					XElement obj2 = ((XContainer)root).Element(XName.op_Implicit("Name"));
					result = ((obj2 != null) ? obj2.Value : null);
				}
			}
			return (string)result;
		}

		public static void WriteMetadata(string uid, string name)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Expected O, but got Unknown
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Expected O, but got Unknown
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Expected O, but got Unknown
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Expected O, but got Unknown
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Expected O, but got Unknown
			EnsureDirectoriesExist();
			XDocument val = new XDocument(new object[1] { (object)new XElement(XName.op_Implicit("SoftcoreCharacter"), new object[4]
			{
				(object)new XElement(XName.op_Implicit("Name"), (object)(name ?? "Unknown")),
				(object)new XElement(XName.op_Implicit("PermanentDeathCount"), (object)0),
				(object)new XElement(XName.op_Implicit("LastBackupGameTime"), (object)(-1)),
				(object)new XElement(XName.op_Implicit("CooldownDuration"), (object)(-1f))
			}) });
			val.Save(SoftcorePaths.GetMetadataPath(uid));
		}

		public static int GetPermanentDeathCount(string uid)
		{
			XDocument val = LoadMetadataOrNull(uid);
			if (val == null)
			{
				return 0;
			}
			XElement root = val.Root;
			XElement val2 = ((root != null) ? ((XContainer)root).Element(XName.op_Implicit("PermanentDeathCount")) : null);
			if (val2 != null && int.TryParse(val2.Value, out var result))
			{
				return result;
			}
			val2 = ((root != null) ? ((XContainer)root).Element(XName.op_Implicit("DeathCount")) : null);
			return (val2 != null && int.TryParse(val2.Value, out result)) ? result : 0;
		}

		public static void IncrementPermanentDeathCount(string uid)
		{
			int permanentDeathCount = GetPermanentDeathCount(uid);
			OutwardSoftcoreMode.DebugLog($"IncrementPermanentDeathCount: uid={uid}, oldCount={permanentDeathCount}, path={SoftcorePaths.GetMetadataPath(uid)}");
			string metadataPath = SoftcorePaths.GetMetadataPath(uid);
			if (!File.Exists(metadataPath))
			{
				string name = GetCharacterName(uid) ?? "Unknown";
				WriteMetadata(uid, name);
			}
			ModifyMetadata(uid, delegate(XElement root)
			{
				//IL_0050: Unknown result type (might be due to invalid IL or missing references)
				//IL_005a: Expected O, but got Unknown
				XElement val = ((XContainer)root).Element(XName.op_Implicit("PermanentDeathCount"));
				if (val != null)
				{
					int.TryParse(val.Value, out var result);
					val.Value = (result + 1).ToString();
				}
				else
				{
					((XContainer)root).Add((object)new XElement(XName.op_Implicit("PermanentDeathCount"), (object)1));
				}
			});
			int permanentDeathCount2 = GetPermanentDeathCount(uid);
			OutwardSoftcoreMode.DebugLog($"IncrementPermanentDeathCount: uid={uid}, newCount={permanentDeathCount2}");
		}

		public static void EnsureMetadataExists(string uid, string name)
		{
			string metadataPath = SoftcorePaths.GetMetadataPath(uid);
			if (!File.Exists(metadataPath))
			{
				WriteMetadata(uid, name ?? "Unknown");
				OutwardSoftcoreMode.DebugLog("Created metadata for " + uid + " (name=" + name + ")");
			}
		}

		public static float GetLastBackupGameTime(string uid)
		{
			float latestBackupGameTime = BackupMetadataStore.GetLatestBackupGameTime(uid);
			if (latestBackupGameTime >= 0f)
			{
				return latestBackupGameTime;
			}
			XDocument val = LoadMetadataOrNull(uid);
			if (val == null)
			{
				return -1f;
			}
			XElement root = val.Root;
			XElement val2 = ((root != null) ? ((XContainer)root).Element(XName.op_Implicit("LastBackupGameTime")) : null);
			float result;
			return (val2 != null && float.TryParse(val2.Value, out result)) ? result : (-1f);
		}

		public static void SetLastBackupGameTime(string uid, float gameTime)
		{
			ModifyMetadata(uid, delegate(XElement root)
			{
				//IL_004e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0058: Expected O, but got Unknown
				XElement val = ((XContainer)root).Element(XName.op_Implicit("LastBackupGameTime"));
				if (val != null)
				{
					val.Value = gameTime.ToString("F2");
				}
				else
				{
					((XContainer)root).Add((object)new XElement(XName.op_Implicit("LastBackupGameTime"), (object)gameTime.ToString("F2")));
				}
			});
		}

		private static (bool canBackup, float remainingTime) GetCooldownState(string uid)
		{
			float num = OutwardSoftcoreMode.SaveCooldownHours?.Value ?? 24f;
			if (num <= 0f)
			{
				return (canBackup: true, remainingTime: 0f);
			}
			float lastBackupGameTime = GetLastBackupGameTime(uid);
			if (lastBackupGameTime < 0f)
			{
				return (canBackup: true, remainingTime: 0f);
			}
			float num2 = ReadStoredCooldownDuration(uid, num);
			float gameTimeF = EnvironmentConditions.GameTimeF;
			float num3 = gameTimeF - lastBackupGameTime;
			float item = Math.Max(0f, num2 - num3);
			return (canBackup: num3 >= num2, remainingTime: item);
		}

		public static bool CanBackupNow(string uid)
		{
			return GetCooldownState(uid).canBackup;
		}

		public static float GetRemainingCooldownTime(string uid)
		{
			return GetCooldownState(uid).remainingTime;
		}

		public static string GetCharacterSaveDirectory(string uid)
		{
			return Path.Combine(SaveManager.GetSavePath(), "Save_" + uid);
		}

		private static void CopyDirectoryContents(string sourceDir, string destDir)
		{
			Directory.CreateDirectory(destDir);
			string[] files = Directory.GetFiles(sourceDir);
			foreach (string text in files)
			{
				string destFileName = Path.Combine(destDir, Path.GetFileName(text));
				File.Copy(text, destFileName, overwrite: true);
			}
		}

		public static bool IsRestoredBackupInstance(string uid, string instancePath)
		{
			if (string.IsNullOrEmpty(uid) || string.IsNullOrEmpty(instancePath))
			{
				return false;
			}
			return Directory.Exists(Path.Combine(SoftcorePaths.GetBackupsDir(uid), instancePath));
		}

		private static float ReadStoredCooldownDuration(string uid, float fallback)
		{
			XDocument val = LoadMetadataOrNull(uid);
			if (val == null)
			{
				return fallback;
			}
			XElement root = val.Root;
			XElement val2 = ((root != null) ? ((XContainer)root).Element(XName.op_Implicit("CooldownDuration")) : null);
			if (val2 != null && float.TryParse(val2.Value, out var result) && result > 0f)
			{
				return result;
			}
			return fallback;
		}

		private static void WriteCooldownDuration(string uid, float duration)
		{
			ModifyMetadata(uid, delegate(XElement root)
			{
				//IL_004e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0058: Expected O, but got Unknown
				XElement val = ((XContainer)root).Element(XName.op_Implicit("CooldownDuration"));
				if (val != null)
				{
					val.Value = duration.ToString("F2");
				}
				else
				{
					((XContainer)root).Add((object)new XElement(XName.op_Implicit("CooldownDuration"), (object)duration.ToString("F2")));
				}
			});
		}

		public static void CreateBackup(string uid, string instanceTimestamp)
		{
			if (string.IsNullOrEmpty(uid) || string.IsNullOrEmpty(instanceTimestamp))
			{
				return;
			}
			EnsureDirectoriesExist();
			string text = Path.Combine(GetCharacterSaveDirectory(uid), instanceTimestamp);
			if (!Directory.Exists(text))
			{
				OutwardSoftcoreMode.LogMessage("Backup source not found: " + text);
				return;
			}
			string destDir = Path.Combine(SoftcorePaths.GetBackupsDir(uid), instanceTimestamp);
			CopyDirectoryContents(text, destDir);
			OutwardSoftcoreMode.LogMessage("Backup created for " + uid + " at " + instanceTimestamp);
			float gameTimeF = EnvironmentConditions.GameTimeF;
			ConfigEntry<bool> cooldownRandomizerEnabled = OutwardSoftcoreMode.CooldownRandomizerEnabled;
			float duration;
			if (cooldownRandomizerEnabled != null && cooldownRandomizerEnabled.Value)
			{
				float num = OutwardSoftcoreMode.CooldownRandomizerMinHours?.Value ?? 24f;
				float num2 = OutwardSoftcoreMode.CooldownRandomizerMaxHours?.Value ?? 168f;
				duration = Random.Range(num, num2);
			}
			else
			{
				duration = OutwardSoftcoreMode.SaveCooldownHours?.Value ?? 24f;
			}
			BackupMetadataStore.WriteGameTime(uid, instanceTimestamp, gameTimeF);
			SetLastBackupGameTime(uid, gameTimeF);
			WriteCooldownDuration(uid, duration);
			EnforceBackupLimit(uid);
		}

		private static void EnforceBackupLimit(string uid)
		{
			string backupsDir = SoftcorePaths.GetBackupsDir(uid);
			if (!Directory.Exists(backupsDir))
			{
				return;
			}
			int num = OutwardSoftcoreMode.MaxBackups?.Value ?? 10;
			List<DirectoryInfo> list = (from d in new DirectoryInfo(backupsDir).GetDirectories()
				orderby d.Name descending
				select d).ToList();
			while (list.Count > num)
			{
				DirectoryInfo directoryInfo = list[list.Count - 1];
				try
				{
					directoryInfo.Delete(recursive: true);
					OutwardSoftcoreMode.LogMessage("Deleted old backup: " + directoryInfo.Name);
				}
				catch (Exception ex)
				{
					OutwardSoftcoreMode.LogMessage("Failed to delete old backup: " + ex.Message);
				}
				list.RemoveAt(list.Count - 1);
			}
		}

		private static HashSet<string> GetActiveCharacterUIDs()
		{
			HashSet<string> hashSet = new HashSet<string>();
			SaveManager instance = SaveManager.Instance;
			if (((instance != null) ? instance.CharacterSaves : null) == null)
			{
				return hashSet;
			}
			foreach (CharacterSaveInstanceHolder characterSafe in SaveManager.Instance.CharacterSaves)
			{
				if (characterSafe != null)
				{
					hashSet.Add(characterSafe.CharacterUID);
				}
			}
			return hashSet;
		}

		public static void RestoreOrphanedBackups()
		{
			string path = Path.Combine(SoftcorePaths.RootPath, "restored_instances.xml");
			if (File.Exists(path))
			{
				try
				{
					File.Delete(path);
					OutwardSoftcoreMode.LogMessage("Cleaned up legacy restored_instances.xml index");
				}
				catch (Exception ex)
				{
					OutwardSoftcoreMode.LogMessage("Failed to delete legacy index: " + ex.Message);
				}
			}
			if (!Directory.Exists(SoftcorePaths.BackupsPath))
			{
				OutwardSoftcoreMode.LogMessage("RestoreOrphanedBackups: no backups directory");
				return;
			}
			HashSet<string> activeCharacterUIDs = GetActiveCharacterUIDs();
			string[] directories = Directory.GetDirectories(SoftcorePaths.BackupsPath);
			OutwardSoftcoreMode.LogMessage($"RestoreOrphanedBackups: scanning {directories.Length} backup dirs, {activeCharacterUIDs.Count} active characters");
			int num = 0;
			string[] array = directories;
			foreach (string path2 in array)
			{
				string fileName = Path.GetFileName(path2);
				if (!string.IsNullOrEmpty(fileName) && !activeCharacterUIDs.Contains(fileName))
				{
					OutwardSoftcoreMode.LogMessage("RestoreOrphanedBackups: restoring " + fileName);
					RestoreCharacter(fileName);
					num++;
				}
			}
			OutwardSoftcoreMode.LogMessage($"RestoreOrphanedBackups: restored {num} characters");
			if (num > 0)
			{
				RefreshCharacterSelectionPanels();
			}
		}

		public static bool IsRestoredBackupCharacter(string uid)
		{
			XDocument val = LoadMetadataOrNull(uid);
			if (val == null)
			{
				return false;
			}
			XElement root = val.Root;
			XElement val2 = ((root != null) ? ((XContainer)root).Element(XName.op_Implicit("IsRestored")) : null);
			bool result = default(bool);
			return val2 != null && bool.TryParse(val2.Value, out result) && result;
		}

		public static void SetRestoredFlag(string uid)
		{
			string metadataPath = SoftcorePaths.GetMetadataPath(uid);
			if (!File.Exists(metadataPath))
			{
				OutwardSoftcoreMode.DebugLog("SetRestoredFlag: no metadata to flag for " + uid + " — skipping");
				return;
			}
			for (int i = 0; i < 2; i++)
			{
				ModifyMetadata(uid, delegate(XElement root)
				{
					//IL_0038: Unknown result type (might be due to invalid IL or missing references)
					//IL_0042: Expected O, but got Unknown
					XElement val = ((XContainer)root).Element(XName.op_Implicit("IsRestored"));
					if (val != null)
					{
						val.Value = "true";
					}
					else
					{
						((XContainer)root).Add((object)new XElement(XName.op_Implicit("IsRestored"), (object)"true"));
					}
				});
				if (IsRestoredBackupCharacter(uid))
				{
					return;
				}
			}
			OutwardSoftcoreMode.LogMessage("Failed to persist IsRestored flag for " + uid);
		}

		public static void ClearRestoredFlag(string uid)
		{
			if (!File.Exists(SoftcorePaths.GetMetadataPath(uid)))
			{
				return;
			}
			ModifyMetadata(uid, delegate(XElement root)
			{
				XElement val = ((XContainer)root).Element(XName.op_Implicit("IsRestored"));
				if (val != null)
				{
					val.Value = "false";
				}
			});
		}

		private static void RestoreCharacter(string uid)
		{
			string backupsDir = SoftcorePaths.GetBackupsDir(uid);
			if (!Directory.Exists(backupsDir))
			{
				return;
			}
			List<DirectoryInfo> list = (from d in new DirectoryInfo(backupsDir).GetDirectories()
				orderby d.Name descending
				select d).ToList();
			if (list.Count == 0)
			{
				return;
			}
			string characterSaveDirectory = GetCharacterSaveDirectory(uid);
			foreach (DirectoryInfo item in list)
			{
				string text = Path.Combine(characterSaveDirectory, item.Name);
				if (!Directory.Exists(text))
				{
					CopyDirectoryContents(item.FullName, text);
					OutwardSoftcoreMode.LogMessage("Restored backup " + item.Name + " for " + uid);
				}
			}
			SetRestoredFlag(uid);
			RegisterRestoredCharacter(uid);
		}

		private static void RegisterRestoredCharacter(string uid)
		{
			string characterSaveDirectory = GetCharacterSaveDirectory(uid);
			if (!Directory.Exists(characterSaveDirectory))
			{
				return;
			}
			CharacterSaveInstanceHolder val = CharacterSaveInstanceHolder.PrepareCharacterSaveInstanceHolder(uid, characterSaveDirectory);
			if (val == null)
			{
				OutwardSoftcoreMode.LogMessage("RegisterRestoredCharacter: PrepareCharacterSaveInstanceHolder returned null for " + uid);
				return;
			}
			object value = AccessTools.Field(typeof(SaveManager), "m_charSaves").GetValue(SaveManager.Instance);
			if (value != null)
			{
				MethodInfo methodInfo = AccessTools.Method(value.GetType(), "ContainsKey", (Type[])null, (Type[])null);
				MethodInfo methodInfo2 = AccessTools.Method(value.GetType(), "Remove", (Type[])null, (Type[])null);
				MethodInfo methodInfo3 = AccessTools.Method(value.GetType(), "Add", (Type[])null, (Type[])null);
				if ((bool)methodInfo.Invoke(value, new object[1] { uid }))
				{
					methodInfo2.Invoke(value, new object[1] { uid });
				}
				methodInfo3.Invoke(value, new object[2] { uid, val });
			}
		}

		private static void RefreshCharacterSelectionPanels()
		{
			CharacterSelectionPanel[] array = Resources.FindObjectsOfTypeAll<CharacterSelectionPanel>();
			CharacterSelectionPanel[] array2 = array;
			foreach (CharacterSelectionPanel val in array2)
			{
				if (((Behaviour)val).isActiveAndEnabled)
				{
					MethodInfo methodInfo = AccessTools.Method(typeof(CharacterSelectionPanel), "RefreshCharacterList", (Type[])null, (Type[])null);
					methodInfo.Invoke(val, null);
				}
			}
		}
	}
}
namespace OutwardSoftcoreMode.Patches
{
	[HarmonyPatch(typeof(CharacterSaveInstanceDisplay), "SetSaveInstance")]
	public class Patch_CharacterSaveInstanceDisplay_SetSaveInstance
	{
		private struct AreaNameLayout
		{
			public Vector2 anchorMin;

			public Vector2 anchorMax;

			public Vector2 pivot;

			public Vector2 anchoredPosition;

			public Vector2 sizeDelta;
		}

		private const string LabelName = "lblSaveSoftcore";

		private const float RightLabelWidth = 339.1f;

		private const float LabelHeight = 35f;

		private static readonly Dictionary<int, AreaNameLayout> _savedAreaNameLayout = new Dictionary<int, AreaNameLayout>();

		private static void Postfix(CharacterSaveInstanceDisplay __instance, SaveInstance _instance)
		{
			string uid = _instance?.SaveID;
			string instancePath = _instance?.InstancePath;
			Transform val = ((Component)__instance).transform.Find("Data");
			if (!((Object)(object)val == (Object)null))
			{
				if (SoftcoreSaveManager.IsRestoredBackupInstance(uid, instancePath))
				{
					SetupLayout(val);
					UpdateLabel(val, "Backup");
				}
				else
				{
					ResetLayout(val);
					HideLabel(val);
				}
			}
		}

		private static RectTransform GetAreaNameRectTransform(Transform data)
		{
			Transform val = data.Find("lblAreaName");
			return ((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<RectTransform>() : null;
		}

		private static void SaveOriginalAreaNameLayout(Transform data)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			int instanceID = ((Object)data).GetInstanceID();
			if (!_savedAreaNameLayout.ContainsKey(instanceID))
			{
				RectTransform areaNameRectTransform = GetAreaNameRectTransform(data);
				if (!((Object)(object)areaNameRectTransform == (Object)null))
				{
					_savedAreaNameLayout[instanceID] = new AreaNameLayout
					{
						anchorMin = areaNameRectTransform.anchorMin,
						anchorMax = areaNameRectTransform.anchorMax,
						pivot = areaNameRectTransform.pivot,
						anchoredPosition = areaNameRectTransform.anchoredPosition,
						sizeDelta = areaNameRectTransform.sizeDelta
					};
				}
			}
		}

		private static void SetupLayout(Transform data)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Unknown result type (might be due to invalid IL or missing references)
			RectTransform areaNameRectTransform = GetAreaNameRectTransform(data);
			if ((Object)(object)areaNameRectTransform != (Object)null)
			{
				SaveOriginalAreaNameLayout(data);
				areaNameRectTransform.anchorMin = new Vector2(1f, 1f);
				areaNameRectTransform.anchorMax = new Vector2(1f, 1f);
				areaNameRectTransform.pivot = new Vector2(1f, 1f);
				areaNameRectTransform.anchoredPosition = Vector2.zero;
				areaNameRectTransform.sizeDelta = new Vector2(339.1f, 35f);
			}
			Transform val = data.Find("lblSaveSoftcore");
			if ((Object)(object)val != (Object)null)
			{
				((Component)val).gameObject.SetActive(true);
				return;
			}
			Transform val2 = data.Find("lblTime");
			if (!((Object)(object)val2 == (Object)null))
			{
				GameObject val3 = SoftcoreColors.CreateSoftcoreLabel(((Component)val2).gameObject, data, "lblSaveSoftcore");
				RectTransform component = val3.GetComponent<RectTransform>();
				if ((Object)(object)component != (Object)null)
				{
					component.anchorMin = new Vector2(1f, 0f);
					component.anchorMax = new Vector2(1f, 0f);
					component.pivot = new Vector2(1f, 0f);
					component.anchoredPosition = Vector2.zero;
					component.sizeDelta = new Vector2(339.1f, 35f);
				}
			}
		}

		private static void UpdateLabel(Transform data, string text)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			Transform val = data.Find("lblSaveSoftcore");
			if (!((Object)(object)val == (Object)null))
			{
				Text componentInChildren = ((Component)val).GetComponentInChildren<Text>();
				if (!((Object)(object)componentInChildren == (Object)null))
				{
					componentInChildren.text = text;
					((Graphic)componentInChildren).color = SoftcoreColors.Purple;
					componentInChildren.alignment = (TextAnchor)5;
				}
			}
		}

		private static void ResetLayout(Transform data)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			int instanceID = ((Object)data).GetInstanceID();
			RectTransform areaNameRectTransform = GetAreaNameRectTransform(data);
			if (!((Object)(object)areaNameRectTransform == (Object)null) && _savedAreaNameLayout.TryGetValue(instanceID, out var value))
			{
				areaNameRectTransform.anchorMin = value.anchorMin;
				areaNameRectTransform.anchorMax = value.anchorMax;
				areaNameRectTransform.pivot = value.pivot;
				areaNameRectTransform.anchoredPosition = value.anchoredPosition;
				areaNameRectTransform.sizeDelta = value.sizeDelta;
				_savedAreaNameLayout.Remove(instanceID);
			}
		}

		private static void HideLabel(Transform data)
		{
			Transform val = data.Find("lblSaveSoftcore");
			if ((Object)(object)val != (Object)null)
			{
				((Component)val).gameObject.SetActive(false);
			}
		}
	}
	[HarmonyPatch(typeof(CharacterSaveSlot), "SetSave")]
	public class Patch_CharacterSaveSlot_SetSave
	{
		private static void Postfix(CharacterSaveSlot __instance, CharacterSaveInstanceHolder _saveContainer)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			string characterUID = _saveContainer.CharacterUID;
			Transform val = (Transform)AccessTools.Field(typeof(CharacterSaveSlot), "m_hardcoreFlag").GetValue(__instance);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			Transform val2 = val.parent.Find("lblSoftcore");
			if (!SoftcoreSaveManager.IsSoftcoreCharacter(characterUID))
			{
				if ((Object)(object)val2 != (Object)null)
				{
					((Component)val2).gameObject.SetActive(false);
				}
				return;
			}
			GameObject val3 = ((val2 != null) ? ((Component)val2).gameObject : null);
			if ((Object)(object)val3 == (Object)null)
			{
				val3 = SoftcoreColors.CreateSoftcoreLabel(((Component)val).gameObject, val.parent, "lblSoftcore");
			}
			val3.SetActive(true);
			Text componentInChildren = val3.GetComponentInChildren<Text>();
			if ((Object)(object)componentInChildren != (Object)null)
			{
				int permanentDeathCount = SoftcoreSaveManager.GetPermanentDeathCount(characterUID);
				componentInChildren.text = ((permanentDeathCount > 0) ? $"Softcore {permanentDeathCount}" : "Softcore");
				((Graphic)componentInChildren).color = SoftcoreColors.Purple;
			}
			((Component)val).gameObject.SetActive(false);
		}
	}
	[HarmonyPatch(typeof(CharacterSelectionPanel), "OnEnable")]
	public class Patch_CharacterSelectionPanel_OnEnable
	{
		private static void Postfix()
		{
			SoftcoreSaveManager.RestoreOrphanedBackups();
		}
	}
	[HarmonyPatch(typeof(CharacterSelectionPanel), "RefreshCharacterList")]
	public class Patch_CharacterSelectionPanel_RefreshCharacterList
	{
		private static void Postfix(CharacterSelectionPanel __instance)
		{
			if (((UIElement)__instance).PlayerID <= 0)
			{
				return;
			}
			FieldInfo fieldInfo = AccessTools.Field(typeof(CharacterSelectionPanel), "m_saveSlot");
			if (!(fieldInfo.GetValue(__instance) is IList list))
			{
				return;
			}
			SaveManager instance = SaveManager.Instance;
			IList<CharacterSaveInstanceHolder> list2 = ((instance != null) ? instance.CharacterSaves : null);
			if (list2 == null)
			{
				return;
			}
			for (int i = 0; i < list.Count && i < list2.Count; i++)
			{
				object obj = list[i];
				if (obj == null)
				{
					continue;
				}
				Type type = obj.GetType();
				PropertyInfo property = type.GetProperty("HarcoreMode");
				if (property == null)
				{
					continue;
				}
				bool flag = (bool)property.GetValue(obj, null);
				if (flag == CharacterManager.Instance.HardcoreMode)
				{
					continue;
				}
				CharacterSaveInstanceHolder obj2 = list2[i];
				string text = ((obj2 != null) ? obj2.CharacterUID : null);
				if (!string.IsNullOrEmpty(text) && SoftcoreSaveManager.IsSoftcoreCharacter(text))
				{
					PropertyInfo property2 = type.GetProperty("interactable");
					if (property2 != null)
					{
						property2.SetValue(obj, true, null);
					}
				}
			}
		}
	}
	[HarmonyPatch(typeof(Character), "LoadPlayerSave")]
	public class Patch_Character_LoadPlayerSave
	{
		private static void Postfix(Character __instance, PlayerSaveData _save)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			string text = UID.op_Implicit(__instance.UID);
			OutwardSoftcoreMode.IsCurrentGameSoftcore = SoftcoreSaveManager.IsSoftcoreCharacter(text);
			if (OutwardSoftcoreMode.IsCurrentGameSoftcore)
			{
				if (SoftcoreSaveManager.IsRestoredBackupCharacter(text))
				{
					OutwardSoftcoreMode.PendingCooldownUIDs.Add(text);
					SoftcoreSaveManager.ClearRestoredFlag(text);
					OutwardSoftcoreMode.DebugLog("Cooldown deferred for restored character " + text);
				}
				else if (SoftcoreSaveManager.GetLastBackupGameTime(text) < 0f)
				{
					float gameTimeF = EnvironmentConditions.GameTimeF;
					SoftcoreSaveManager.SetLastBackupGameTime(text, gameTimeF);
					OutwardSoftcoreMode.DebugLog($"Cooldown initialized to {gameTimeF:F2} for {text}");
				}
			}
		}
	}
	[HarmonyPatch(typeof(DefeatScenariosManager), "ActivateDefeatScenario")]
	public class Patch_DefeatScenariosManager_ActivateDefeatScenario
	{
		private static readonly List<string> _defeatedCharacterUIDs = new List<string>();

		private static bool _deathTriggered;

		private static bool _suppressedHardcore;

		private static bool _originalSupportHardcore;

		private static bool Prefix(DefeatScenario _scenario)
		{
			_defeatedCharacterUIDs.Clear();
			_deathTriggered = false;
			_suppressedHardcore = false;
			_originalSupportHardcore = false;
			CharacterManager instance = CharacterManager.Instance;
			AddIfDead((instance != null) ? instance.GetFirstLocalCharacter() : null);
			AddIfDead((instance != null) ? instance.GetSecondLocalCharacter() : null);
			if (_defeatedCharacterUIDs.Count == 0)
			{
				return true;
			}
			bool flag = false;
			HashSet<string> hashSet = new HashSet<string>();
			List<string> list = new List<string>();
			foreach (string defeatedCharacterUID in _defeatedCharacterUIDs)
			{
				if (!string.IsNullOrEmpty(defeatedCharacterUID) && hashSet.Add(defeatedCharacterUID) && SoftcoreSaveManager.IsSoftcoreCharacter(defeatedCharacterUID))
				{
					flag = true;
					list.Add(defeatedCharacterUID);
				}
			}
			if (flag)
			{
				EventBusPublisher.PublishDeathRollBefore(new List<string>(list));
				int num = OutwardSoftcoreMode.DeathChance?.Value ?? 20;
				int num2 = Random.Range(0, 100);
				if (num2 < num)
				{
					foreach (string item in list)
					{
						int permanentDeathCount = SoftcoreSaveManager.GetPermanentDeathCount(item);
						SoftcoreSaveManager.IncrementPermanentDeathCount(item);
						int permanentDeathCount2 = SoftcoreSaveManager.GetPermanentDeathCount(item);
						OutwardSoftcoreMode.LogMessage($"Softcore death for {item} — permanent death count: {permanentDeathCount} -> {permanentDeathCount2}");
					}
					_deathTriggered = true;
				}
				EventBusPublisher.PublishDeathRollAfter(_deathTriggered, _deathTriggered ? new List<string>(list) : new List<string>());
			}
			if (_deathTriggered)
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(DefeatScenariosManager), "DefeatHardcoreDeath", (Type[])null, (Type[])null);
				if (methodInfo != null)
				{
					methodInfo.Invoke(DefeatScenariosManager.Instance, null);
				}
				else
				{
					OutwardSoftcoreMode.LogMessage("DefeatHardcoreDeath method not found — setting HardcoreDeathTriggered directly");
					CharacterManager.Instance.HardcoreDeathTriggered = true;
				}
				return false;
			}
			if (flag && (Object)(object)_scenario != (Object)null)
			{
				_suppressedHardcore = true;
				_originalSupportHardcore = _scenario.SupportHardcore;
				_scenario.SupportHardcore = false;
			}
			return true;
		}

		private static void AddIfDead(Character c)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)c != (Object)null && (Object)(object)c.Stats != (Object)null && c.Stats.CurrentHealth <= 0f)
			{
				_defeatedCharacterUIDs.Add(UID.op_Implicit(c.UID));
			}
		}

		private static void Postfix(DefeatScenario _scenario)
		{
			if (_suppressedHardcore && (Object)(object)_scenario != (Object)null)
			{
				_scenario.SupportHardcore = _originalSupportHardcore;
			}
			_defeatedCharacterUIDs.Clear();
			_deathTriggered = false;
			_suppressedHardcore = false;
			_originalSupportHardcore = false;
		}
	}
	[HarmonyPatch(typeof(MainScreen), "OnSplitModeChosen")]
	public class Patch_MainScreen_DifficultySelection
	{
		private const string SoftcoreDisclaimer = "Are you sure you want to play in softcore mode? \n<color=#A855F7>In softcore mode, when you are defeated there is a chance that your character dies PERMANENTLY, but you can create manual save backups to restore if needed.</color>\n\nAdditional note: When playing coop, players must be in the same mode to play together.";

		private static bool Prefix(MainScreen __instance, bool _splitActive)
		{
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Expected O, but got Unknown
			//IL_00eb: Expected O, but got Unknown
			//IL_00eb: Expected O, but got Unknown
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Expected O, but got Unknown
			//IL_00aa: Expected O, but got Unknown
			//IL_00aa: Expected O, but got Unknown
			OutwardSoftcoreMode.PendingSoftcoreCount = 0;
			OutwardSoftcoreMode.IsCurrentGameSoftcore = false;
			string loc = LocalizationManager.Instance.GetLoc("CharacterCreation_Mode_GameMode");
			string[] array = new string[3] { "General_DifficultyNormal", "Softcore", "General_DifficultyHardcore" };
			MethodInfo onHardcore = AccessTools.Method(typeof(MainScreen), "OnHardcoreModeChosen", (Type[])null, (Type[])null);
			if (!_splitActive)
			{
				((UIElement)__instance).m_characterUI.MessagePanel.Show(loc, (string)null, array, (UnityAction)delegate
				{
					__instance.ShowCharacterCreation(false);
				}, (UnityAction)delegate
				{
					OnSoftcoreModeChosen(__instance, splitActive: false);
				}, (UnityAction)delegate
				{
					onHardcore.Invoke(__instance, new object[1] { false });
				});
			}
			else
			{
				((UIElement)__instance).m_characterUI.MessagePanel.Show(loc, (string)null, array, (UnityAction)delegate
				{
					__instance.ShowCharacterCreation2P(false);
				}, (UnityAction)delegate
				{
					OnSoftcoreModeChosen(__instance, splitActive: true);
				}, (UnityAction)delegate
				{
					onHardcore.Invoke(__instance, new object[1] { true });
				});
			}
			return false;
		}

		private static void OnSoftcoreModeChosen(MainScreen instance, bool splitActive)
		{
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Expected O, but got Unknown
			if (Object.op_Implicit((Object)(object)Global.AudioManager))
			{
				Global.AudioManager.PlaySound((Sounds)90001, 0f, 1f, 1f, 1f, 1f);
			}
			((UIElement)instance).m_characterUI.MessagePanel.Show("Are you sure you want to play in softcore mode? \n<color=#A855F7>In softcore mode, when you are defeated there is a chance that your character dies PERMANENTLY, but you can create manual save backups to restore if needed.</color>\n\nAdditional note: When playing coop, players must be in the same mode to play together.", (string)null, MakeConfirmAction(splitActive, instance), (UnityAction)delegate
			{
				instance.m_optionButtonsPanel.Focus();
			}, true, -1f, (UnityAction)null);
		}

		private static UnityAction MakeConfirmAction(bool splitActive, MainScreen instance)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Expected O, but got Unknown
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			int count = ((!splitActive) ? 1 : 2);
			if (splitActive)
			{
				return (UnityAction)delegate
				{
					OutwardSoftcoreMode.PendingSoftcoreCount = count;
					instance.ShowCharacterCreation2P(true);
				};
			}
			return (UnityAction)delegate
			{
				OutwardSoftcoreMode.PendingSoftcoreCount = count;
				instance.ShowCharacterCreation(true);
			};
		}
	}
	[HarmonyPatch(typeof(PauseMenu), "Show")]
	public class Patch_PauseMenu_AddSaveButton
	{
		private static void Postfix(PauseMenu __instance)
		{
			//IL_0017: 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)
			Character localCharacter = ((UIElement)__instance).LocalCharacter;
			UID? val = ((localCharacter != null) ? new UID?(localCharacter.UID) : ((UID?)null));
			string text = (val.HasValue ? UID.op_Implicit(val.GetValueOrDefault()) : null);
			bool flag = !string.IsNullOrEmpty(text) && SoftcoreSaveManager.IsSoftcoreCharacter(text);
			Transform buttonsContainer = GetButtonsContainer(__instance);
			if ((Object)(object)buttonsContainer == (Object)null)
			{
				return;
			}
			Transform val2 = FindSoftcoreButton(buttonsContainer);
			if (flag)
			{
				if ((Object)(object)val2 == (Object)null)
				{
					CreateButton(buttonsContainer);
					val2 = FindSoftcoreButton(buttonsContainer);
				}
				if ((Object)(object)val2 != (Object)null)
				{
					((Component)val2).gameObject.SetActive(true);
					RefreshSoftcoreSaveButton(__instance);
				}
			}
			else if ((Object)(object)val2 != (Object)null)
			{
				((Component)val2).gameObject.SetActive(false);
			}
		}

		private static Transform GetButtonsContainer(PauseMenu menu)
		{
			object? obj = AccessTools.Field(typeof(PauseMenu), "m_hideOnPauseButtons")?.GetValue(menu);
			GameObject val = (GameObject)((obj is GameObject) ? obj : null);
			return (val != null) ? val.transform : null;
		}

		private static Transform FindSoftcoreButton(Transform container)
		{
			return (container != null) ? container.Find("btnSaveSoftcore") : null;
		}

		private static Transform FindSoftcoreButton(PauseMenu menu)
		{
			Transform buttonsContainer = GetButtonsContainer(menu);
			return ((Object)(object)buttonsContainer != (Object)null) ? FindSoftcoreButton(buttonsContainer) : null;
		}

		internal static void RefreshSoftcoreSaveButton(PauseMenu menu)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			Character localCharacter = ((UIElement)menu).LocalCharacter;
			UID? val = ((localCharacter != null) ? new UID?(localCharacter.UID) : ((UID?)null));
			string text = (val.HasValue ? UID.op_Implicit(val.GetValueOrDefault()) : null);
			if (!string.IsNullOrEmpty(text) && SoftcoreSaveManager.IsSoftcoreCharacter(text))
			{
				Transform val2 = FindSoftcoreButton(menu);
				if (!((Object)(object)val2 == (Object)null))
				{
					UpdateButtonText(((Component)val2).gameObject, text);
				}
			}
		}

		private static GameObject CreateButton(Transform parent)
		{
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Expected O, but got Unknown
			Transform obj = parent.Find("btnSave");
			GameObject val = ((obj != null) ? ((Component)obj).gameObject : null);
			if ((Object)(object)val == (Object)null)
			{
				OutwardSoftcoreMode.LogMessage("Cannot create softcore save button: btnSave template not found");
				return null;
			}
			GameObject val2 = Object.Instantiate<GameObject>(val, parent);
			((Object)val2).name = "btnSaveSoftcore";
			val2.SetActive(true);
			SoftcoreColors.DestroyLocalize(val2);
			Text componentInChildren = val2.GetComponentInChildren<Text>();
			if ((Object)(object)componentInChildren != (Object)null)
			{
				((Graphic)componentInChildren).color = SoftcoreColors.Purple;
			}
			Button component = val2.GetComponent<Button>();
			if ((Object)(object)component != (Object)null)
			{
				((UnityEventBase)component.onClick).RemoveAllListeners();
				((UnityEvent)component.onClick).AddListener(new UnityAction(OnSaveClicked));
			}
			return val2;
		}

		private static void OnSaveClicked()
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			OutwardSoftcoreMode.PendingManualBackupUIDs.Clear();
			foreach (SplitPlayer localPlayer in SplitScreenManager.Instance.LocalPlayers)
			{
				if ((Object)(object)localPlayer.AssignedCharacter != (Object)null)
				{
					string text = UID.op_Implicit(localPlayer.AssignedCharacter.UID);
					if (SoftcoreSaveManager.IsSoftcoreCharacter(text) && SoftcoreSaveManager.CanBackupNow(text))
					{
						OutwardSoftcoreMode.PendingManualBackupUIDs.Add(text);
					}
				}
			}
			if (OutwardSoftcoreMode.PendingManualBackupUIDs.Count <= 0)
			{
				return;
			}
			foreach (string pendingManualBackupUID in OutwardSoftcoreMode.PendingManualBackupUIDs)
			{
				EventBusPublisher.PublishSaveBackupBefore(pendingManualBackupUID);
			}
			PauseMenu[] array = Resources.FindObjectsOfTypeAll<PauseMenu>();
			foreach (PauseMenu val in array)
			{
				if (!((Behaviour)val).isActiveAndEnabled)
				{
					continue;
				}
				Transform val2 = FindSoftcoreButton(val);
				if (!((Object)(object)val2 == (Object)null))
				{
					Button component = ((Component)val2).GetComponent<Button>();
					if ((Object)(object)component != (Object)null)
					{
						((Selectable)component).interactable = false;
					}
				}
			}
			SaveManager.Instance.Save();
		}

		private static void UpdateButtonText(GameObject btnObj, string uid)
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			Text componentInChildren = btnObj.GetComponentInChildren<Text>();
			if (!((Object)(object)componentInChildren == (Object)null))
			{
				Button component = btnObj.GetComponent<Button>();
				bool flag = SoftcoreSaveManager.CanBackupNow(uid);
				if (flag)
				{
					componentInChildren.text = "Save Backup";
					((Graphic)componentInChildren).color = SoftcoreColors.Purple;
				}
				else
				{
					float remainingCooldownTime = SoftcoreSaveManager.GetRemainingCooldownTime(uid);
					componentInChildren.text = "Save " + FormatCooldown(remainingCooldownTime);
					((Graphic)componentInChildren).color = SoftcoreColors.Purple;
				}
				if ((Object)(object)component != (Object)null)
				{
					((Selectable)component).interactable = flag;
				}
			}
		}

		private static string FormatCooldown(float gameHours)
		{
			int num = Mathf.CeilToInt(gameHours * 60f);
			int num2 = num / 1440;
			int num3 = num % 1440 / 60;
			int num4 = num % 60;
			return (num2 > 0) ? $"{num2}:{num3:D2}:{num4:D2}" : $"{num3}:{num4:D2}";
		}
	}
	[HarmonyPatch(typeof(PauseMenu), "Update")]
	public class Patch_PauseMenu_Update
	{
		private static float _nextRefresh;

		private static void Postfix(PauseMenu __instance)
		{
			if (!((UIElement)__instance).IsDisplayed)
			{
				_nextRefresh = 0f;
			}
			else if (!(Time.unscaledTime < _nextRefresh))
			{
				_nextRefresh = Time.unscaledTime + 0.5f;
				Patch_PauseMenu_AddSaveButton.RefreshSoftcoreSaveButton(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(SaveInstance), "Save")]
	public class Patch_SaveInstance_Save
	{
		private static void Postfix(SaveInstance __instance, bool _saveChar, bool _saveWorld)
		{
			string saveID = __instance.SaveID;
			if (!string.IsNullOrEmpty(saveID) && OutwardSoftcoreMode.PendingManualBackupUIDs.Contains(saveID))
			{
				string instancePath = __instance.InstancePath;
				if (string.IsNullOrEmpty(instancePath))
				{
					OutwardSoftcoreMode.LogMessage("Cannot backup: missing InstancePath");
					return;
				}
				if (!SoftcoreSaveManager.IsSoftcoreCharacter(saveID))
				{
					OutwardSoftcoreMode.LogMessage("Cannot backup: character is not softcore");
					return;
				}
				string localCharacterNameByUID = GetLocalCharacterNameByUID(saveID);
				SoftcoreSaveManager.EnsureMetadataExists(saveID, localCharacterNameByUID);
				SoftcoreSaveManager.CreateBackup(saveID, instancePath);
				EventBusPublisher.PublishSaveBackupAfter(saveID);
				OutwardSoftcoreMode.PendingManualBackupUIDs.Remove(saveID);
				OutwardSoftcoreMode.LogMessage("Manual softcore backup completed");
			}
		}

		private static string GetLocalCharacterNameByUID(string uid)
		{
			//IL_0033: 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_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			foreach (SplitPlayer localPlayer in SplitScreenManager.Instance.LocalPlayers)
			{
				Character assignedCharacter = localPlayer.AssignedCharacter;
				UID? val = ((assignedCharacter != null) ? new UID?(assignedCharacter.UID) : ((UID?)null));
				UID val2 = UID.op_Implicit(uid);
				if (val.HasValue && (!val.HasValue || val.GetValueOrDefault() == val2))
				{
					return localPlayer.AssignedCharacter.Name;
				}
			}
			return "Unknown";
		}
	}
	[HarmonyPatch(typeof(SaveManager), "ConfirmAddNewSave")]
	public class Patch_SaveManager_ConfirmAddNewSave
	{
		private static void Postfix(CharacterSave _newSave)
		{
			if (OutwardSoftcoreMode.PendingSoftcoreCount <= 0 && !OutwardSoftcoreMode.IsCurrentGameSoftcore)
			{
				return;
			}
			string text = ((CharacterSaveData)(_newSave?.PSave?)).UID;
			if (string.IsNullOrEmpty(text))
			{
				OutwardSoftcoreMode.LogMessage("Cannot write softcore metadata: no UID in new save");
				OutwardSoftcoreMode.PendingSoftcoreCount = 0;
				return;
			}
			SoftcoreSaveManager.WriteMetadata(text, _newSave.PSave.Name);
			OutwardSoftcoreMode.LogMessage("Softcore metadata written for " + _newSave.PSave.Name + " (" + text + ")");
			OutwardSoftcoreMode.IsCurrentGameSoftcore = true;
			OutwardSoftcoreMode.PendingManualBackupUIDs.Add(text);
			if (OutwardSoftcoreMode.PendingSoftcoreCount > 0)
			{
				OutwardSoftcoreMode.PendingSoftcoreCount--;
			}
		}
	}
	[HarmonyPatch(typeof(SaveManager), "RetrieveCharacterSaves")]
	public class Patch_SaveManager_RetrieveCharacterSaves
	{
		private static void Postfix()
		{
			SoftcoreSaveManager.RestoreOrphanedBackups();
		}
	}
}
namespace OutwardSoftcoreMode.Events
{
	public enum EventName
	{
		SaveBackupBefore,
		SaveBackupAfter,
		DeathRollBefore,
		DeathRollAfter
	}
	public enum EventParam
	{
		CallerUID,
		SoftcoreUIDs,
		RollResult,
		AffectedUIDs
	}
	public static class EventBusKeys
	{
		private static readonly Dictionary<EventParam, string> ParamNames = new Dictionary<EventParam, string>
		{
			[EventParam.CallerUID] = "callerUID",
			[EventParam.SoftcoreUIDs] = "softcoreUIDs",
			[EventParam.RollResult] = "rollResult",
			[EventParam.AffectedUIDs] = "affectedUIDs"
		};

		public static string GetEventName(EventName name)
		{
			return name.ToString();
		}

		public static string GetParamName(EventParam param)
		{
			if (!ParamNames.TryGetValue(param, out var value))
			{
				throw new ArgumentOutOfRangeException("param");
			}
			return value;
		}
	}
	public static class EventBusPublisher
	{
		public static void PublishSaveBackupBefore(string callerUID)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected O, but got Unknown
			//IL_0017: Expected O, but got Unknown
			EventPayload val = new EventPayload { };
			string paramName;
			((Dictionary<string, object>)val)[paramName] = callerUID;
			EventPayload val2 = val;
			EventBus.Publish("gymmed.softcore_mode_*", EventBusKeys.GetEventName(EventName.SaveBackupBefore), val2);
		}

		public static void PublishSaveBackupAfter(string callerUID)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected O, but got Unknown
			//IL_0017: Expected O, but got Unknown
			EventPayload val = new EventPayload { };
			string paramName;
			((Dictionary<string, object>)val)[paramName] = callerUID;
			EventPayload val2 = val;
			EventBus.Publish("gymmed.softcore_mode_*", EventBusKeys.GetEventName(EventName.SaveBackupAfter), val2);
		}

		public static void PublishDeathRollBefore(List<string> softcoreUIDs)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected O, but got Unknown
			//IL_0017: Expected O, but got Unknown
			EventPayload val = new EventPayload { };
			string paramName;
			((Dictionary<string, object>)val)[paramName] = softcoreUIDs;
			EventPayload val2 = val;
			EventBus.Publish("gymmed.softcore_mode_*", EventBusKeys.GetEventName(EventName.DeathRollBefore), val2);
		}

		public static void PublishDeathRollAfter(bool rollResult, List<string> affectedUIDs)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected O, but got Unknown
			//IL_002c: Expected O, but got Unknown
			EventPayload val = new EventPayload { };
			string paramName;
			((Dictionary<string, object>)val)[paramName] = rollResult;
			string paramName2 = EventBusKeys.GetParamName(EventParam.AffectedUIDs);
			((Dictionary<string, object>)val)[paramName2] = affectedUIDs;
			EventPayload val2 = val;
			EventBus.Publish("gymmed.softcore_mode_*", EventBusKeys.GetEventName(EventName.DeathRollAfter), val2);
		}
	}
	public static class EventBusRegister
	{
		public static void RegisterEvents()
		{
			EventBus.RegisterEvent("gymmed.softcore_mode_*", EventBusKeys.GetEventName(EventName.SaveBackupBefore), "Fired before a manual backup is created.", new(string, Type, string)[1] { (EventBusKeys.GetParamName(EventParam.CallerUID), typeof(string), "The UID of the character whose save triggered the backup.") });
			EventBus.RegisterEvent("gymmed.softcore_mode_*", EventBusKeys.GetEventName(EventName.SaveBackupAfter), "Fired after a manual backup is created.", new(string, Type, string)[1] { (EventBusKeys.GetParamName(EventParam.CallerUID), typeof(string), "The UID of the character whose save was backed up.") });
			EventBus.RegisterEvent("gymmed.softcore_mode_*", EventBusKeys.GetEventName(EventName.DeathRollBefore), "Fired before the death roll is made for softcore characters.", new(string, Type, string)[1] { (EventBusKeys.GetParamName(EventParam.SoftcoreUIDs), typeof(List<string>), "List of UIDs for all softcore characters at 0 HP.") });
			EventBus.RegisterEvent("gymmed.softcore_mode_*", EventBusKeys.GetEventName(EventName.DeathRollAfter), "Fired after the death roll is made for softcore characters.", new(string, Type, string)[2]
			{
				(EventBusKeys.GetParamName(EventParam.RollResult), typeof(bool), "True if death was triggered, false if survived."),
				(EventBusKeys.GetParamName(EventParam.AffectedUIDs), typeof(List<string>), "List of UIDs whose death count was incremented (empty if survived).")
			});
		}
	}
}
namespace OutwardSoftcoreMode.BepInEx.Configs
{
	public static class BackupsConfigs
	{
		public static ConfigEntry<int> MaxBackups;

		public static void Init(BaseUnityPlugin plugin)
		{
			MaxBackups = plugin.Config.Bind<int>("Backups", "MaxBackups", 10, "Maximum number of backup instances kept per character. Oldest deleted when exceeded.");
		}
	}
	public static class CooldownRandomizerConfigs
	{
		public static ConfigEntry<bool> CooldownRandomizerEnabled;

		public static ConfigEntry<float> CooldownRandomizerMinHours;

		public static ConfigEntry<float> CooldownRandomizerMaxHours;

		public static void Init(BaseUnityPlugin plugin)
		{
			CooldownRandomizerEnabled = plugin.Config.Bind<bool>("Backup save cooldown randomizer", "CooldownRandomizerEnabled", true, "Enable random cooldown range for backup saves.");
			CooldownRandomizerMinHours = plugin.Config.Bind<float>("Backup save cooldown randomizer", "CooldownRandomizerMinHours", 24f, "Minimum random cooldown in game hours.");
			CooldownRandomizerMaxHours = plugin.Config.Bind<float>("Backup save cooldown randomizer", "CooldownRandomizerMaxHours", 168f, "Maximum random cooldown in game hours.");
		}
	}
	public static class DeathChanceConfigs
	{
		public static ConfigEntry<int> DeathChance;

		public static void Init(BaseUnityPlugin plugin)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			DeathChance = plugin.Config.Bind<int>("Defeat", "DeathChance", 20, new ConfigDescription("Permanent death chance on defeat (percentage). Minimum 20 (like hardcore), maximum 100.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(20, 100), Array.Empty<object>()));
		}
	}
	public static class SaveCooldownConfigs
	{
		public static ConfigEntry<float> SaveCooldownHours;

		public static void Init(BaseUnityPlugin plugin)
		{
			SaveCooldownHours = plugin.Config.Bind<float>("Backups", "SaveCooldownHours", 24f, "Minimum game hours between manual backups. Set to 0 to disable cooldown.");
		}
	}
}