using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("AchievementEnablerPlus")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.0.3.0")] [assembly: AssemblyInformationalVersion("2.0.3")] [assembly: AssemblyProduct("AchievementEnablerPlus")] [assembly: AssemblyTitle("AchievementEnablerPlus")] [assembly: AssemblyVersion("2.0.3.0")] namespace Robgob.ValheimAchievementEligibility; [BepInPlugin("com.robgob.valheim.achievementeligibility", "Achievement Enabler+", "2.0.3")] public sealed class AchievementEligibilityPlugin : BaseUnityPlugin { internal const string PluginGuid = "com.robgob.valheim.achievementeligibility"; internal const string PluginName = "Achievement Enabler+"; internal const string PluginVersion = "2.0.3"; private static readonly string[] RecoveryChecks = new string[6] { "CheckAchievementProgressUnlockEnemyStat", "CheckAchievementProgressUnlockItemPickup", "CheckAchievementProgressUnlockItemCraft", "CheckAchievementProgressUnlockPickable", "CheckAchievementsProgressUnlockFoodEaten", "CheckAchievementProgressUnlockBuildPiecesPlaced" }; internal static ManualLogSource PluginLogger; private float nextRecoveryAttempt = 15f; private int recoveryAttempts; private Player sessionPlayer; private float nextCleanup; private Harmony harmony; private void Awake() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown PluginLogger = ((BaseUnityPlugin)this).Logger; harmony = new Harmony("com.robgob.valheim.achievementeligibility"); try { harmony.PatchAll(typeof(AchievementEligibilityPlugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Achievement Enabler+ 2.0.3 loaded: achievements enabled; item and character cheat cleanup active."); } catch (Exception ex) { harmony.UnpatchSelf(); ((Behaviour)this).enabled = false; ((BaseUnityPlugin)this).Logger.LogError((object)("Unable to apply all patches; plugin disabled: " + ex)); } } private void OnDestroy() { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } private void Update() { Player localPlayer = Player.m_localPlayer; if (localPlayer != sessionPlayer) { sessionPlayer = localPlayer; recoveryAttempts = 0; nextRecoveryAttempt = Time.unscaledTime + 15f; nextCleanup = 0f; } if ((Object)(object)localPlayer == (Object)null) { return; } if (Time.unscaledTime >= nextCleanup) { nextCleanup = Time.unscaledTime + 1f; CharacterSanitizer.CleanLocal(); } if (recoveryAttempts < 3 && !(Time.unscaledTime < nextRecoveryAttempt)) { nextRecoveryAttempt = Time.unscaledTime + 15f; if (RunRecoveryChecks()) { recoveryAttempts++; } } } private bool RunRecoveryChecks() { Achievements val = Object.FindAnyObjectByType(); if ((Object)(object)val == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null) { return false; } int num = 0; string[] recoveryChecks = RecoveryChecks; foreach (string text in recoveryChecks) { try { MethodInfo methodInfo = AccessTools.Method(typeof(Achievements), text, (Type[])null, (Type[])null); if (!(methodInfo == null)) { methodInfo.Invoke(val, null); num++; } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Recovery check " + text + " failed: " + ex.GetBaseException().Message)); } } if (num > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Ran {num} achievement progress recovery checks (attempt {recoveryAttempts + 1}/3)."); } return num > 0; } } [HarmonyPatch(typeof(Achievements), "CanGetAchievements")] internal static class CanGetAchievementsPatch { [HarmonyPriority(0)] private static bool Prefix(ref bool __result) { __result = true; return false; } [HarmonyPriority(0)] private static void Postfix(ref bool __result) { __result = true; } } internal static class CharacterSanitizer { internal static void Clean(PlayerProfile profile) { if (profile == null) { return; } profile.m_usedCheats = false; if (profile.m_playerStats == null) { return; } PlayerStats[] playerStats = profile.m_playerStats; foreach (PlayerStats val in playerStats) { if (val?.m_stats != null) { val.m_stats[(PlayerStatType)4] = 0f; } } } internal static void CleanLocal() { if ((Object)(object)Game.instance != (Object)null) { Clean(Game.instance.GetPlayerProfile()); } if ((Object)(object)Player.m_localPlayer != (Object)null) { Sanitizer.Clean(((Humanoid)Player.m_localPlayer).GetInventory()); } } } [HarmonyPatch] internal static class ProfileCleanupPatch { private static IEnumerable TargetMethods() { string[] array = new string[6] { "Load", "LoadPlayerFromDisk", "LoadPlayerData", "Save", "SavePlayerToDisk", "SavePlayerData" }; foreach (string text in array) { yield return AccessTools.Method(typeof(PlayerProfile), text, (Type[])null, (Type[])null); } } private static void Prefix(PlayerProfile __instance) { CharacterSanitizer.Clean(__instance); } private static void Postfix(PlayerProfile __instance) { CharacterSanitizer.Clean(__instance); } } [HarmonyPatch(typeof(ConsoleCommand), "RunAction")] internal static class CommandCleanupPatch { private static IEnumerable Transpiler(IEnumerable instructions) { List list = new List(instructions); MethodInfo method = typeof(Achievements).GetMethod("IsCheatedAtAll", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); int num = 0; foreach (CodeInstruction item in list) { if (!(item.opcode != OpCodes.Call) && object.Equals(item.operand, method)) { item.opcode = OpCodes.Ldc_I4_1; item.operand = null; num++; } } if (num != 1) { throw new InvalidOperationException("Expected one command cheat-confirmation check; found " + num); } return list; } private static void Prefix() { CharacterSanitizer.CleanLocal(); } private static void Postfix() { CharacterSanitizer.CleanLocal(); } } [HarmonyPatch(typeof(PlayerProfile), "IncrementStat")] internal static class CheatCounterPatch { private static bool Prefix(PlayerProfile __instance, PlayerStatType __0) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 if ((int)__0 != 4) { return true; } CharacterSanitizer.Clean(__instance); return false; } } [HarmonyPatch(typeof(Player), "SetLocalPlayer")] internal static class LocalPlayerCleanupPatch { private static void Postfix() { CharacterSanitizer.CleanLocal(); } } [HarmonyPatch(typeof(Achievements), "IsCheatedAtAll")] internal static class AchievementCheatStatusPatch { private static bool Prefix(ref bool __result) { CharacterSanitizer.CleanLocal(); __result = false; return false; } [HarmonyPriority(0)] private static void Postfix(ref bool __result) { __result = false; } } internal static class Sanitizer { internal static bool Clean(ItemData item) { if (item == null || !item.m_cheated) { return false; } item.m_cheated = false; return true; } internal static int Clean(Inventory inventory) { if (inventory == null) { return 0; } int num = 0; List allItems = inventory.GetAllItems(); for (int i = 0; i < allItems.Count; i++) { if (Clean(allItems[i])) { num++; } } return num; } internal static void Clean(ItemDrop drop) { if ((Object)(object)drop != (Object)null) { Clean(drop.m_itemData); } } } [HarmonyPatch(typeof(Inventory), "Changed")] internal static class InventoryChangedPatch { [HarmonyPriority(0)] private static void Prefix(Inventory __instance, ref bool cheatedStateChanged, ref bool ___m_cheatedPopup) { Sanitizer.Clean(__instance); cheatedStateChanged = false; ___m_cheatedPopup = false; } private static void Postfix(Inventory __instance) { Sanitizer.Clean(__instance); } } [HarmonyPatch(typeof(Inventory), "Save")] internal static class InventorySavePatch { private static void Prefix(Inventory __instance) { Sanitizer.Clean(__instance); } } [HarmonyPatch] internal static class InventoryIngressPatch { private static IEnumerable TargetMethods() { MethodInfo[] methods = typeof(Inventory).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name == "Load" || methodInfo.Name == "LoadOld" || methodInfo.Name == "AddItem" || methodInfo.Name == "AddTempItem") { yield return methodInfo; } } } private static void Prefix(object[] __args) { if (__args != null) { foreach (object obj in __args) { Sanitizer.Clean((ItemData)((obj is ItemData) ? obj : null)); } } } private static void Postfix(Inventory __instance) { Sanitizer.Clean(__instance); } } [HarmonyPatch(typeof(Inventory), "MoveInventoryToGrave")] internal static class GraveTransferPatch { private static void Prefix(Inventory __instance, Inventory original) { Sanitizer.Clean(original); Sanitizer.Clean(__instance); } private static void Postfix(Inventory __instance) { Sanitizer.Clean(__instance); } } [HarmonyPatch(typeof(ItemDrop), "Save")] internal static class WorldItemSavePatch { private static void Prefix(ItemDrop __instance) { Sanitizer.Clean(__instance); } } [HarmonyPatch(typeof(ItemDrop), "Load")] internal static class WorldItemLoadPatch { private static void Postfix(ItemDrop __instance) { Sanitizer.Clean(__instance); } } [HarmonyPatch(typeof(ItemDrop), "OnPlayerDrop")] internal static class PlayerDropPatch { private static void Prefix(ItemDrop __instance) { Sanitizer.Clean(__instance); } private static void Postfix(ItemDrop __instance) { Sanitizer.Clean(__instance); } } [HarmonyPatch(typeof(ItemDrop), "OnCreateNew", new Type[] { typeof(GameObject), typeof(bool) })] internal static class NewWorldItemPatch { private static void Prefix(ref bool cheated) { cheated = false; } } [HarmonyPatch(typeof(ItemDrop), "OnCreateNew", new Type[] { typeof(ItemDrop), typeof(bool) })] internal static class CopiedWorldItemPatch { private static void Prefix(ItemDrop item, ref bool cheated) { cheated = false; Sanitizer.Clean(item); } } [HarmonyPatch(typeof(ItemDrop), "SaveToZDO")] internal static class ZdoWritePatch { private static void Prefix(ItemData itemData) { Sanitizer.Clean(itemData); } } [HarmonyPatch(typeof(ItemDrop), "LoadFromZDO")] internal static class ZdoReadPatch { private static void Postfix(ItemData itemData) { Sanitizer.Clean(itemData); } } [HarmonyPatch(typeof(ItemDrop), "Pickup")] internal static class PickupPatch { private static void Prefix(ItemDrop __instance) { Sanitizer.Clean(__instance); } } [HarmonyPatch(typeof(ItemDrop), "GetHoverText")] internal static class HoverTextPatch { private static void Prefix(ItemDrop __instance) { Sanitizer.Clean(__instance); } } [HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[] { typeof(int) })] internal static class TooltipPatch { private static void Prefix(ItemData __instance) { Sanitizer.Clean(__instance); } } [HarmonyPatch(typeof(Container), "Save")] internal static class ContainerSavePatch { private static void Prefix(Container __instance) { Sanitizer.Clean(__instance.GetInventory()); } } [HarmonyPatch(typeof(Container), "Load")] internal static class ContainerLoadPatch { private static void Postfix(Container __instance) { Sanitizer.Clean(__instance.GetInventory()); } }