using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("0.0.0.0")] namespace TreasureMapOverhaul; [HarmonyPatch(typeof(ItemDatabase), "GetItemByID", new Type[] { typeof(string) })] public static class Bank_GetItemByID_Fallback { private const string TornMapId = "99999999"; private const string PreferredAssetName = "GEN - A Torn Map"; public static void Postfix(ItemDatabase __instance, string __0, ref Item __result) { try { if (__0 != "99999999" || ((Object)(object)__result != (Object)null && !IsEmpty(__result)) || !TmoAssets.EnsureLoaded()) { return; } Item val = null; if (!string.IsNullOrWhiteSpace("GEN - A Torn Map")) { val = TmoAssets.Load("GEN - A Torn Map"); } if ((Object)(object)val == (Object)null) { val = ((IEnumerable)TmoAssets.LoadAll()).FirstOrDefault((Func)((Item i) => (Object)(object)i != (Object)null && ((BaseScriptableObject)i).Id == "99999999")); } if (!((Object)(object)val == (Object)null)) { ((BaseScriptableObject)val).Id = "99999999"; List list = (__instance.ItemDB ?? Array.Empty()).ToList(); list.RemoveAll((Item i) => (Object)(object)i != (Object)null && ((BaseScriptableObject)i).Id == "99999999"); list.Add(val); __instance.ItemDB = list.ToArray(); __result = val; } } catch (Exception arg) { Plugin.Log.LogError((object)$"[TMO] BankFallback: Exception {arg}"); } } private static bool IsEmpty(Item item) { try { return item == GameData.PlayerInv.Empty || string.Equals(item.ItemName, "Empty", StringComparison.OrdinalIgnoreCase); } catch { return false; } } } [HarmonyPatch(typeof(GlobalBank), "LoadBank")] public static class BankPatch { private static readonly string[] OldMapNames = new string[4] { "GEN - Torn Map Top Left", "GEN - Torn Map Top Right", "GEN - Torn Map Bottom Left", "GEN - Torn Map Bottom Right" }; private const string NewTornMapName = "GEN - A Torn Map"; [HarmonyPostfix] public static void OnBankLoad(GlobalBank __instance) { try { if ((Object)(object)__instance == (Object)null) { Plugin.Log.LogError((object)"[TMO] BankPatch: GlobalBank instance is null."); return; } if (__instance.StoredItems == null || __instance.Quantities == null) { Plugin.Log.LogError((object)"[TMO] BankPatch: StoredItems or Quantities is null."); return; } Item val = GameData.ItemDB?.ItemDB?.FirstOrDefault((Func)((Item i) => (Object)(object)i != (Object)null && ((Object)i).name == "GEN - A Torn Map")); if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning((object)"[TMO] BankPatch: 'GEN - A Torn Map' not found in ItemDB; skipping bank conversion."); return; } int num = 0; int num2 = Math.Min(__instance.StoredItems.Count, __instance.Quantities.Count); for (int j = 0; j < num2; j++) { Item val2 = __instance.StoredItems[j]; if ((Object)(object)val2 != (Object)null && OldMapNames.Contains(((Object)val2).name)) { num += Math.Max(0, __instance.Quantities[j]); __instance.StoredItems[j] = val; } } if (num <= 0) { return; } try { __instance.DisplayBankPage(); } catch (Exception arg) { Plugin.Log.LogWarning((object)$"[TMO] BankPatch: UI refresh failed after convert: {arg}"); } try { __instance.SaveBank(); } catch (Exception arg2) { Plugin.Log.LogError((object)$"[TMO] BankPatch: SaveBank failed after convert: {arg2}"); } } catch (Exception arg3) { Plugin.Log.LogError((object)$"[TMO] BankPatch: Exception: {arg3}"); } } } [HarmonyPatch(typeof(CharSelectManager), "Play")] public static class InventoryPatch { private static readonly string[] OldMapNames = new string[4] { "GEN - Torn Map Top Left", "GEN - Torn Map Top Right", "GEN - Torn Map Bottom Left", "GEN - Torn Map Bottom Right" }; private const string NewTornMapName = "GEN - A Torn Map"; [HarmonyPostfix] public static void OnCharacterEnter() { try { Inventory playerInv = GameData.PlayerInv; if ((Object)(object)playerInv == (Object)null) { Plugin.Log.LogError((object)"[TMO] InventoryPatch: PlayerInv is null."); return; } Item val = GameData.ItemDB?.ItemDB?.FirstOrDefault((Func)((Item i) => (Object)(object)i != (Object)null && ((Object)i).name == "GEN - A Torn Map")); if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning((object)"[TMO] InventoryPatch: New map 'GEN - A Torn Map' not found in ItemDB; skipping migration."); return; } List storedSlots = playerInv.StoredSlots; if (storedSlots == null) { Plugin.Log.LogError((object)"[TMO] InventoryPatch: StoredSlots is null."); return; } int num = 0; for (int j = 0; j < storedSlots.Count; j++) { ItemIcon val2 = storedSlots[j]; if (!((Object)(object)val2 == (Object)null)) { Item myItem = val2.MyItem; if ((Object)(object)myItem != (Object)null && OldMapNames.Contains(((Object)myItem).name)) { num += Math.Max(1, val2.Quantity); val2.MyItem = playerInv.Empty; val2.Quantity = 1; val2.UpdateSlotImage(); } } } if (num <= 0) { return; } int num2 = 0; for (int k = 0; k < num; k++) { if (playerInv.AddItemToInv(val)) { num2++; } } } catch (Exception arg) { Plugin.Log.LogError((object)$"[TMO] InventoryPatch exception: {arg}"); } } } [HarmonyPatch(typeof(ItemDatabase), "Start")] public static class ItemDBPatch { private const string TornMapId = "99999999"; private const string PreferredAssetName = ""; [HarmonyPostfix] public static void InjectCustomItem(ItemDatabase __instance) { try { if ((Object)(object)__instance == (Object)null || __instance.ItemDB == null) { Plugin.Log.LogError((object)"[TMO] ItemDBPatch: ItemDatabase or ItemDB is null."); } else { if (!TmoAssets.EnsureLoaded()) { return; } Item val = null; if (!string.IsNullOrWhiteSpace("")) { val = TmoAssets.Load(""); } if ((Object)(object)val == (Object)null) { val = ((IEnumerable)TmoAssets.LoadAll()).FirstOrDefault((Func)((Item i) => (Object)(object)i != (Object)null && ((BaseScriptableObject)i).Id == "99999999")); } if ((Object)(object)val == (Object)null) { Plugin.Log.LogError((object)"[TMO] Could not find custom Item in bundle (set PreferredAssetName or ensure Id == 99999999)."); return; } ((BaseScriptableObject)val).Id = "99999999"; List list = __instance.ItemDB.ToList(); list.RemoveAll((Item i) => (Object)(object)i != (Object)null && ((BaseScriptableObject)i).Id == "99999999"); list.Add(val); __instance.ItemDB = list.ToArray(); if (GameData.GM?.Maps != null) { GameData.GM.Maps.Clear(); GameData.GM.Maps.Add(val); } else { Plugin.Log.LogWarning((object)"[TMO] GM.Maps not available."); } } } catch (Exception arg) { Plugin.Log.LogError((object)$"[TMO] ItemDBPatch failed: {arg}"); } } } internal static class TmoIds { internal const string TornMapId = "99999999"; internal const string PreferredAssetName = ""; } [HarmonyPatch(typeof(ItemDatabase), "GetItemByID", new Type[] { typeof(string) })] public static class ItemDB_GetItemByID_Fallback { public static void Postfix(ItemDatabase __instance, string __0, ref Item __result) { try { if (!string.Equals(__0, "99999999", StringComparison.Ordinal) || ((Object)(object)__result != (Object)null && !IsEmpty(__result)) || !TmoAssets.EnsureLoaded()) { return; } Item val = null; if (!string.IsNullOrWhiteSpace("")) { val = TmoAssets.Load(""); } if ((Object)(object)val == (Object)null) { val = ((IEnumerable)TmoAssets.LoadAll()).FirstOrDefault((Func)((Item i) => (Object)(object)i != (Object)null && ((BaseScriptableObject)i).Id == "99999999")); } if ((Object)(object)val == (Object)null) { return; } ((BaseScriptableObject)val).Id = "99999999"; List list = (__instance.ItemDB ?? Array.Empty()).ToList(); list.RemoveAll((Item i) => (Object)(object)i != (Object)null && ((BaseScriptableObject)i).Id == "99999999"); list.Add(val); __instance.ItemDB = list.ToArray(); __result = val; try { if (GameData.GM?.Maps != null) { GameData.GM.Maps.Clear(); GameData.GM.Maps.Add(val); } } catch { } } catch (Exception arg) { Plugin.Log.LogWarning((object)$"[TMO] GetItemByID fallback failed: {arg}"); } } private static bool IsEmpty(Item item) { try { return item == GameData.PlayerInv.Empty || string.Equals(item.ItemName, "Empty", StringComparison.OrdinalIgnoreCase); } catch { return false; } } } [HarmonyPatch(typeof(LootTable), "InitLootTable")] public static class LootTable_InitLootTable_MapChance_Transpiler { [HarmonyTranspiler] public static IEnumerable Transpiler(IEnumerable instructions) { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Expected O, but got Unknown List list = new List(instructions); List list2 = new List(list.Count); bool flag = false; int num = 0; while (num < list.Count) { CodeInstruction val = list[num]; if (!flag && val.opcode == OpCodes.Ldc_R4 && val.operand is float num2 && Mathf.Approximately(num2, 0.0125f)) { list2.Add(new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(LootTable_InitLootTable_MapChance_Transpiler), "ConfigChance", (Type[])null, (Type[])null))); int num3 = num + 1; if (num3 < list.Count && IsLoadLike(list[num3])) { num3++; } if (num3 < list.Count && list[num3].opcode == OpCodes.Mul) { num3++; } num = num3; flag = true; } else { list2.Add(val); num++; } } if (!flag) { Plugin.Log.LogWarning((object)"[TMO] MapChance Transpiler: 0.0125f constant not found; map gate not patched."); } return list2; } private static bool IsLoadLike(CodeInstruction ci) { OpCode opcode = ci.opcode; return opcode == OpCodes.Ldloc || opcode == OpCodes.Ldloc_S || opcode == OpCodes.Ldloc_0 || opcode == OpCodes.Ldloc_1 || opcode == OpCodes.Ldloc_2 || opcode == OpCodes.Ldloc_3 || opcode == OpCodes.Ldsfld || opcode == OpCodes.Ldfld || opcode == OpCodes.Call; } public static float ConfigChance() { try { float normalizedChance = Plugin.GetNormalizedChance(Plugin.TreasureMapDropChancePercent); return Mathf.Clamp01(normalizedChance); } catch { return 0.0125f; } } } [BepInPlugin("et508.erenshor.treasuremapoverhaul", "Treasure Map Overhaul", "1.0.5")] public class Plugin : BaseUnityPlugin { internal static ManualLogSource Log; public static ConfigEntry TreasureMapDropChancePercent; private void Awake() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown TreasureMapDropChancePercent = ((BaseUnityPlugin)this).Config.Bind("Drop Chance", "TreasureMapDropChance", 1.25f, new ConfigDescription("Chance to drop A Torn Treasure Map (0.0-100%). Default: 2.0%. Reload scene or wait for new respawns for changes to apply.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); Log = ((BaseUnityPlugin)this).Logger; Log.LogInfo((object)"Treasure Map Overhaul loaded."); Harmony val = new Harmony("et508.erenshor.treasuremapoverhaul"); val.PatchAll(); } public static float GetNormalizedChance(ConfigEntry entry) { return Mathf.Clamp01(entry.Value / 100f); } } [HarmonyPatch(typeof(QuestDB), "Start")] public static class QuestDBPatch { private const string PreferredQuestAssetName = "Treasure Map Repair"; private const string TargetQuestDbName = "TreasureMap"; [HarmonyPostfix] public static void Postfix(QuestDB __instance) { try { if ((Object)(object)__instance == (Object)null) { Plugin.Log.LogError((object)"[TMO] QuestReplace: __instance is null."); return; } GameObject gameObject = ((Component)__instance).gameObject; if ((Object)(object)gameObject.GetComponent() == (Object)null) { TmoQuestReplacer tmoQuestReplacer = gameObject.AddComponent(); tmoQuestReplacer.Init(__instance, "Treasure Map Repair", "TreasureMap"); } } catch (Exception arg) { Plugin.Log.LogError((object)$"[TMO] QuestReplace: Exception in Postfix: {arg}"); } } } public class TmoQuestReplacer : MonoBehaviour { [CompilerGenerated] private sealed class d__5 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public TmoQuestReplacer <>4__this; private float 5__1; private Quest 5__2; private Quest 5__3; private Quest[] 5__4; private string 5__5; private Exception 5__6; private Exception 5__7; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__5(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__2 = null; 5__3 = null; 5__4 = null; 5__5 = null; 5__6 = null; 5__7 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = null; <>1__state = 1; return true; case 1: <>1__state = -1; 5__1 = Time.realtimeSinceStartup + 15f; break; case 2: <>1__state = -1; break; } if ((Object)(object)GameData.ItemDB == (Object)null) { if (Time.realtimeSinceStartup > 5__1) { Plugin.Log.LogError((object)"[TMO] QuestReplace: Timed out waiting for GameData.ItemDB."); Object.Destroy((Object)(object)<>4__this); return false; } <>2__current = null; <>1__state = 2; return true; } if (!TmoAssets.EnsureLoaded()) { Plugin.Log.LogError((object)"[TMO] QuestReplace: Asset bundle not available."); Object.Destroy((Object)(object)<>4__this); return false; } 5__2 = null; if (!string.IsNullOrWhiteSpace(<>4__this._questAssetName)) { 5__2 = TmoAssets.Load(<>4__this._questAssetName); if ((Object)(object)5__2 == (Object)null) { Plugin.Log.LogWarning((object)("[TMO] QuestReplace: Preferred quest asset '" + <>4__this._questAssetName + "' not found in bundle.")); } } if ((Object)(object)5__2 == (Object)null) { 5__4 = TmoAssets.LoadAll(); 5__2 = ((IEnumerable)5__4).FirstOrDefault((Func)((Quest q) => (Object)(object)q != (Object)null && MatchesQuest(q, <>4__this._targetDbName))); 5__4 = null; } if ((Object)(object)5__2 == (Object)null) { Plugin.Log.LogError((object)"[TMO] QuestReplace: Could not find donor Quest in bundle."); Object.Destroy((Object)(object)<>4__this); return false; } 5__3 = FindLiveQuest(<>4__this._questDb, <>4__this._targetDbName); if ((Object)(object)5__3 == (Object)null) { Plugin.Log.LogError((object)"[TMO] QuestReplace: Target quest not found in QuestDB."); Object.Destroy((Object)(object)<>4__this); return false; } try { 5__5 = JsonUtility.ToJson((object)5__2); JsonUtility.FromJsonOverwrite(5__5, (object)5__3); 5__5 = null; } catch (Exception ex) { 5__6 = ex; Plugin.Log.LogError((object)$"[TMO] QuestReplace: FromJsonOverwrite failed: {5__6}"); } try { RemapQuestItemsToLiveDB_ByNameThenId(5__3, GameData.ItemDB); } catch (Exception ex) { 5__7 = ex; Plugin.Log.LogWarning((object)$"[TMO] QuestReplace: Remap failed: {5__7}"); } Object.Destroy((Object)(object)<>4__this); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private QuestDB _questDb; private string _questAssetName; private string _targetDbName; public void Init(QuestDB questDb, string questAssetName, string targetDbName) { _questDb = questDb; _questAssetName = questAssetName; _targetDbName = targetDbName; } private void OnEnable() { ((MonoBehaviour)this).StartCoroutine(Run()); } [IteratorStateMachine(typeof(d__5))] private IEnumerator Run() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__5(0) { <>4__this = this }; } private static bool MatchesQuest(Quest q, string dbName) { if ((Object)(object)q == (Object)null) { return false; } if (string.IsNullOrWhiteSpace(dbName)) { return false; } FieldInfo field = ((object)q).GetType().GetField("DBName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { string a = field.GetValue(q) as string; return string.Equals(a, dbName, StringComparison.OrdinalIgnoreCase); } return false; } private static Quest FindLiveQuest(QuestDB db, string dbName) { try { if (!(((object)db).GetType().GetField("QuestDatabase", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(db) is Quest[] source)) { return null; } return ((IEnumerable)source).FirstOrDefault((Func)((Quest q) => (Object)(object)q != (Object)null && MatchesQuest(q, dbName))); } catch { return null; } } private static string GetQuestName(Quest q) { try { string text = ((object)q).GetType().GetField("DBName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(q) as string; return string.IsNullOrEmpty(text) ? ((Object)q).name : text; } catch { return ((q != null) ? ((Object)q).name : null) ?? "(null)"; } } private static void RemapQuestItemsToLiveDB_ByNameThenId(Quest quest, ItemDatabase liveDb) { if ((Object)(object)quest == (Object)null || (Object)(object)liveDb == (Object)null) { return; } FieldInfo field = ((object)quest).GetType().GetField("RequiredItems", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) { return; } object value = field.GetValue(quest); if (value is Item[] array) { for (int i = 0; i < array.Length; i++) { array[i] = FindLiveByNameThenId(liveDb, array[i]) ?? array[i]; } field.SetValue(quest, array); } else if (value is List list) { for (int j = 0; j < list.Count; j++) { list[j] = FindLiveByNameThenId(liveDb, list[j]) ?? list[j]; } field.SetValue(quest, list); } } private static Item FindLiveByNameThenId(ItemDatabase db, Item donorItem) { if ((Object)(object)donorItem == (Object)null) { return null; } string donorName = ((Object)donorItem).name; if (!string.IsNullOrEmpty(donorName)) { Item val = db.ItemDB?.FirstOrDefault((Func)((Item i) => (Object)(object)i != (Object)null && ((Object)i).name == donorName)); if ((Object)(object)val != (Object)null) { return val; } } string donorId = GetItemId(donorItem); if (!string.IsNullOrEmpty(donorId)) { Item val2 = db.ItemDB?.FirstOrDefault((Func)((Item i) => (Object)(object)i != (Object)null && GetItemId(i) == donorId)); if ((Object)(object)val2 != (Object)null) { return val2; } } return null; } private static string GetItemId(Item it) { try { FieldInfo field = ((object)it).GetType().GetField("Id", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field?.FieldType == typeof(string)) { return (string)field.GetValue(it); } PropertyInfo property = ((object)it).GetType().GetProperty("Id", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property?.PropertyType == typeof(string)) { return (string)property.GetValue(it, null); } } catch { } return null; } } internal static class TmoAssets { internal const string BundleFileName = "tmo_assets"; private static AssetBundle _bundle; private static bool _loggedAttempt; internal static AssetBundle Bundle => _bundle; internal static bool EnsureLoaded() { if ((Object)(object)_bundle != (Object)null) { return true; } if (!_loggedAttempt) { _loggedAttempt = true; Plugin.Log.LogInfo((object)"[TMO] Loading shared asset bundle…"); } try { string directoryName = Path.GetDirectoryName(typeof(Plugin).Assembly.Location); string text = Path.Combine(directoryName ?? ".", "tmo_assets"); _bundle = AssetBundle.LoadFromFile(text); if ((Object)(object)_bundle == (Object)null) { Plugin.Log.LogError((object)("[TMO] Failed to load AssetBundle at: " + text)); return false; } return true; } catch (Exception arg) { Plugin.Log.LogError((object)$"[TMO] EnsureLoaded exception: {arg}"); return false; } } internal static T Load(string name) where T : Object { if (!EnsureLoaded()) { return default(T); } try { return _bundle.LoadAsset(name); } catch (Exception arg) { Plugin.Log.LogError((object)$"[TMO] Load<{typeof(T).Name}>('{name}') failed: {arg}"); return default(T); } } internal static T[] LoadAll() where T : Object { if (!EnsureLoaded()) { return Array.Empty(); } try { return _bundle.LoadAllAssets(); } catch (Exception arg) { Plugin.Log.LogError((object)$"[TMO] LoadAll<{typeof(T).Name}> failed: {arg}"); return Array.Empty(); } } } [HarmonyPatch(typeof(Water), "Start")] public static class WaterPatch { private const string OldFishableName = "GEN - Torn Map Top Right"; private const string NewTornMapName = "GEN - A Torn Map"; [HarmonyPostfix] public static void OnWaterStart(Water __instance) { try { if ((Object)(object)__instance == (Object)null) { Plugin.Log.LogError((object)"[TMO] WaterPatch: Water instance is null."); return; } Item val = GameData.ItemDB?.ItemDB?.FirstOrDefault((Func)((Item i) => (Object)(object)i != (Object)null && ((Object)i).name == "GEN - A Torn Map")); if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning((object)"[TMO] WaterPatch: Torn Map 'GEN - A Torn Map' not found in ItemDB; skipping fishable replacement."); return; } int num = 0; num += ReplaceInList(__instance.DayFishables, val); num += ReplaceInList(__instance.NightFishables, val); num += ReplaceInList(__instance.Fishables, val); if (num > 0) { Plugin.Log.LogInfo((object)string.Format("[TMO] WaterPatch: Replaced {0} old fishable map entries with Torn Map '{1}'.", num, "GEN - A Torn Map")); } } catch (Exception arg) { Plugin.Log.LogError((object)$"[TMO] WaterPatch exception: {arg}"); } } private static int ReplaceInList(List list, Item tornMap) { if (list == null) { return 0; } int num = 0; for (int i = 0; i < list.Count; i++) { Item val = list[i]; if ((Object)(object)val != (Object)null && ((Object)val).name == "GEN - Torn Map Top Right") { list[i] = tornMap; num++; } } return num; } }