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 AchievementEnabler v0.5.1
plugins/AchievementEnabler.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("AchievementEnabler")] [assembly: AssemblyDescription("Lets a modded Valheim earn achievements. Mods no longer count as cheating, and old cheat marks can be cleared.")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("MidnightMods")] [assembly: AssemblyProduct("AchievementEnabler")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("6f2a1c74-58d0-4a3e-9b21-7c4e0d8f5a13")] [assembly: AssemblyFileVersion("0.5.1")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("0.5.1.0")] namespace AchievementEnabler; [BepInPlugin("MidnightsFX.AchievementEnabler", "AchievementEnabler", "0.5.1")] public class AchievementEnabler : BaseUnityPlugin { public const string PluginGUID = "MidnightsFX.AchievementEnabler"; public const string PluginName = "AchievementEnabler"; public const string PluginVersion = "0.5.1"; internal static ManualLogSource Log; private void Awake() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; ValConfig.Init(((BaseUnityPlugin)this).Config); if (!ValConfig.Enabled.Value) { Logger.LogInfo("Disabled in config; nothing patched."); return; } Harmony val = new Harmony("MidnightsFX.AchievementEnabler"); Type[] types = typeof(AchievementEnabler).Assembly.GetTypes(); foreach (Type type in types) { try { val.CreateClassProcessor(type).Patch(); } catch (Exception ex) { Logger.LogError("Could not apply " + type.FullName + ": " + ex.Message); } } ClearCheatedItemsCommand.Register(); Logger.LogInfo("Ready."); } } internal static class AchievementGatePatches { [HarmonyPatch(typeof(Achievements), "CanGetAchievements")] private static class StatWriteGate { private static bool Prefix(ref bool __result) { __result = true; return false; } } [HarmonyPatch(typeof(Achievements), "IsCheatedAtAll")] private static class CheatedAtAllGate { private static bool Prefix(ref bool __result) { __result = false; return false; } } [HarmonyPatch(typeof(Achievements), "CheckMetaAchievementProgressUnlock")] private static class MetaAchievements { private static bool running; private static void Award(Achievement achievement) { if (InitializeSweep.Running) { Logger.LogDebug("'" + achievement.m_id + "' is earned by holding other achievements, and was already due."); Achievements.AchievementEvent(achievement, false, false); } else { Logger.LogInfo("Awarding '" + achievement.m_id + "', which is earned by holding other achievements."); Achievements.AchievementEvent(achievement, true, true); } } private static bool Prefix(string calleeId) { if (running || (Object)(object)Achievements.m_instance == (Object)null) { return false; } running = true; try { bool flag; do { flag = false; foreach (AchievementList achievementList in Achievements.m_instance.m_achievementLists) { foreach (Achievement achievement in achievementList.m_achievements) { if (!(achievement.m_id == calleeId) && achievement.m_otherAchievementTriggers.Count != 0 && !achievement.m_unlocked && achievement.CheckUnlocked()) { Award(achievement); flag = true; } } } } while (flag); } finally { running = false; } return false; } } [HarmonyPatch(typeof(Achievements), "Initialize")] private static class InitializeSweep { internal static bool Running; private static void Prefix() { Running = true; } private static void Finalizer() { Running = false; } } [HarmonyPatch] private static class BypassCheatChecks { private const BindingFlags AnyStatic = BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static readonly MethodInfo Getter = typeof(PlayerProfile).GetProperty("s_bypassCheatChecks", BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetGetMethod(nonPublic: true); private static bool Prepare() { if (Getter != null) { return true; } FieldInfo field = typeof(PlayerProfile).GetField("s_bypassCheatChecks", BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType == typeof(bool)) { field.SetValue(null, true); Logger.LogInfo("PlayerProfile.s_bypassCheatChecks is a field in this game version; set it to true."); } else { Logger.LogWarning("Could not find PlayerProfile.s_bypassCheatChecks, so the game will keep flagging new things as cheated. Achievements still count."); } return false; } private static MethodBase TargetMethod() { return Getter; } private static bool Prefix(ref bool __result) { __result = true; return false; } } [HarmonyPatch(typeof(ItemDrop), "OnCreateNew", new Type[] { typeof(ItemDrop), typeof(bool) })] private static class NewItemDrop { private static void Prefix(ref bool cheated) { cheated = false; } } [HarmonyPatch(typeof(CharacterDrop), "DropItems")] private static class CreatureLoot { private static void Prefix(ref bool cheated) { cheated = false; } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(string), typeof(int), typeof(int), typeof(int), typeof(long), typeof(string), typeof(bool), typeof(bool) })] private static class NewInventoryItem { private static void Prefix(ref bool cheated) { cheated = false; } } [HarmonyPatch(typeof(ConsoleCommand), "RunAction")] private static class CheatCommand { private static void Prefix(ConsoleCommand __instance, out bool __state) { __state = __instance.IsCheat; __instance.IsCheat = false; } private static void Finalizer(ConsoleCommand __instance, bool __state) { __instance.IsCheat = __state; } } [HarmonyPatch(typeof(ServerOptionsGUI), "SetKeyAndValueIsCheat")] private static class SetKey { private static bool Prefix(ref bool __result) { __result = false; return false; } } } internal static class AchievementsPanelPatches { [HarmonyPatch(typeof(InventoryGui), "UpdateAchievementsList")] private static class AchievementsList { private static bool warned; private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); int num = ReplaceFieldReads(list, "m_isSecret", "IsSecret"); int num2 = ReplaceFieldReads(list, "m_clickable", "IsClickable"); if (!warned && num == 0) { Logger.LogWarning("Could not find the secret-achievement check in InventoryGui.UpdateAchievementsList, so secret achievements stay concealed there."); } if (!warned && num2 == 0) { Logger.LogWarning("Could not find the clickable check in InventoryGui.UpdateAchievementsList, so achievements the game keeps shut stay shut."); } warned |= num == 0 || num2 == 0; return list; } } [HarmonyPatch(typeof(AchievementsGui), "OnOpenAchievementDetails")] private static class AchievementDetails { private static bool warned; private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = new List<CodeInstruction>(instructions); if (ReplaceFieldReads(list, "m_isSecret", "IsSecret") == 0 && !warned) { warned = true; Logger.LogWarning("Could not find the secret-achievement check in AchievementsGui.OnOpenAchievementDetails, so secret achievements stay concealed there."); } return list; } private static void Prefix(AchievementsGui __instance, Achievement achievement, bool clickable, out bool __state) { __state = clickable && !__instance.m_achievementDetails.activeSelf && !IsConcealed(achievement); if (__state) { GatherConcealedRequirements(); } } private static void Postfix(AchievementDetailUnlockCondition ___m_achievementDetailsElementPrefab, RectTransform ___m_achievementDetailsListRoot, Achievement achievement, bool __state) { if (!__state || !ValConfig.ShowAllRequirements.Value) { return; } foreach (Achievement otherAchievementTrigger in achievement.m_otherAchievementTriggers) { string text = (IsConcealed(otherAchievementTrigger) ? "$inventory_achievement_secret" : otherAchievementTrigger.m_name); AddRow(___m_achievementDetailsElementPrefab, ___m_achievementDetailsListRoot, Localization.instance.Localize(text), otherAchievementTrigger.m_unlocked ? 1f : 0f, 1f); } } } [HarmonyPatch(typeof(AchievementsGui), "CreateStatRow")] private static class RequirementRow { private static bool Prefix(AchievementDetailUnlockCondition ___m_achievementDetailsElementPrefab, RectTransform ___m_achievementDetailsListRoot, string statKey, float currentAmount, float totalAmount) { if (!ValConfig.ShowAllRequirements.Value || (currentAmount < totalAmount && concealedRequirements.Contains(statKey))) { return true; } AddRow(___m_achievementDetailsElementPrefab, ___m_achievementDetailsListRoot, RequirementName(statKey), currentAmount, totalAmount); return false; } } private static readonly HashSet<string> concealedRequirements = new HashSet<string>(); private static bool IsSecret(Achievement achievement) { if (achievement.m_isSecret) { return !ValConfig.ShowSecretAchievements.Value; } return false; } private static bool IsClickable(Achievement achievement) { if (!achievement.m_clickable) { return ValConfig.ShowAllRequirements.Value; } return true; } private static bool IsConcealed(Achievement achievement) { if (IsSecret(achievement)) { return !achievement.m_unlocked; } return false; } private unsafe static void GatherConcealedRequirements() { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: 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_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) concealedRequirements.Clear(); foreach (AchievementList achievementList in Achievements.m_instance.m_achievementLists) { foreach (Achievement achievement in achievementList.m_achievements) { if (!IsConcealed(achievement)) { continue; } foreach (PlayerStatRequirement item in achievement.m_statTrigger) { HashSet<string> hashSet = concealedRequirements; PlayerStatType stat = item.m_stat; hashSet.Add(((object)(*(PlayerStatType*)(&stat))/*cast due to .constrained prefix*/).ToString()); } foreach (EnemyStatRequirement enemyStatsTrigger in achievement.m_enemyStatsTriggers) { concealedRequirements.Add(enemyStatsTrigger.m_stat); } List<DictStatRequirement>[] array = new List<DictStatRequirement>[8] { achievement.m_knownWorldTriggers, achievement.m_knownWorldKeysTriggers, achievement.m_knownCommandsTriggers, achievement.m_piecePlacedTriggers, achievement.m_itemPickupTriggers, achievement.m_itemCraftTriggers, achievement.m_foodEatenTriggers, achievement.m_pickableTriggers }; for (int i = 0; i < array.Length; i++) { foreach (DictStatRequirement item2 in array[i]) { concealedRequirements.Add(item2.m_stat); } } } } } private static int ReplaceFieldReads(List<CodeInstruction> code, string field, string replacement) { FieldInfo fieldInfo = AccessTools.Field(typeof(Achievement), field); MethodInfo operand = AccessTools.Method(typeof(AchievementsPanelPatches), replacement, (Type[])null, (Type[])null); int num = 0; foreach (CodeInstruction item in code) { if (CodeInstructionExtensions.LoadsField(item, fieldInfo, false)) { item.opcode = OpCodes.Call; item.operand = operand; num++; } } return num; } private static void AddRow(AchievementDetailUnlockCondition prefab, RectTransform root, string name, float current, float total) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) AchievementDetailUnlockCondition obj = Object.Instantiate<AchievementDetailUnlockCondition>(prefab, (Transform)(object)root); ((Component)obj).gameObject.SetActive(true); Color color = ((current >= total) ? Color.green : Color.gray); ((Graphic)obj.StatName).color = color; ((Graphic)obj.Progress).color = color; ((TMP_Text)obj.StatName).text = name; ((TMP_Text)obj.Progress).text = $"{Mathf.FloorToInt(current)} / {total}"; } private static string RequirementName(string statKey) { if (!Enum.TryParse<PlayerStatType>(statKey, out PlayerStatType _)) { string text = Localization.instance.Localize(statKey); if (!string.IsNullOrEmpty(text)) { return text; } return statKey; } string text2 = "stat_" + statKey; string text3 = Localization.instance.Localize("$" + text2); if (text3 != "[" + text2 + "]") { return text3; } string text4 = Regex.Replace(statKey, "(?<=[a-z])(?=[A-Z0-9])", " "); return text4.Substring(0, 1) + text4.Substring(1).ToLowerInvariant(); } } internal static class AchievementStatBackfill { [HarmonyPatch(typeof(PlayerProfile), "LoadPlayerFromDisk")] private static class CharacterLoad { private static void Postfix(PlayerProfile __instance, bool __result) { if (__result && ValConfig.BackfillAchievementStats.Value) { Backfill(__instance); } } } private const int RawStats = 0; private const int Any = 1; private static void Backfill(PlayerProfile profile) { PlayerStats[] playerStats = profile.m_playerStats; if (playerStats == null || playerStats.Length <= 1) { Logger.LogWarning("The character's stat tables are not the shape this game version was expected to have; left alone."); return; } PlayerStats val = playerStats[0]; PlayerStats val2 = playerStats[1]; if (val == null || val2 == null) { return; } int num = Merge(val.m_stats, val2.m_stats); if (val.m_enemyStats != null && val2.m_enemyStats != null) { for (int i = 0; i < val.m_enemyStats.Length && i < val2.m_enemyStats.Length; i++) { num += Merge(val.m_enemyStats[i], val2.m_enemyStats[i]); } } num += Merge(val.m_itemPickupStats, val2.m_itemPickupStats); num += Merge(val.m_itemCraftStats, val2.m_itemCraftStats); num += Merge(val.m_pickableStats, val2.m_pickableStats); num += Merge(val.m_foodEatenStats, val2.m_foodEatenStats); num += Merge(val.m_piecesPlacedStats, val2.m_piecesPlacedStats); if (num > 0) { Logger.LogInfo($"Counted {num} stat(s) towards achievements on character '{profile.GetName()}' that the game had recorded but not counted."); } else { Logger.LogDebug("Nothing to count towards achievements on character '" + profile.GetName() + "'."); } } private static int Merge<TKey>(Dictionary<TKey, float> raw, Dictionary<TKey, float> counted) { if (raw == null || counted == null) { return 0; } int num = 0; foreach (KeyValuePair<TKey, float> item in raw) { if (!counted.TryGetValue(item.Key, out var value) || !(value >= item.Value)) { counted[item.Key] = item.Value; num++; } } return num; } } internal static class BestStatPatches { [HarmonyPatch(typeof(PlayerProfile), "SetStat")] private static class RecordWrite { private static bool Prefix(PlayerProfile __instance, PlayerStatType stat, float amount, bool cheated) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) if (!ValConfig.KeepBestStats.Value || !BestStats.Contains(stat)) { return true; } PlayerStats[] playerStats = __instance.m_playerStats; if (playerStats == null || playerStats.Length <= 1) { return true; } Raise(playerStats[0].m_stats, stat, amount); if (Achievements.CanGetAchievements(cheated)) { Raise(playerStats[1].m_stats, stat, amount); Raise(playerStats[Achievements.GetCurrentAchievementDifficultyIndex()].m_stats, stat, amount); if (playerStats[1][stat] > amount) { Logger.LogDebug($"Kept {stat} at {playerStats[1][stat]} rather than lowering it to {amount}."); } Achievements.AchievementStatSetEvent(stat, (uint)playerStats[1][stat]); } return false; } } [HarmonyPatch(typeof(Player), "SurvivedOneDay")] private static class DaySurvived { private static void Postfix() { if (!ValConfig.KeepBestStats.Value) { return; } PlayerStats[] array = Game.instance.GetPlayerProfile()?.m_playerStats; if (array == null || array.Length <= 1) { return; } bool flag = false; for (int i = 0; i < array.Length; i++) { if (StreakIntoBest(array[i]) && i == 1) { flag = true; } } if (flag) { Achievements.AchievementStatSetEvent((PlayerStatType)129, (uint)array[1][(PlayerStatType)129]); } } } [HarmonyPatch(typeof(PlayerProfile), "LoadPlayerFromDisk")] private static class CharacterLoad { [HarmonyPriority(200)] private static void Postfix(PlayerProfile __instance, bool __result) { if (__result && ValConfig.KeepBestStats.Value) { Repair(__instance); } } } private const int RawStats = 0; private const int Any = 1; private const int FirstDifficulty = 2; private static readonly HashSet<PlayerStatType> BestStats = FindBestStats(); private static HashSet<PlayerStatType> FindBestStats() { //IL_0053: 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_0074: Unknown result type (might be due to invalid IL or missing references) HashSet<PlayerStatType> hashSet = new HashSet<PlayerStatType> { (PlayerStatType)129, (PlayerStatType)132, (PlayerStatType)130, (PlayerStatType)131 }; foreach (PlayerStatType value in Enum.GetValues(typeof(PlayerStatType))) { if (((object)value/*cast due to .constrained prefix*/).ToString().StartsWith("BuildCluster", StringComparison.Ordinal)) { hashSet.Add(value); } } return hashSet; } private static void Repair(PlayerProfile profile) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) PlayerStats[] playerStats = profile.m_playerStats; if (playerStats == null || playerStats.Length <= 1 || playerStats[0] == null || playerStats[1] == null) { return; } Dictionary<PlayerStatType, float> dictionary = new Dictionary<PlayerStatType, float>(); foreach (PlayerStatType bestStat in BestStats) { dictionary[bestStat] = Get(playerStats[1].m_stats, bestStat); } PlayerStats[] array = playerStats; for (int i = 0; i < array.Length; i++) { StreakIntoBest(array[i]); } foreach (PlayerStatType bestStat2 in BestStats) { float num = 0f; for (int j = 2; j < playerStats.Length; j++) { if (playerStats[j] != null) { num = Math.Max(num, Get(playerStats[j].m_stats, bestStat2)); } } Raise(playerStats[0].m_stats, bestStat2, num); Raise(playerStats[1].m_stats, bestStat2, num); } List<string> list = new List<string>(); foreach (PlayerStatType bestStat3 in BestStats) { float num2 = Get(playerStats[1].m_stats, bestStat3); if (num2 > dictionary[bestStat3]) { list.Add($"{bestStat3} {dictionary[bestStat3]:0.#} -> {num2:0.#}"); } } if (list.Count > 0) { Logger.LogInfo("Restored best stats on character '" + profile.GetName() + "' that the game had lowered: " + string.Join(", ", list) + "."); } else { Logger.LogDebug("No lowered best stats to restore on character '" + profile.GetName() + "'."); } } private static bool StreakIntoBest(PlayerStats table) { if (table == null) { return false; } return Raise(table.m_stats, (PlayerStatType)129, Get(table.m_stats, (PlayerStatType)128)); } private static bool Raise(Dictionary<PlayerStatType, float> stats, PlayerStatType stat, float value) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (stats.TryGetValue(stat, out var value2) && value2 >= value) { return false; } stats[stat] = value; return true; } private static float Get(Dictionary<PlayerStatType, float> stats, PlayerStatType stat) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) if (!stats.TryGetValue(stat, out var value)) { return 0f; } return value; } } internal static class CheatFlagPatches { [HarmonyPatch(typeof(PlayerProfile), "LoadPlayerFromDisk")] private static class CharacterLoad { private static void Postfix(PlayerProfile __instance, bool __result) { if (__result && __instance.m_usedCheats && ValConfig.ClearCharacterCheatFlag.Value) { __instance.m_usedCheats = false; Logger.LogInfo("Cleared the cheat flag on character '" + __instance.GetName() + "'."); } } } [HarmonyPatch(typeof(Achievements), "IsWorldCheated")] private static class WorldCheck { private static bool Prefix(ref bool __result) { if (!ValConfig.IgnoreWorldCheatFlag.Value) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(ItemData), "Load", new Type[] { typeof(ZPackage), typeof(ItemData), typeof(Item) })] private static class ItemLoad { private static void Postfix(ItemData itemData) { if (ValConfig.ClearItemCheatFlagOnLoad.Value) { itemData.m_cheated = false; } } } [HarmonyPatch] private static class InventoryLoad { private static IEnumerable<MethodBase> TargetMethods() { yield return AccessTools.Method(typeof(Inventory), "Load", new Type[1] { typeof(ZPackage) }, (Type[])null); yield return AccessTools.Method(typeof(Inventory), "Load", new Type[2] { typeof(ZPackage), typeof(bool) }, (Type[])null); } private static void Postfix(Inventory __instance) { if (!ValConfig.ClearItemCheatFlagOnLoad.Value) { return; } foreach (ItemData allItem in __instance.GetAllItems()) { allItem.m_cheated = false; } } } } internal static class ClearCheatedItemsCommand { internal const string Name = "clearcheateditems"; internal static void Register() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_0020: Unknown result type (might be due to invalid IL or missing references) new ConsoleCommand("clearcheateditems", "clears the cheated flag from every item you are carrying", new ConsoleEventFailable(Run), true, true, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } private static object Run(ConsoleEventArgs args) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return "you need to be in a world with a character loaded"; } List<string> list = new List<string>(); foreach (ItemData allItem in ((Humanoid)localPlayer).GetInventory().GetAllItems()) { if (allItem.m_cheated) { allItem.m_cheated = false; string text = Localization.instance.Localize(allItem.m_shared.m_name); list.Add((allItem.m_stack > 1) ? $"{text} x{allItem.m_stack}" : text); } } if (list.Count == 0) { args.Context.AddString("Nothing to clear: none of the items you are carrying are flagged as cheated."); return true; } args.Context.AddString(string.Format("Cleared the cheated flag from {0} item(s): {1}", list.Count, string.Join(", ", list))); Logger.LogInfo(string.Format("{0} cleared the cheated flag from {1} item(s).", "clearcheateditems", list.Count)); return true; } } internal class Logger { public static LogLevel Level = (LogLevel)16; public static void EnableDebugLogging(object sender, EventArgs e) { CheckEnableDebugLogging(); } public static void CheckEnableDebugLogging() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (ValConfig.EnableDebugMode.Value) { Level = (LogLevel)32; } else { Level = (LogLevel)16; } } public static void SetDebugLogging(bool state) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (state) { Level = (LogLevel)32; } else { Level = (LogLevel)16; } } public static void LogDebug(string message) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 if ((int)Level >= 32) { AchievementEnabler.Log.LogInfo((object)("[DEBUG]" + message)); } } public static void LogInfo(string message) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 if ((int)Level >= 16) { AchievementEnabler.Log.LogInfo((object)message); } } public static void LogWarning(string message) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)Level >= 4) { AchievementEnabler.Log.LogWarning((object)message); } } public static void LogError(string message) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)Level >= 2) { AchievementEnabler.Log.LogError((object)message); } } } internal class ValConfig { private static ConfigFile cfg; public static ConfigEntry<bool> Enabled; public static ConfigEntry<bool> EnableDebugMode; public static ConfigEntry<bool> ClearCharacterCheatFlag; public static ConfigEntry<bool> IgnoreWorldCheatFlag; public static ConfigEntry<bool> ClearItemCheatFlagOnLoad; public static ConfigEntry<bool> BackfillAchievementStats; public static ConfigEntry<bool> KeepBestStats; public static ConfigEntry<bool> ShowAllRequirements; public static ConfigEntry<bool> ShowSecretAchievements; public static ConfigEntry<bool> VanillaOnlyRequirements; public static void Init(ConfigFile config) { cfg = config; cfg.SaveOnConfigSet = false; Enabled = cfg.Bind<bool>("General", "Enabled", true, "Master switch, read at startup. Off means nothing is patched and the clearcheateditems command\nis not added."); EnableDebugMode = cfg.Bind<bool>("General", "EnableDebugMode", false, "Verbose logging."); ClearCharacterCheatFlag = cfg.Bind<bool>("CheatFlags", "ClearCharacterCheatFlag", true, "Clear the cheat flag from a character when it loads. The flag no longer blocks achievements, and\ncheat commands no longer set it, but without this a character flagged before still gets a cheated\nnotice in the achievements panel."); IgnoreWorldCheatFlag = cfg.Bind<bool>("CheatFlags", "IgnoreWorldCheatFlag", true, "Never treat a world as cheated. The game counts a world as cheated for good once it has a starting\nkey the World Modifiers screen cannot set. That no longer blocks achievements, but without this\nsuch a world still gets a cheated notice in the achievements panel. The keys are left alone."); ClearItemCheatFlagOnLoad = cfg.Bind<bool>("CheatFlags", "ClearItemCheatFlagOnLoad", true, "Clear the cheated flag from every item as it loads, wherever it loads from: your inventory,\nchests, tombstones and items lying in the world. Since Valheim 1.0.14 a flagged item will not\nstack with a clean one, so a single flagged stack keeps splitting every stack of that item you\npick up. Clearing it is what puts stacking back. The flag is gone from the save the next time\nthe item is written, so this cannot be undone by turning the setting off again."); BackfillAchievementStats = cfg.Bind<bool>("Achievements", "BackfillAchievementStats", true, "Count progress you made while achievements were switched off. The game records every stat twice:\nonce in a raw table that always counts, and once in the table achievements actually read, which\nit skips whenever cheats or mods are detected. Anything done before this mod was installed is\ntherefore in the raw table only, and the game never goes back for it - which is why a character\nwith hundreds of trees felled can still show no progress towards Lumberjack. This copies the raw\nnumbers across when the character loads, taking the higher of the two, so nothing is counted twice."); KeepBestStats = cfg.Bind<bool>("Achievements", "KeepBestStats", true, "Stop the game lowering your best-ever stats, and put back the ones it already lowered: most days\nsurvived in a row, highest comfort, highest piece built, and the most of each kind of piece built\naround you. The game checks a new value against your best at the world's current combat difficulty\nonly, then writes it over your overall best, so the first days after a change of difficulty can\nwrite a small number over a large one and relock achievements such as surviving 30 days. With this\non, a best is only ever raised, your best run of days keeps up with your current one, and bests the\ngame already lowered are put back from its per-difficulty records when the character loads."); ShowAllRequirements = cfg.Bind<bool>("AchievementsPanel", "ShowAllRequirements", false, "Show what the achievements you already know about ask for. Every achievement in the panel that is\nnot concealed opens, including the ones the game keeps shut, such as The Homesteader, and its\ndetails name every requirement with your progress, not only the ones already met. Achievements\nearned by holding others list those others. Secret achievements you have not earned stay\nconcealed, and so does any requirement that would give one away, such as the bosses The Viking\nasks for. Turn on ShowSecretAchievements too to see every achievement and every requirement.\nClient side: changes only what your own panel shows, never what counts or unlocks, and takes\neffect the next time the panel is opened."); ShowSecretAchievements = cfg.Bind<bool>("AchievementsPanel", "ShowSecretAchievements", false, "Show every achievement in the achievements panel. Secret achievements are listed under their real\nname and description instead of \"Concealed\" and \"Only Odin knows what is hidden here.\". With\nShowAllRequirements on too, every achievement opens and names every requirement. Off by default\nbecause it spoils them. Client side: changes only what your own panel shows, never what counts or\nunlocks, and takes effect the next time the panel is opened."); VanillaOnlyRequirements = cfg.Bind<bool>("Achievements", "VanillaOnlyRequirements", false, "Limit the craft-, eat-, build-, find- and kill-everything achievements to vanilla items, food, build\npieces, trophies and creatures, leaving out what other mods add. Client-side, and can be changed\nwhile the game is running. Achievements this lets you unlock stay unlocked if you turn it off again."); cfg.Save(); cfg.SaveOnConfigSet = true; Logger.CheckEnableDebugLogging(); EnableDebugMode.SettingChanged += Logger.EnableDebugLogging; VanillaOnlyRequirements.SettingChanged += VanillaRequirementPatches.Reapply; } } internal static class MainMenuPatches { [HarmonyPatch(typeof(FejdStartup), "SetupGui")] private static class ModdedLabel { private static bool warned; private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { FieldInfo fieldInfo = AccessTools.Field(typeof(FejdStartup), "m_moddedText"); MethodInfo methodInfo = AccessTools.Method(typeof(GameObject), "SetActive", (Type[])null, (Type[])null); List<CodeInstruction> list = new List<CodeInstruction>(instructions); int num = 0; for (int i = 0; i + 2 < list.Count; i++) { if (CodeInstructionExtensions.LoadsField(list[i], fieldInfo, false) && CodeInstructionExtensions.Calls(list[i + 2], methodInfo)) { list[i + 1].opcode = OpCodes.Ldc_I4_1; list[i + 1].operand = null; num++; } } if (num == 0 && !warned) { warned = true; Logger.LogWarning("Could not find m_moddedText.SetActive in FejdStartup.SetupGui, so the main menu's modded label was not changed."); } return list; } } } internal static class VanillaRequirementPatches { [HarmonyPatch(typeof(ObjectDB), "Awake")] private static class ObjectDBAwake { [HarmonyPriority(800)] [HarmonyBefore(new string[] { "com.jotunn.jotunn" })] private static void Prefix(ObjectDB __instance) { RecordItems(__instance.m_items); } } [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] private static class ObjectDBCopy { [HarmonyPriority(800)] [HarmonyBefore(new string[] { "com.jotunn.jotunn" })] private static void Prefix(ObjectDB other) { RecordItems(other?.m_items); } } [HarmonyPatch(typeof(ZNetScene), "Awake")] private static class ZNetSceneAwake { [HarmonyPriority(800)] [HarmonyBefore(new string[] { "com.jotunn.jotunn" })] private static void Prefix(ZNetScene __instance) { RecordCreatures(__instance.m_prefabs); } } [HarmonyPatch] private static class Filter { private static bool Prepare() { if (Initialize != null) { return true; } Logger.LogWarning("Could not find Achievements.Initialize; VanillaOnlyRequirements will have no effect."); return false; } private static MethodBase TargetMethod() { return Initialize; } private static void Postfix() { if (!ValConfig.VanillaOnlyRequirements.Value || (Object)(object)Achievements.m_instance == (Object)null) { return; } if (s_itemNames == null || s_pieceNames == null || s_creatureNames == null) { Logger.LogWarning("Vanilla content was not recorded before the achievements were set up; leaving their requirements as they are."); return; } foreach (AchievementList achievementList in Achievements.m_instance.m_achievementLists) { foreach (Achievement achievement in achievementList.m_achievements) { if (!achievement.m_isDynamicallyPopulated) { continue; } int num; int count; switch (achievement.m_id) { case "AllWeaponCraft": case "AllItemCraft": case "AllFoodCooked": num = RemoveNonVanilla(achievement, achievement.m_itemCraftTriggers, (DictStatRequirement r) => r.m_stat, s_itemNames); count = achievement.m_itemCraftTriggers.Count; break; case "AllFoodEaten": num = RemoveNonVanilla(achievement, achievement.m_foodEatenTriggers, (DictStatRequirement r) => r.m_stat, s_itemNames); count = achievement.m_foodEatenTriggers.Count; break; case "FindAllTrophies": num = RemoveNonVanilla(achievement, achievement.m_itemPickupTriggers, (DictStatRequirement r) => r.m_stat, s_itemNames); count = achievement.m_itemPickupTriggers.Count; break; case "AllBuildPieces": num = RemoveNonVanilla(achievement, achievement.m_piecePlacedTriggers, (DictStatRequirement r) => r.m_stat, s_pieceNames); count = achievement.m_piecePlacedTriggers.Count; break; case "KillAllCreatures": case "KillAllCreaturesHard": num = RemoveNonVanilla(achievement, achievement.m_enemyStatsTriggers, (EnemyStatRequirement r) => r.m_stat, s_creatureNames); count = achievement.m_enemyStatsTriggers.Count; break; default: continue; } Logger.LogDebug($"{achievement.m_id}: dropped {num} non-vanilla requirements ({count} left)."); if (num > 0 && !achievement.m_unlocked && achievement.CheckUnlocked()) { Achievements.AchievementEvent(achievement, false, false); Logger.LogInfo(achievement.m_id + " is complete with only vanilla requirements; unlocked it."); } } } } private static int RemoveNonVanilla<T>(Achievement achievement, List<T> requirements, Func<T, string> stat, HashSet<string> vanilla) { List<string> dropped = null; int result = requirements.RemoveAll(delegate(T r) { string text = stat(r); if (text == null || vanilla.Contains(text)) { return false; } (dropped ?? (dropped = new List<string>())).Add(text); return true; }); if (dropped != null) { Logger.LogDebug(achievement.m_id + ": dropped " + string.Join(", ", dropped)); } return result; } } private const BindingFlags AnyInstance = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private const BindingFlags AnyStatic = BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static readonly MethodInfo Initialize = typeof(Achievements).GetMethod("Initialize", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); private static readonly FieldInfo PlatformInitialized = typeof(Achievements).GetField("m_platformInitialized", BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); private static HashSet<string> s_itemNames; private static HashSet<string> s_pieceNames; private static HashSet<string> s_creatureNames; public static void Reapply(object sender, EventArgs e) { if (!(Initialize == null) && !((Object)(object)Achievements.m_instance == (Object)null) && !((Object)(object)Game.instance == (Object)null) && (!(PlatformInitialized != null) || (bool)PlatformInitialized.GetValue(null))) { Initialize.Invoke(Achievements.m_instance, null); } } private static void RecordItems(List<GameObject> items) { if (s_itemNames != null || items == null || items.Count == 0) { return; } HashSet<string> hashSet = new HashSet<string>(); HashSet<string> hashSet2 = new HashSet<string>(); foreach (GameObject item in items) { ItemDrop val = (Object.op_Implicit((Object)(object)item) ? item.GetComponent<ItemDrop>() : null); SharedData val2 = ((!Object.op_Implicit((Object)(object)val)) ? null : val.m_itemData?.m_shared); if (val2 == null) { continue; } if (!string.IsNullOrEmpty(val2.m_name)) { hashSet.Add(val2.m_name); } if (!Object.op_Implicit((Object)(object)val2.m_buildPieces)) { continue; } foreach (GameObject piece in val2.m_buildPieces.m_pieces) { Piece val3 = (Object.op_Implicit((Object)(object)piece) ? piece.GetComponent<Piece>() : null); if ((Object)(object)val3 != (Object)null && !string.IsNullOrEmpty(val3.m_name)) { hashSet2.Add(val3.m_name); } } } s_itemNames = hashSet; s_pieceNames = hashSet2; Logger.LogInfo($"Recorded {hashSet.Count} vanilla items and {hashSet2.Count} vanilla build pieces."); } private static void RecordCreatures(List<GameObject> prefabs) { if (s_creatureNames != null || prefabs == null || prefabs.Count == 0) { return; } HashSet<string> hashSet = new HashSet<string>(); foreach (GameObject prefab in prefabs) { Character val = (Object.op_Implicit((Object)(object)prefab) ? prefab.GetComponent<Character>() : null); if ((Object)(object)val != (Object)null && !string.IsNullOrEmpty(val.m_name)) { hashSet.Add(val.m_name); } } s_creatureNames = hashSet; Logger.LogInfo($"Recorded {hashSet.Count} vanilla creatures."); } }