Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Odins Favor v0.2.0
OdinsFavor.dll
Decompiled 12 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace OdinsFavor { public interface ICondition { bool IsMet(); string Describe(); } internal class Boss { public string Key; public string Name; public string Message; public static readonly Dictionary<string, Boss> ByAlias; static Boss() { ByAlias = new Dictionary<string, Boss>(StringComparer.OrdinalIgnoreCase); Add(new Boss { Key = "defeated_eikthyr", Name = "Eikthyr", Message = "The thunder of Eikthyr awakens {mod}" }, "eikthyr"); Add(new Boss { Key = "defeated_gdking", Name = "The Elder", Message = "The roots of The Elder release {mod}" }, "elder", "theelder", "gdking"); Add(new Boss { Key = "defeated_bonemass", Name = "Bonemass", Message = "Bonemass's rot is cleansed. {mod} awakens" }, "bonemass"); Add(new Boss { Key = "defeated_dragon", Name = "Moder", Message = "Moder's frost breaks. {mod} awakens" }, "moder", "dragon"); Add(new Boss { Key = "defeated_goblinking", Name = "Yagluth", Message = "Yagluth's flame is quenched. {mod} awakens" }, "yagluth", "goblinking"); Add(new Boss { Key = "defeated_queen", Name = "The Queen", Message = "The Queen's mist lifts. {mod} awakens" }, "queen", "thequeen", "seekerqueen"); Add(new Boss { Key = "defeated_fader", Name = "Fader", Message = "Fader falls. {mod} awakens" }, "fader"); } private static void Add(Boss boss, params string[] aliases) { foreach (string key in aliases) { ByAlias[key] = boss; } } } internal class KeyCondition : ICondition { public readonly string Key; public readonly Boss Boss; public KeyCondition(string key, Boss boss) { Key = key; Boss = boss; } public bool IsMet() { if ((Object)(object)ZoneSystem.instance != (Object)null) { return ZoneSystem.instance.GetGlobalKey(Key); } return false; } public string Describe() { if (Boss == null) { return "world key " + Key; } return Boss.Name + " defeated"; } } internal class DaysCondition : ICondition { private readonly int _days; public DaysCondition(int days) { _days = days; } public bool IsMet() { if ((Object)(object)EnvMan.instance != (Object)null) { return EnvMan.instance.GetDay() >= _days; } return false; } public string Describe() { return $"day {_days}"; } } internal class SkillCondition : ICondition { private readonly SkillType _skill; private readonly float _level; public SkillCondition(SkillType skill, float level) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) _skill = skill; _level = level; } public bool IsMet() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { return ((Character)localPlayer).GetSkills().GetSkillLevel(_skill) >= _level; } return false; } public string Describe() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) return $"{ConditionParser.Pretty(((object)_skill/*cast due to .constrained prefix*/).ToString())} level {_level}"; } } internal class BiomeCondition : ICondition { private static readonly FieldRef<Player, HashSet<string>> KnownBiomes = AccessTools.FieldRefAccess<Player, HashSet<string>>("m_knownBiome"); private readonly Biome _biome; private readonly string _key; public unsafe BiomeCondition(Biome biome) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) _biome = biome; _key = "$biome_" + ((object)(*(Biome*)(&biome))/*cast due to .constrained prefix*/).ToString().ToLower(); } public bool IsMet() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return false; } return KnownBiomes.Invoke(localPlayer).Any((string b) => b == _key || b.EndsWith(" " + _key)); } public string Describe() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) return "discovered " + ConditionParser.Pretty(((object)_biome/*cast due to .constrained prefix*/).ToString()); } } internal abstract class PrefabCondition : ICondition { protected readonly string Prefab; private string _internalName; private bool _warned; protected PrefabCondition(string prefab) { Prefab = prefab; } public abstract bool IsMet(); public abstract string Describe(); protected abstract string Lookup(); protected string InternalName() { if (_internalName != null) { return _internalName; } if (Prefab.StartsWith("$")) { return _internalName = Prefab; } string text = Lookup(); if (text == null) { return null; } if (text.Length == 0) { if (!_warned) { ConditionParser.Warn?.Invoke("No item or creature called \"" + Prefab + "\" exists. Check the spelling (use the spawn name, e.g. Greyling, Iron)."); } _warned = true; return null; } return _internalName = text; } } internal class ItemCondition : PrefabCondition { public ItemCondition(string prefab) : base(prefab) { } protected override string Lookup() { if ((Object)(object)ObjectDB.instance == (Object)null || ObjectDB.instance.m_items.Count == 0) { return null; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(Prefab); ItemDrop val = default(ItemDrop); if (!((Object)(object)itemPrefab != (Object)null) || !itemPrefab.TryGetComponent<ItemDrop>(ref val)) { return ""; } return val.m_itemData.m_shared.m_name; } public override bool IsMet() { Player localPlayer = Player.m_localPlayer; string text = InternalName(); if ((Object)(object)localPlayer != (Object)null && text != null) { return localPlayer.IsKnownMaterial(text); } return false; } public override string Describe() { return "found " + Prefab; } } internal class KillsCondition : PrefabCondition { private readonly int _count; public KillsCondition(string prefab, int count) : base(prefab) { _count = count; } protected override string Lookup() { if ((Object)(object)ZNetScene.instance == (Object)null) { return null; } GameObject prefab = ZNetScene.instance.GetPrefab(Prefab); Character val = default(Character); if (!((Object)(object)prefab != (Object)null) || !prefab.TryGetComponent<Character>(ref val)) { return ""; } return val.m_name; } public override bool IsMet() { Game instance = Game.instance; PlayerProfile val = ((instance != null) ? instance.GetPlayerProfile() : null); string text = InternalName(); if (val == null || text == null) { return false; } if (val.m_playerStats[0].m_enemyStats[0].TryGetValue(text, out var value)) { return value >= (float)_count; } return false; } public override string Describe() { return $"{_count} {Prefab} kills"; } } internal class AllCondition : ICondition { public readonly List<ICondition> Parts; public AllCondition(List<ICondition> parts) { Parts = parts; } public bool IsMet() { return Parts.All((ICondition p) => p.IsMet()); } public string Describe() { return string.Join(" and ", Parts.Select((ICondition p) => (!(p is AnyCondition)) ? p.Describe() : ("(" + p.Describe() + ")"))); } } internal class AnyCondition : ICondition { public readonly List<ICondition> Parts; public AnyCondition(List<ICondition> parts) { Parts = parts; } public bool IsMet() { return Parts.Any((ICondition p) => p.IsMet()); } public string Describe() { return string.Join(" or ", Parts.Select((ICondition p) => p.Describe())); } } public static class ConditionParser { public const string Help = "boss:bonemass days:30 skill:woodcutting:50 item:Iron biome:Mistlands kills:Greyling:100 key:defeated_eikthyr\nCombine with 'and' / 'or' (or & and |), group with ( )."; public static Action<string> Warn; private static readonly Dictionary<string, string> PrettyNames = new Dictionary<string, string> { { "WoodCutting", "Woodcutting" }, { "AshLands", "Ashlands" } }; public static ICondition Parse(string text) { List<string> list = Tokenize(text); if (list.Count == 0) { throw new FormatException("The rule is empty."); } int pos = 0; ICondition result = ParseAny(list, ref pos); if (pos < list.Count) { throw new FormatException("Didn't expect \"" + list[pos] + "\" there."); } return result; } internal static string Pretty(string name) { if (PrettyNames.TryGetValue(name, out var value)) { return value; } return string.Concat(name.Select((char c, int i) => (i <= 0 || !char.IsUpper(c)) ? c.ToString() : (" " + c))); } internal static Boss FirstBoss(ICondition condition) { if (!(condition is KeyCondition keyCondition)) { if (!(condition is AllCondition allCondition)) { if (condition is AnyCondition anyCondition) { return anyCondition.Parts.Select(FirstBoss).FirstOrDefault((Boss b) => b != null); } return null; } return allCondition.Parts.Select(FirstBoss).FirstOrDefault((Boss b) => b != null); } return keyCondition.Boss; } private static List<string> Tokenize(string text) { List<string> list = new List<string>(); string text2 = ""; string text3 = text ?? ""; for (int i = 0; i < text3.Length; i++) { char c = text3[i]; if (char.IsWhiteSpace(c) || c == '(' || c == ')' || c == '&' || c == '|') { if (text2.Length > 0) { list.Add(text2); } text2 = ""; if (!char.IsWhiteSpace(c)) { list.Add(c.ToString()); } } else { text2 += c; } } if (text2.Length > 0) { list.Add(text2); } return list.Select((string t) => (!t.Equals("and", StringComparison.OrdinalIgnoreCase)) ? ((!t.Equals("or", StringComparison.OrdinalIgnoreCase)) ? t : "|") : "&").ToList(); } private static ICondition ParseAny(List<string> tokens, ref int pos) { List<ICondition> list = new List<ICondition> { ParseAll(tokens, ref pos) }; while (pos < tokens.Count && tokens[pos] == "|") { pos++; list.Add(ParseAll(tokens, ref pos)); } if (list.Count != 1) { return new AnyCondition(list); } return list[0]; } private static ICondition ParseAll(List<string> tokens, ref int pos) { List<ICondition> list = new List<ICondition> { ParseAtom(tokens, ref pos) }; while (pos < tokens.Count && tokens[pos] == "&") { pos++; list.Add(ParseAtom(tokens, ref pos)); } if (list.Count != 1) { return new AllCondition(list); } return list[0]; } private static ICondition ParseAtom(List<string> tokens, ref int pos) { if (pos >= tokens.Count) { throw new FormatException("The rule ends too early. Is something missing after 'and' / 'or'?"); } string text = tokens[pos++]; switch (text) { case "(": { ICondition result = ParseAny(tokens, ref pos); if (pos >= tokens.Count || tokens[pos] != ")") { throw new FormatException("A '(' is never closed with ')'."); } pos++; return result; } case ")": case "&": case "|": throw new FormatException("Didn't expect \"" + text + "\" there."); default: return ParseSingle(text); } } private static ICondition ParseSingle(string token) { //IL_02e9: Unknown result type (might be due to invalid IL or missing references) //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_02f4: Invalid comparison between Unknown and I4 //IL_0313: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_0270: Invalid comparison between Unknown and I4 //IL_028f: Unknown result type (might be due to invalid IL or missing references) string[] parts = token.Split(new char[1] { ':' }); switch (parts[0].ToLowerInvariant()) { case "boss": { Expect(parts, 2, token, "boss:bonemass"); if (!Boss.ByAlias.TryGetValue(parts[1], out var value)) { throw new FormatException("Unknown boss \"" + parts[1] + "\". Use one of: eikthyr, elder, bonemass, moder, yagluth, queen, fader."); } return new KeyCondition(value.Key, value); } case "key": Expect(parts, 2, token, "key:defeated_eikthyr"); return new KeyCondition(parts[1], Boss.ByAlias.Values.FirstOrDefault((Boss b) => b.Key == parts[1])); case "days": case "day": Expect(parts, 2, token, "days:30"); return new DaysCondition(Number(parts[1], token)); case "skill": { Expect(parts, 3, token, "skill:woodcutting:50"); if (!Enum.TryParse<SkillType>(parts[1], ignoreCase: true, out SkillType result2) || (int)result2 == 0 || (int)result2 == 999) { throw new FormatException("Unknown skill \"" + parts[1] + "\". Examples: woodcutting, pickaxes, run, swords, cooking."); } return new SkillCondition(result2, Number(parts[2], token)); } case "item": Expect(parts, 2, token, "item:Iron"); return new ItemCondition(parts[1]); case "biome": { Expect(parts, 2, token, "biome:Mistlands"); if (!Enum.TryParse<Biome>(parts[1], ignoreCase: true, out Biome result) || (int)result == 0 || (int)result == 895) { throw new FormatException("Unknown biome \"" + parts[1] + "\". Use one of: Meadows, BlackForest, Swamp, Mountain, Plains, Mistlands, AshLands, DeepNorth, Ocean."); } return new BiomeCondition(result); } case "kill": case "kills": Expect(parts, 3, token, "kills:Greyling:100"); return new KillsCondition(parts[1], Number(parts[2], token)); default: throw new FormatException("Unknown rule \"" + token + "\". Rules look like: boss:bonemass, days:30, skill:woodcutting:50, item:Iron, biome:Mistlands, kills:Greyling:100, key:defeated_eikthyr."); } } private static void Expect(string[] parts, int count, string token, string example) { if (parts.Length != count || parts.Any(string.IsNullOrWhiteSpace)) { throw new FormatException("\"" + token + "\" should look like " + example + "."); } } private static int Number(string text, string token) { if (!int.TryParse(text, out var result) || result < 0) { throw new FormatException("\"" + text + "\" in \"" + token + "\" should be a whole number."); } return result; } } public enum UnlockAfter { AlwaysOn, Eikthyr, TheElder, Bonemass, Moder, Yagluth, TheQueen, Fader, Custom } [BepInPlugin("giridhar.odinsfavor", "Odin's Favor", "0.2.0")] public class OdinsFavorPlugin : BaseUnityPlugin { private enum PatchKind { Prefix, Postfix, Transpiler, Finalizer, ILManipulator } private class SavedPatch { public MethodBase Target; public Patch Patch; public PatchKind Kind; } private class Gate { public PluginInfo Info; public ConfigEntry<UnlockAfter> Entry; public ConfigEntry<string> Rule; public ConfigEntry<string> Message; public bool Running = true; public bool HasRun; public ICondition Condition; public string ConditionSource; public string PendingSource; public float PendingSince; public bool Earned; public readonly List<SavedPatch> Saved = new List<SavedPatch>(); public readonly HashSet<int> SetupDoneBeforeLock = new HashSet<int>(); public readonly Dictionary<MethodBase, int> SetupRunsAtLock = new Dictionary<MethodBase, int>(); } public const string Guid = "giridhar.odinsfavor"; public const string ModName = "Odin's Favor"; public const string Version = "0.2.0"; private const string DefaultRuleMessage = "Proven worthy: {condition}|{mod} awakens"; private const float QuietSecondsAfterSpawn = 3f; private const float SettingSettleSeconds = 2f; private const float SecondsBetweenMessages = 4f; private static readonly string[] AlwaysOnNamespaces = new string[2] { "ServerSync", "LocalizationManager" }; private static readonly HashSet<string> SetupMethods = new HashSet<string> { "Awake", "Start" }; private static readonly Dictionary<MethodBase, int> SetupRuns = new Dictionary<MethodBase, int>(); private readonly HashSet<MethodBase> _tracked = new HashSet<MethodBase>(); private ConfigEntry<bool> _modEnabled; private ConfigEntry<bool> _showMessages; private readonly List<Gate> _gates = new List<Gate>(); private Harmony _harmony; private ZoneSystem _lastWorld; private PlayerProfile _lastProfile; private float _playerSeenAt = -1f; private float _nextCheck; private readonly Queue<string> _pendingWarnings = new Queue<string>(); private readonly Queue<string> _pendingUnlocks = new Queue<string>(); private float _nextUnlockMessageAt; private static void TrackSetup(MethodBase __originalMethod) { SetupRuns.TryGetValue(__originalMethod, out var value); SetupRuns[__originalMethod] = value + 1; } private static int RunsOf(MethodBase method) { if (!SetupRuns.TryGetValue(method, out var value)) { return 0; } return value; } private void Awake() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown _harmony = new Harmony("giridhar.odinsfavor"); ConditionParser.Warn = Warn; _modEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "Enabled", true, "Turn Odin's Favor off to run every mod normally."); _showMessages = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "ShowUnlockMessages", true, "Show an on-screen message when a locked mod unlocks."); } private void Start() { //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected O, but got Unknown //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Expected O, but got Unknown //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Expected O, but got Unknown foreach (PluginInfo item in Chainloader.PluginInfos.Values.OrderBy((PluginInfo p) => p.Metadata.Name)) { if (!((Object)(object)item.Instance == (Object)null) && !(item.Metadata.GUID == "giridhar.odinsfavor")) { ConfigEntry<UnlockAfter> entry = ((BaseUnityPlugin)this).Config.Bind<UnlockAfter>("2 - Mods", CleanKey(item.Metadata.Name), UnlockAfter.AlwaysOn, new ConfigDescription("When should " + item.Metadata.Name + " turn on? Pick a boss, or Custom to write your own rule in '4 - Custom rules'.\nGUID: " + item.Metadata.GUID, (AcceptableValueBase)null, Array.Empty<object>())); ConfigEntry<string> message = ((BaseUnityPlugin)this).Config.Bind<string>("3 - Messages", CleanKey(item.Metadata.Name), "", new ConfigDescription("Message shown when " + item.Metadata.Name + " unlocks. Leave empty for the default.\nUse {mod}, {boss} and {condition} as placeholders, and | for a second line.\nExample: The power of {boss} surges|Torches now burn forever", (AcceptableValueBase)null, Array.Empty<object>())); ConfigEntry<string> rule = ((BaseUnityPlugin)this).Config.Bind<string>("4 - Custom rules", CleanKey(item.Metadata.Name), "", new ConfigDescription("Your own unlock rule for " + item.Metadata.Name + ". Only used when '2 - Mods' is set to Custom.\nboss:bonemass days:30 skill:woodcutting:50 item:Iron biome:Mistlands kills:Greyling:100 key:defeated_eikthyr\nCombine with 'and' / 'or' (or & and |), group with ( ).\nExample: boss:bonemass and (days:50 or skill:woodcutting:50)", (AcceptableValueBase)null, Array.Empty<object>())); _gates.Add(new Gate { Info = item, Entry = entry, Rule = rule, Message = message }); } } ((BaseUnityPlugin)this).Config.Save(); ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Odin's Favor found {_gates.Count} mods."); } private void Update() { if (Time.unscaledTime < _nextCheck) { return; } _nextCheck = Time.unscaledTime + 1f; ZoneSystem instance = ZoneSystem.instance; Game instance2 = Game.instance; PlayerProfile val = ((instance2 != null) ? instance2.GetPlayerProfile() : null); if ((Object)(object)instance != (Object)(object)_lastWorld || val != _lastProfile) { foreach (Gate gate in _gates) { gate.Earned = false; } _lastWorld = instance; _lastProfile = val; } if ((Object)(object)Player.m_localPlayer == (Object)null) { _playerSeenAt = -1f; } else if (_playerSeenAt < 0f) { _playerSeenAt = Time.unscaledTime; } bool flag = _playerSeenAt >= 0f && Time.unscaledTime - _playerSeenAt >= 3f; foreach (Gate gate2 in _gates) { try { bool flag2 = ShouldRun(gate2, instance); if (flag2 == gate2.Running) { continue; } if (flag2) { Resume(gate2); if (flag && _modEnabled.Value && _showMessages.Value) { ShowUnlockMessage(gate2); } } else { Suspend(gate2); } goto IL_0184; } catch (Exception arg) { ((BaseUnityPlugin)this).Logger.LogError((object)$"Error while switching {gate2.Info.Metadata.Name}; it may be partly locked: {arg}"); goto IL_0184; } IL_0184: if (gate2.Running) { gate2.HasRun = true; } } if ((Object)(object)instance != (Object)null && (Object)(object)MessageHud.instance != (Object)null) { while (_pendingWarnings.Count > 0) { MessageHud.instance.ShowMessage((MessageType)1, _pendingWarnings.Dequeue(), 0, (Sprite)null, false, true); } if (_pendingUnlocks.Count > 0 && Time.unscaledTime >= _nextUnlockMessageAt) { MessageHud.instance.ShowMessage((MessageType)2, _pendingUnlocks.Dequeue(), 0, (Sprite)null, false, true); _nextUnlockMessageAt = Time.unscaledTime + 4f; } } else { _pendingUnlocks.Clear(); } } private void Warn(string text) { ((BaseUnityPlugin)this).Logger.LogWarning((object)text); _pendingWarnings.Enqueue("Odin's Favor: " + text); } private void ShowUnlockMessage(Gate gate) { if (gate.Condition != null) { Boss boss = ConditionParser.FirstBoss(gate.Condition); string text = ((!string.IsNullOrWhiteSpace(gate.Message.Value)) ? gate.Message.Value : ((gate.Condition is KeyCondition { Boss: not null } keyCondition) ? keyCondition.Boss.Message : "Proven worthy: {condition}|{mod} awakens")); string text2 = gate.Condition.Describe(); text = text.Replace("{mod}", gate.Info.Metadata.Name).Replace("{boss}", boss?.Name ?? "").Replace("{condition}", char.ToUpper(text2[0]) + text2.Substring(1)) .Replace("|", "\n"); _pendingUnlocks.Enqueue(text); } } private bool ShouldRun(Gate gate, ZoneSystem world) { if (!_modEnabled.Value) { return true; } UpdateCondition(gate); if (gate.Condition == null) { return true; } if (gate.Earned) { return true; } if ((Object)(object)world == (Object)null) { return false; } return gate.Earned = gate.Condition.IsMet(); } private void UpdateCondition(Gate gate) { UnlockAfter value = gate.Entry.Value; string text = ((value == UnlockAfter.Custom) ? ("custom:" + gate.Rule.Value) : value.ToString()); if (text == gate.ConditionSource) { gate.PendingSource = null; return; } if (gate.ConditionSource != null) { if (text != gate.PendingSource) { gate.PendingSource = text; gate.PendingSince = Time.unscaledTime; return; } if (Time.unscaledTime - gate.PendingSince < 2f) { return; } } gate.PendingSource = null; ICondition condition = null; switch (value) { case UnlockAfter.Custom: try { condition = ConditionParser.Parse(gate.Rule.Value); } catch (FormatException ex) { string text2 = (string.IsNullOrWhiteSpace(gate.Rule.Value) ? "it's set to Custom but has no rule in '4 - Custom rules'" : ex.Message); Warn((gate.Condition != null) ? ("Can't read the new rule for " + gate.Info.Metadata.Name + ", so it keeps the old one (" + gate.Condition.Describe() + "): " + text2) : ("Can't read the rule for " + gate.Info.Metadata.Name + ", so it stays on: " + text2)); gate.ConditionSource = text; return; } break; default: { Boss boss = Boss.ByAlias[value.ToString()]; condition = new KeyCondition(boss.Key, boss); break; } case UnlockAfter.AlwaysOn: break; } gate.ConditionSource = text; gate.Condition = condition; gate.Earned = false; if (condition != null) { ((BaseUnityPlugin)this).Logger.LogInfo((object)(gate.Info.Metadata.Name + " unlocks when: " + condition.Describe())); } } private void Suspend(Gate gate) { Assembly assembly = ((object)gate.Info.Instance).GetType().Assembly; foreach (MethodBase item in Harmony.GetAllPatchedMethods().ToList()) { Patches patchInfo = Harmony.GetPatchInfo(item); if (patchInfo != null) { Collect(gate, item, patchInfo.Prefixes, PatchKind.Prefix, assembly); Collect(gate, item, patchInfo.Postfixes, PatchKind.Postfix, assembly); Collect(gate, item, patchInfo.Transpilers, PatchKind.Transpiler, assembly); Collect(gate, item, patchInfo.Finalizers, PatchKind.Finalizer, assembly); Collect(gate, item, patchInfo.ILManipulators, PatchKind.ILManipulator, assembly); } } gate.SetupDoneBeforeLock.Clear(); gate.SetupRunsAtLock.Clear(); foreach (SavedPatch item2 in SetupTargets(gate)) { TrackSetupMethod(item2.Target); gate.SetupRunsAtLock[item2.Target] = RunsOf(item2.Target); foreach (Object item3 in LiveInstances(item2.Target)) { gate.SetupDoneBeforeLock.Add(item3.GetInstanceID()); } } ((Behaviour)gate.Info.Instance).enabled = false; gate.Running = false; ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Locked {gate.Info.Metadata.Name} ({gate.Saved.Count} patches suspended)."); if (gate.HasRun) { string text = gate.Info.Metadata.Name + " is locked again. Restart the game to lock it fully."; ((BaseUnityPlugin)this).Logger.LogWarning((object)text); MessageHud instance = MessageHud.instance; if (instance != null) { instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true); } } } private void Collect(Gate gate, MethodBase target, IEnumerable<Patch> patches, PatchKind kind, Assembly asm) { if (patches == null) { return; } foreach (Patch item in patches.Where((Patch p) => p.PatchMethod?.DeclaringType?.Assembly == asm && !IsAlwaysOn(p, target)).ToList()) { try { _harmony.Unpatch(target, item.PatchMethod); gate.Saved.Add(new SavedPatch { Target = target, Patch = item, Kind = kind }); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not suspend a patch from " + gate.Info.Metadata.Name + " on " + target.Name + ": " + ex.Message)); } } } private void Resume(Gate gate) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown foreach (SavedPatch item in gate.Saved) { try { Patch patch = item.Patch; HarmonyMethod val = new HarmonyMethod(patch.PatchMethod, patch.priority, patch.before, patch.after, (bool?)patch.debug) { wrapTryCatch = patch.wrapTryCatch }; Harmony val2 = new Harmony(patch.owner); switch (item.Kind) { case PatchKind.Prefix: val2.Patch(item.Target, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); break; case PatchKind.Postfix: val2.Patch(item.Target, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); break; case PatchKind.Transpiler: val2.Patch(item.Target, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null); break; case PatchKind.Finalizer: val2.Patch(item.Target, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null); break; case PatchKind.ILManipulator: val2.Patch(item.Target, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, val); break; } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not restore a patch for " + gate.Info.Metadata.Name + " on " + item.Target.Name + ": " + ex.Message)); } } int num = ReplayMissedSetup(gate); ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Unlocked {gate.Info.Metadata.Name} ({gate.Saved.Count} patches restored, {num} setup steps replayed)."); gate.Saved.Clear(); gate.SetupDoneBeforeLock.Clear(); gate.SetupRunsAtLock.Clear(); ((Behaviour)gate.Info.Instance).enabled = true; gate.Running = true; } private static bool IsAlwaysOn(Patch patch, MethodBase target) { if (target.DeclaringType?.FullName == "Localization") { return true; } string ns = patch.PatchMethod.DeclaringType?.Namespace ?? ""; return AlwaysOnNamespaces.Any((string n) => ns == n || ns.StartsWith(n + ".")); } private static IEnumerable<SavedPatch> SetupTargets(Gate gate) { return gate.Saved.Where((SavedPatch s) => s.Kind == PatchKind.Postfix && !s.Target.IsStatic && SetupMethods.Contains(s.Target.Name) && typeof(Object).IsAssignableFrom(s.Target.DeclaringType)); } private static IEnumerable<Object> LiveInstances(MethodBase target) { return from o in Object.FindObjectsByType(target.DeclaringType, (FindObjectsSortMode)0) where o != (Object)null select o; } private void TrackSetupMethod(MethodBase target) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown if (!_tracked.Add(target)) { return; } try { _harmony.Patch(target, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(OdinsFavorPlugin), "TrackSetup", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not watch " + target.DeclaringType?.Name + "." + target.Name + ": " + ex.Message)); } } private int ReplayMissedSetup(Gate gate) { int num = 0; foreach (SavedPatch item in SetupTargets(gate)) { gate.SetupRunsAtLock.TryGetValue(item.Target, out var value); if (RunsOf(item.Target) <= value) { continue; } List<object> list = (from o in LiveInstances(item.Target) where !gate.SetupDoneBeforeLock.Contains(o.GetInstanceID()) select o).Cast<object>().ToList(); if (list.Count == 0) { if (NeedsInstance(item)) { continue; } list.Add(null); } foreach (object item2 in list) { try { if (InvokePostfix(item, item2)) { num++; } } catch (Exception ex) { Exception ex2 = ((ex is TargetInvocationException { InnerException: not null } ex3) ? ex3.InnerException : ex); ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Could not replay {gate.Info.Metadata.Name}'s setup on {item.Target.DeclaringType.Name}.{item.Target.Name}: {ex2}"); } } } return num; } private static bool NeedsInstance(SavedPatch saved) { return saved.Patch.PatchMethod.GetParameters().Any((ParameterInfo p) => p.Name == "__instance" || p.Name.StartsWith("___")); } private bool InvokePostfix(SavedPatch saved, object instance) { MethodInfo patchMethod = saved.Patch.PatchMethod; ParameterInfo[] parameters = patchMethod.GetParameters(); object[] array = new object[parameters.Length]; Dictionary<int, FieldInfo> dictionary = new Dictionary<int, FieldInfo>(); for (int i = 0; i < parameters.Length; i++) { ParameterInfo parameterInfo = parameters[i]; Type type = (parameterInfo.ParameterType.IsByRef ? parameterInfo.ParameterType.GetElementType() : parameterInfo.ParameterType); if (parameterInfo.Name == "__instance") { array[i] = instance; continue; } if (parameterInfo.Name == "__originalMethod") { array[i] = saved.Target; continue; } if (parameterInfo.Name == "__runOriginal") { array[i] = true; continue; } if (parameterInfo.Name == "__args") { array[i] = new object[0]; continue; } if (parameterInfo.Name == "__state") { array[i] = (type.IsValueType ? Activator.CreateInstance(type) : null); continue; } if (parameterInfo.Name.StartsWith("___")) { FieldInfo fieldInfo = AccessTools.Field(saved.Target.DeclaringType, parameterInfo.Name.Substring(3)); if (fieldInfo == null) { return Skip(saved, "unknown field " + parameterInfo.Name); } array[i] = fieldInfo.GetValue(instance); if (parameterInfo.ParameterType.IsByRef) { dictionary[i] = fieldInfo; } continue; } return Skip(saved, "unsupported argument " + parameterInfo.Name); } patchMethod.Invoke(null, array); foreach (KeyValuePair<int, FieldInfo> item in dictionary) { item.Value.SetValue(instance, array[item.Key]); } return true; } private bool Skip(SavedPatch saved, string reason) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Skipped replaying " + saved.Patch.PatchMethod.DeclaringType?.Name + "." + saved.Patch.PatchMethod.Name + " (" + reason + ").")); return false; } private static string CleanKey(string name) { char[] array = new char[8] { '=', '\n', '\t', '\\', '"', '\'', '[', ']' }; foreach (char oldChar in array) { name = name.Replace(oldChar, '_'); } return name.Trim(); } } }