#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 PendingManualBackupUIDs = new HashSet(); internal static HashSet PendingCooldownUIDs = new HashSet(); public static ConfigEntry MaxBackups => BackupsConfigs.MaxBackups; public static ConfigEntry SaveCooldownHours => SaveCooldownConfigs.SaveCooldownHours; public static ConfigEntry DeathChance => DeathChanceConfigs.DeathChance; public static ConfigEntry CooldownRandomizerEnabled => CooldownRandomizerConfigs.CooldownRandomizerEnabled; public static ConfigEntry CooldownRandomizerMinHours => CooldownRandomizerConfigs.CooldownRandomizerMinHours; public static ConfigEntry 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 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(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(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 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 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 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 GetActiveCharacterUIDs() { HashSet hashSet = new HashSet(); 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 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 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[] 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 _savedAreaNameLayout = new Dictionary(); 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() : 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(); 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(); 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(); 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 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 _defeatedCharacterUIDs = new List(); 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 hashSet = new HashSet(); List list = new List(); 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(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(list) : new List()); } 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? \nIn 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.\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? \nIn 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.\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(val, parent); ((Object)val2).name = "btnSaveSoftcore"; val2.SetActive(true); SoftcoreColors.DestroyLocalize(val2); Text componentInChildren = val2.GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null) { ((Graphic)componentInChildren).color = SoftcoreColors.Purple; } Button component = val2.GetComponent