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 Unshamed v1.0.0
Unshamed.dll
Decompiled 8 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Unshamed")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Azumatt")] [assembly: AssemblyProduct("Unshamed")] [assembly: AssemblyCopyright("Copyright © 2022")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("E0E2F92E-557C-4A05-9D89-AA92A0BD75C4")] [assembly: AssemblyFileVersion("1.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 Unshamed { internal static class Audit { private const int Raw = 0; private const int Any = 1; private static readonly int FirstProgressKey = 42; private static readonly HashSet<string> LooksLikeCheating = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "nobuildcost", "nocraftcost", "allrecipesunlocked", "allpiecesunlocked", "noworkbench", "dungeonbuild", "teleportall", "passivemobs" }; internal static List<string> Run(PlayerProfile profile) { List<string> list = new List<string>(); PlayerStats val = profile.m_playerStats[0]; list.Add("--- eligibility ---"); list.Add("Achievements.IsCheatedAtAll: " + Eligibility.Cheated()); list.Add(" " + Eligibility.Describe()); list.Add("Cheats stat: " + (val.m_stats.TryGetValue((PlayerStatType)4, out var value) ? value : 0f)); List<string> list2 = CheatFlags.CheatCommandsUsed(profile).ToList(); list.Add("Cheat commands in history: " + ((list2.Count == 0) ? "none" : string.Join(", ", list2))); list.Add("Commands in history overall: " + val.m_knownCommands.Count + " (vanilla records every command you run, not just cheats)"); Buckets(profile, list); Keys(val, list); return list; } private static void Buckets(PlayerProfile profile, List<string> report) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Invalid comparison between Unknown and I4 //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Expected I4, but got Unknown //IL_00cc: Unknown result type (might be due to invalid IL or missing references) report.Add(""); report.Add("--- stat buckets ---"); foreach (DifficultyRequirement item in Enum.GetValues(typeof(DifficultyRequirement)).Cast<DifficultyRequirement>()) { if ((int)item != 10) { PlayerStats val = profile.m_playerStats[item]; int num = val.m_enemyStats.Sum((Dictionary<string, float> d) => d.Count) + val.m_itemPickupStats.Count + val.m_itemCraftStats.Count + val.m_pickableStats.Count + val.m_foodEatenStats.Count + val.m_piecesPlacedStats.Count; report.Add($"{(int)item,2} {item,-12} {val.m_stats.Count((KeyValuePair<PlayerStatType, float> s) => s.Value > 0f),4} counters, {num,5} named entries"); } } StatSync.Result result = StatSync.Apply(profile, preview: true); report.Add(result.Any ? ("RawStats is ahead of Any by " + result.ToString() + ". 'unshamed retro' copies that forward.") : "Any is level with RawStats, nothing to copy forward."); } private static void Keys(PlayerStats raw, List<string> report) { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Invalid comparison between Unknown and I4 List<string> list = new List<string>(); List<string> list2 = new List<string>(); List<string> list3 = new List<string>(); List<string> list4 = new List<string>(); List<string> list5 = new List<string>(); foreach (KeyValuePair<string, float> knownWorldKey in raw.m_knownWorldKeys) { Split(knownWorldKey.Key, out string key, out string value); string item = $"{knownWorldKey.Key} ({TimeSpan.FromSeconds(knownWorldKey.Value):c})"; GlobalKeys result; if (key.Length > 0 && key.All(char.IsDigit)) { list4.Add(item); } else if (!Enum.TryParse<GlobalKeys>(key, ignoreCase: true, out result) || !Enum.IsDefined(typeof(GlobalKeys), result)) { (key.Contains("7656119") ? list5 : list3).Add(item); } else if (!(value == "default") && (int)result < FirstProgressKey) { (LooksLikeCheating.Contains(key) ? list2 : list).Add(item); } } report.Add(""); report.Add("--- world keys (" + raw.m_knownWorldKeys.Count + " entries, and vanilla never clears this) ---"); Section(report, "Most likely to be read as cheating", list2); Section(report, "Other non-default modifiers", list); Section(report, "No vanilla GlobalKeys member matches, so a mod put them there", list3); Section(report, "Entries carrying somebody's SteamID", list5); Section(report, "Bare numbers, which vanilla wrote itself", list4); report.Add(""); report.Add("These are your character's keys. What actually marks a world cheated in 1.0 is the world's own m_startingGlobalKeys, checked by ServerOptionsGUI.WorldContainsCheatedModifiers: anything you cannot set through the World Modifiers menu counts."); } private static void Section(List<string> report, string title, List<string> lines) { report.Add(title + ": " + ((lines.Count == 0) ? "none" : lines.Count.ToString())); foreach (string item in lines.OrderBy((string l) => l)) { report.Add(" " + item); } } internal static void Split(string entry, out string key, out string? value) { int num = entry.IndexOf(' '); if (num < 0) { key = entry; value = null; } else { key = entry.Substring(0, num); value = entry.Substring(num + 1); } } } [HarmonyPatch(typeof(Achievements), "IsCheatedAtAll")] internal static class Achievements_IsCheatedAtAll_Patch { private static int _frame = -1; private static bool _cached; private static void Postfix(ref bool __result) { if (UnshamedPlugin.On(UnshamedPlugin.IgnoreModdedFlag) && __result) { if (Time.frameCount != _frame) { _frame = Time.frameCount; _cached = Eligibility.CheatedIgnoringMods(); } __result = _cached; Achievements.m_cheatCheckCache = _cached; } } } internal static class Eligibility { internal static bool CheatedIgnoringMods() { try { bool num = Object.op_Implicit((Object)(object)Game.instance) && Game.instance.GetPlayerProfile().m_usedCheats; bool flag = Object.op_Implicit((Object)(object)Player.m_localPlayer) && ((Humanoid)Player.m_localPlayer).GetInventory().AnyCheatedItem(); return num || flag || Achievements.IsWorldCheated(); } catch (Exception ex) { UnshamedPlugin.UnshamedLogger.LogWarning((object)("Couldn't work out cheat state, leaving vanilla's answer alone: " + ex.Message)); return true; } } internal static bool Cheated() { if (Object.op_Implicit((Object)(object)Game.instance)) { return Achievements.IsCheatedAtAll(); } return false; } private static bool WorldCheated() { try { return Achievements.IsWorldCheated(); } catch (Exception) { return false; } } internal static string Describe() { if (!Object.op_Implicit((Object)(object)Game.instance)) { return "no game instance yet"; } bool usedCheats = Game.instance.GetPlayerProfile().m_usedCheats; bool flag = Object.op_Implicit((Object)(object)Player.m_localPlayer) && ((Humanoid)Player.m_localPlayer).GetInventory().AnyCheatedItem(); return $"m_usedCheats {usedCheats}, cheated world {WorldCheated()}, cheated items {flag}, Game.isModded {Game.isModded}" + (UnshamedPlugin.On(UnshamedPlugin.IgnoreModdedFlag) ? " (isModded ignored by this mod)" : ""); } } internal static class StatSync { internal readonly struct Result { internal readonly int Stats; internal readonly int Entries; internal bool Any => Stats + Entries > 0; internal Result(int stats, int entries) { Stats = stats; Entries = entries; } public override string ToString() { return $"{Stats} counters and {Entries} named entries"; } } internal sealed class Wanted { internal readonly HashSet<PlayerStatType> Stats = new HashSet<PlayerStatType>(); internal readonly HashSet<string> Names = new HashSet<string>(StringComparer.Ordinal); internal Wanted(IEnumerable<Achievement> achievements) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) foreach (Achievement achievement in achievements) { foreach (PlayerStatRequirement item in achievement.m_statTrigger) { Stats.Add(item.m_stat); } foreach (EnemyStatRequirement enemyStatsTrigger in achievement.m_enemyStatsTriggers) { Names.Add(enemyStatsTrigger.m_stat); } Add(achievement.m_itemCraftTriggers); Add(achievement.m_itemPickupTriggers); Add(achievement.m_pickableTriggers); Add(achievement.m_foodEatenTriggers); Add(achievement.m_piecePlacedTriggers); Add(achievement.m_knownCommandsTriggers); Add(achievement.m_knownWorldTriggers); Add(achievement.m_knownWorldKeysTriggers); foreach (Achievement otherAchievementTrigger in achievement.m_otherAchievementTriggers) { if ((Object)(object)otherAchievementTrigger != (Object)null) { Merge(otherAchievementTrigger); } } } } private void Merge(Achievement a) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) foreach (PlayerStatRequirement item in a.m_statTrigger) { Stats.Add(item.m_stat); } foreach (EnemyStatRequirement enemyStatsTrigger in a.m_enemyStatsTriggers) { Names.Add(enemyStatsTrigger.m_stat); } Add(a.m_itemCraftTriggers); Add(a.m_itemPickupTriggers); Add(a.m_pickableTriggers); Add(a.m_foodEatenTriggers); Add(a.m_piecePlacedTriggers); Add(a.m_knownCommandsTriggers); Add(a.m_knownWorldTriggers); Add(a.m_knownWorldKeysTriggers); } private void Add(List<DictStatRequirement> triggers) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) foreach (DictStatRequirement trigger in triggers) { Names.Add(trigger.m_stat); } } } private sealed class Snapshot { private readonly Dictionary<PlayerStatType, float> _stats; private readonly Dictionary<string, float>[] _enemy; private readonly Dictionary<string, float>[] _named; internal Snapshot(PlayerStats from) { _stats = new Dictionary<PlayerStatType, float>(from.m_stats); _enemy = from.m_enemyStats.Select((Dictionary<string, float> d) => new Dictionary<string, float>(d)).ToArray(); _named = (from d in Named(@from) select new Dictionary<string, float>(d)).ToArray(); } internal void RestoreTo(PlayerStats to) { Replace(to.m_stats, _stats); for (int i = 0; i < _enemy.Length; i++) { Replace(to.m_enemyStats[i], _enemy[i]); } Dictionary<string, float>[] array = Named(to); for (int j = 0; j < array.Length; j++) { Replace(array[j], _named[j]); } } private static Dictionary<string, float>[] Named(PlayerStats s) { return new Dictionary<string, float>[8] { s.m_itemPickupStats, s.m_itemCraftStats, s.m_pickableStats, s.m_foodEatenStats, s.m_piecesPlacedStats, s.m_knownWorlds, s.m_knownWorldKeys, s.m_knownCommands }; } private static void Replace<TKey>(Dictionary<TKey, float> live, Dictionary<TKey, float> saved) { live.Clear(); foreach (KeyValuePair<TKey, float> item in saved) { live[item.Key] = item.Value; } } } private const int Raw = 0; private const int Any = 1; internal static Result Apply(PlayerProfile profile, bool preview, Wanted? only = null) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) int num = 0; int num2 = 0; PlayerStats val = profile.m_playerStats[0]; PlayerStats val2 = profile.m_playerStats[1]; foreach (KeyValuePair<PlayerStatType, float> stat in val.m_stats) { if ((only == null || only.Stats.Contains(stat.Key)) && (!val2.m_stats.TryGetValue(stat.Key, out var value) || !(value >= stat.Value))) { if (!preview) { val2.m_stats[stat.Key] = stat.Value; } num++; } } for (int i = 0; i < val.m_enemyStats.Length; i++) { num2 += Merge(val.m_enemyStats[i], val2.m_enemyStats[i], preview, only); } num2 += Merge(val.m_itemPickupStats, val2.m_itemPickupStats, preview, only); num2 += Merge(val.m_itemCraftStats, val2.m_itemCraftStats, preview, only); num2 += Merge(val.m_pickableStats, val2.m_pickableStats, preview, only); num2 += Merge(val.m_foodEatenStats, val2.m_foodEatenStats, preview, only); num2 += Merge(val.m_piecesPlacedStats, val2.m_piecesPlacedStats, preview, only); num2 += Merge(val.m_knownWorlds, val2.m_knownWorlds, preview, only); num2 += Merge(val.m_knownWorldKeys, val2.m_knownWorldKeys, preview, only); num2 += Merge(val.m_knownCommands, val2.m_knownCommands, preview, only); return new Result(num, num2); } private static int Merge(Dictionary<string, float> from, Dictionary<string, float> to, bool preview, Wanted? only) { int num = 0; foreach (KeyValuePair<string, float> item in from) { if ((only == null || only.Names.Contains(item.Key)) && (!to.TryGetValue(item.Key, out var value) || !(value >= item.Value))) { if (!preview) { to[item.Key] = item.Value; } num++; } } return num; } internal static List<Achievement> Preview(PlayerProfile profile) { List<Achievement> list = new List<Achievement>(); PlayerStats val = profile.m_playerStats[1]; Snapshot snapshot = new Snapshot(val); try { Achievements.MarkAchievementDifficultyDirty(); Apply(profile, preview: false); foreach (Achievement item in All()) { if (!item.m_unlocked && item.CheckUnlocked()) { item.m_unlocked = false; list.Add(item); } } } finally { snapshot.RestoreTo(val); foreach (Achievement item2 in list) { item2.m_unlocked = false; } Achievements.MarkAchievementDifficultyDirty(); } return list; } internal static List<Achievement> Sweep() { List<Achievement> list = new List<Achievement>(); if (!Object.op_Implicit((Object)(object)Achievements.m_instance)) { return list; } Achievements.MarkAchievementDifficultyDirty(); foreach (Achievement item in All()) { if (!item.m_unlocked && item.CheckUnlocked()) { Achievements.AchievementEvent(item, false, UnshamedPlugin.On(UnshamedPlugin.ShowPopups)); list.Add(item); } } return list; } internal static IEnumerable<Achievement> All() { if (!Object.op_Implicit((Object)(object)Achievements.m_instance)) { yield break; } foreach (AchievementList achievementList in Achievements.m_instance.m_achievementLists) { if ((Object)(object)achievementList == (Object)null) { continue; } foreach (Achievement achievement in achievementList.m_achievements) { if ((Object)(object)achievement != (Object)null) { yield return achievement; } } } } internal static string Name(Achievement a) { string text = Localization.instance.Localize(a.m_name); if (!string.IsNullOrEmpty(text)) { return text; } return a.m_id; } internal static Achievement? Find(string id) { return All().FirstOrDefault((Func<Achievement, bool>)((Achievement a) => string.Equals(a.m_id, id, StringComparison.OrdinalIgnoreCase))); } } internal static class AutoRetro { private static bool _done; private static bool _announced; private static float _next; internal static void Tick() { Announce(); if (_done || !UnshamedPlugin.On(UnshamedPlugin.Retroactive) || Time.realtimeSinceStartup < _next) { return; } _next = Time.realtimeSinceStartup + 3f; if (!Object.op_Implicit((Object)(object)Game.instance) || !Object.op_Implicit((Object)(object)Player.m_localPlayer) || !Object.op_Implicit((Object)(object)Achievements.m_instance)) { return; } PlayerProfile playerProfile = Game.instance.GetPlayerProfile(); if (playerProfile == null) { return; } if (Eligibility.Cheated()) { _done = true; UnshamedPlugin.UnshamedLogger.LogInfo((object)("Retroactive skipped, this character still counts as cheated: " + Eligibility.Describe())); return; } StatSync.Result result = StatSync.Apply(playerProfile, preview: true); _done = true; if (!result.Any) { return; } StatSync.Apply(playerProfile, preview: false); List<Achievement> list = StatSync.Sweep(); playerProfile.Save(); UnshamedPlugin.UnshamedLogger.LogInfo((object)$"Retroactive copied {result}, vanilla unlocked {list.Count}"); foreach (Achievement item in list) { UnshamedPlugin.UnshamedLogger.LogInfo((object)(" " + item.m_id + " - " + StatSync.Name(item))); } } private static void Announce() { if (_announced || !Object.op_Implicit((Object)(object)Game.instance) || !Object.op_Implicit((Object)(object)Player.m_localPlayer) || !Object.op_Implicit((Object)(object)Achievements.m_instance)) { return; } _announced = true; PlayerProfile playerProfile = Game.instance.GetPlayerProfile(); int num = StatSync.All().Count((Achievement a) => a.m_unlocked); int num2 = StatSync.All().Count(); UnshamedPlugin.UnshamedLogger.LogInfo((object)$"Eligible for achievements: {!Eligibility.Cheated()} ({Eligibility.Describe()})"); UnshamedPlugin.UnshamedLogger.LogInfo((object)$"Achievements {num}/{num2} unlocked."); if (Eligibility.Cheated()) { return; } List<Achievement> list = StatSync.Preview(playerProfile); if (list.Count == 0) { UnshamedPlugin.UnshamedLogger.LogInfo((object)"Nothing waiting, this character has already been credited with what it earned."); return; } UnshamedPlugin.UnshamedLogger.LogInfo((object)$"{list.Count} achievement(s) you already earned are waiting. Run 'unshamed retro' to see them, or 'unshamed retro apply' to take them."); foreach (Achievement item in list) { UnshamedPlugin.UnshamedLogger.LogInfo((object)(" " + item.m_id + " - " + StatSync.Name(item))); } } } internal static class CheatFlags { internal static string DescribeTargets() { return "PlayerProfile.m_usedCheats" + (UnshamedPlugin.On(UnshamedPlugin.ClearCheatStat) ? ", PlayerStatType.Cheats" : ""); } internal static List<string> Clear(PlayerProfile? profile, string reason) { List<string> list = new List<string>(); if (profile == null) { return list; } if (profile.m_usedCheats) { profile.m_usedCheats = false; list.Add("m_usedCheats"); } if (UnshamedPlugin.On(UnshamedPlugin.ClearCheatStat)) { PlayerStats[] playerStats = profile.m_playerStats; foreach (PlayerStats val in playerStats) { if (val != null && val.m_stats.TryGetValue((PlayerStatType)4, out var value) && !(value <= 0f)) { val.m_stats[(PlayerStatType)4] = 0f; list.Add($"Cheats stat ({value})"); } } } if (list.Count > 0) { UnshamedPlugin.UnshamedLogger.LogInfo((object)("Cleared on " + reason + ": " + string.Join(", ", list))); } return list; } internal static bool AnySet(PlayerProfile? profile) { return profile?.m_usedCheats ?? false; } internal static IEnumerable<string> CheatCommandsUsed(PlayerProfile profile) { ConsoleCommand value; return profile.m_playerStats[0].m_knownCommands.Keys.Where((string k) => Terminal.commands.TryGetValue(k.ToLower(), out value) && value.IsCheat); } } [HarmonyPatch(typeof(Terminal), "InitTerminal")] internal static class Commands { [CompilerGenerated] private static class <>O { public static ConsoleEvent <0>__Run; public static Func<PlayerProfile, List<string>> <1>__Run; } [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleOptionsFetcher <>9__1_0; public static Func<Achievement, bool> <>9__8_0; public static Func<Achievement, bool> <>9__10_0; public static Func<string, bool> <>9__12_0; internal List<string> <Postfix>b__1_0() { return new List<string> { "status", "audit", "clear", "list", "retro", "recon" }; } internal bool <Status>b__8_0(Achievement a) { return a.m_unlocked; } internal bool <List>b__10_0(Achievement a) { return a.m_unlocked; } internal bool <Pick>b__12_0(string t) { return t.Length > 0; } } private static bool _registered; private static PlayerProfile? Profile { get { if (!Object.op_Implicit((Object)(object)Game.instance)) { return null; } return Game.instance.GetPlayerProfile(); } } private static void Postfix() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_005b: 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_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown if (_registered) { return; } _registered = true; object obj = <>O.<0>__Run; if (obj == null) { ConsoleEvent val = Run; <>O.<0>__Run = val; obj = (object)val; } object obj2 = <>c.<>9__1_0; if (obj2 == null) { ConsoleOptionsFetcher val2 = () => new List<string> { "status", "audit", "clear", "list", "retro", "recon" }; <>c.<>9__1_0 = val2; obj2 = (object)val2; } new ConsoleCommand("unshamed", "status | audit | clear | list | retro [apply [n|id ...]] | recon", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)obj2, false, false, false); } private static void Out(ConsoleEventArgs args, string line) { args.Context.AddString(line); UnshamedPlugin.UnshamedLogger.LogInfo((object)Strip(line)); } private static string Strip(string line) { return Regex.Replace(line, "<[^>]+>", ""); } private static void Run(ConsoleEventArgs args) { string text = ((args.Length > 1) ? args[1].ToLower() : "status"); switch (text) { case "status": Status(args); break; case "audit": Report(args, Audit.Run); break; case "clear": Clear(args); break; case "list": List(args); break; case "retro": Retroactive(args); break; case "recon": Out(args, "Wrote " + Recon.Run()); break; default: Out(args, "Don't know '" + text + "'. Try status, audit, clear, list, retro or recon."); break; } } private static void Report(ConsoleEventArgs args, Func<PlayerProfile, List<string>> build) { PlayerProfile profile = Profile; if (profile == null) { Out(args, "No profile loaded yet."); return; } foreach (string item in build(profile)) { Out(args, item); } } private static void Status(ConsoleEventArgs args) { PlayerProfile profile = Profile; if (profile == null) { Out(args, "No profile loaded yet."); return; } Out(args, "Eligible for achievements: " + !Eligibility.Cheated()); Out(args, " " + Eligibility.Describe()); Out(args, "Watching: " + CheatFlags.DescribeTargets()); List<Achievement> list = StatSync.All().ToList(); Out(args, (list.Count == 0) ? "Achievements: none loaded yet, get into a world first." : ("Achievements: " + list.Count((Achievement a) => a.m_unlocked) + " / " + list.Count + " unlocked")); StatSync.Result result = StatSync.Apply(profile, preview: true); Out(args, result.Any ? ("Retroactive: " + result.ToString() + " waiting, run 'unshamed retro'") : "Retroactive: nothing to copy forward"); } private static void Clear(ConsoleEventArgs args) { PlayerProfile profile = Profile; if (profile == null) { Out(args, "No profile loaded yet."); return; } List<string> list = CheatFlags.Clear(profile, "console"); Out(args, (list.Count == 0) ? "Nothing was set." : ("Cleared " + string.Join(", ", list))); Out(args, profile.Save() ? "Saved the character." : "Character save failed, check the log."); } private static void List(ConsoleEventArgs args) { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) List<Achievement> list = StatSync.All().ToList(); if (list.Count == 0) { Out(args, "No achievements loaded. Get into a world first."); return; } string filter = ((args.Length > 2) ? args[2].ToLower() : ""); foreach (Achievement item in list.Where((Achievement a) => filter.Length == 0 || a.m_id.ToLower().Contains(filter))) { Out(args, string.Format("{0} {1} ({2}) - {3}", item.m_unlocked ? "<color=green>[x]</color>" : "[ ]", item.m_id, item.m_difficultyRequirement, StatSync.Name(item))); } Out(args, list.Count((Achievement a) => a.m_unlocked) + " / " + list.Count + " unlocked"); } private static void Retroactive(ConsoleEventArgs args) { PlayerProfile profile = Profile; if (profile == null) { Out(args, "No profile loaded yet."); return; } if (!Object.op_Implicit((Object)(object)Achievements.m_instance)) { Out(args, "Achievements aren't loaded yet. Get into a world first."); return; } if (Eligibility.Cheated()) { Out(args, "<color=orange>This character still counts as cheated, so vanilla would refuse the unlocks.</color>"); Out(args, " " + Eligibility.Describe()); return; } List<Achievement> list = StatSync.Preview(profile); if (list.Count == 0) { StatSync.Result result = StatSync.Apply(profile, preview: true); Out(args, result.Any ? ("Nothing new would unlock, though there is still " + result.ToString() + " to copy forward.") : "Nothing to copy forward and nothing new to unlock."); return; } if (args.Length <= 2 || !(args[2].ToLower() == "apply")) { Out(args, list.Count + " achievement(s) your character already earned:"); for (int i = 0; i < list.Count; i++) { Out(args, $" <color=yellow>{i + 1,2}</color> {list[i].m_id,-28} {StatSync.Name(list[i])}"); } Out(args, "'unshamed retro apply' takes all of them."); Out(args, "'unshamed retro apply 1 3 4' or 'unshamed retro apply <id>' takes only those."); return; } List<string> unknown; List<Achievement> list2 = Pick(args, list, out unknown); foreach (string item in unknown) { Out(args, "<color=orange>Don't know '" + item + "', skipping it.</color>"); } if (list2.Count == 0) { Out(args, "Nothing selected, nothing done."); return; } StatSync.Wanted only = new StatSync.Wanted(list2); StatSync.Result result2 = StatSync.Apply(profile, preview: false, only); List<Achievement> list3 = StatSync.Sweep(); profile.Save(); Out(args, "Copied " + result2.ToString() + "."); Out(args, "Vanilla unlocked " + list3.Count + ":"); foreach (Achievement item2 in list3) { Out(args, " " + item2.m_id + " - " + StatSync.Name(item2)); } int num = list.Count - list3.Count; if (num > 0) { Out(args, num + " left on the table, run 'unshamed retro' again to see them."); } } private static List<Achievement> Pick(ConsoleEventArgs args, List<Achievement> would, out List<string> unknown) { unknown = new List<string>(); List<string> list = (from t in args.Args.Skip(3) where t.Length > 0 select t).ToList(); if (list.Count == 0) { return would; } List<Achievement> list2 = new List<Achievement>(); foreach (string token in list) { if (int.TryParse(token, out var result)) { if (result >= 1 && result <= would.Count) { list2.Add(would[result - 1]); } else { unknown.Add(token); } continue; } Achievement val = ((IEnumerable<Achievement>)would).FirstOrDefault((Func<Achievement, bool>)((Achievement a) => string.Equals(a.m_id, token, StringComparison.OrdinalIgnoreCase))); if ((Object)(object)val != (Object)null) { list2.Add(val); } else { unknown.Add(token); } } return list2.Distinct().ToList(); } } [HarmonyPatch(typeof(PlayerProfile), "Save")] internal static class PlayerProfile_Save_Patch { private static void Prefix(PlayerProfile __instance) { if (UnshamedPlugin.On(UnshamedPlugin.ClearOnSave)) { CheatFlags.Clear(__instance, "save"); } } } [HarmonyPatch(typeof(PlayerProfile), "Load")] internal static class PlayerProfile_Load_Patch { private static void Postfix(PlayerProfile __instance, bool __result) { if (__result && UnshamedPlugin.On(UnshamedPlugin.ClearOnLoad) && Object.op_Implicit((Object)(object)Game.instance)) { CheatFlags.Clear(__instance, "load"); } } } [HarmonyPatch(typeof(ConsoleCommand), "RunAction")] internal static class ConsoleCommand_RunAction_Patch { private static void Postfix(ConsoleCommand __instance) { if (__instance.IsCheat && UnshamedPlugin.On(UnshamedPlugin.ClearAfterCommand) && Object.op_Implicit((Object)(object)Game.instance)) { CheatFlags.Clear(Game.instance.GetPlayerProfile(), "command " + __instance.Command); } } } [HarmonyPatch(typeof(Game), "Awake")] internal static class Game_Awake_Patch { private static void Prefix() { Game.isModded = true; } } [BepInPlugin("Azumatt.Unshamed", "Unshamed", "1.0.0")] public class UnshamedPlugin : BaseUnityPlugin { public enum Toggle { On = 1, Off = 0 } private class ConfigurationManagerAttributes { [UsedImplicitly] public int? Order; [UsedImplicitly] public bool? Browsable; [UsedImplicitly] public string? Category; [UsedImplicitly] public Action<ConfigEntryBase>? CustomDrawer; } internal const string ModName = "Unshamed"; internal const string ModVersion = "1.0.0"; internal const string Author = "Azumatt"; private const string ModGUID = "Azumatt.Unshamed"; private static string ConfigFileName = "Azumatt.Unshamed.cfg"; private static string ConfigFileFullPath; private readonly Harmony _harmony = new Harmony("Azumatt.Unshamed"); public static readonly ManualLogSource UnshamedLogger; internal static UnshamedPlugin Instance; internal static ConfigEntry<Toggle> Enabled; internal static ConfigEntry<Toggle> ClearOnLoad; internal static ConfigEntry<Toggle> ClearOnSave; internal static ConfigEntry<Toggle> ClearAfterCommand; internal static ConfigEntry<Toggle> ClearCheatStat; internal static ConfigEntry<Toggle> IgnoreModdedFlag; internal static ConfigEntry<Toggle> ShowPopups; internal static ConfigEntry<string> ForceTrue; internal static ConfigEntry<string> ForceFalse; internal static ConfigEntry<Toggle> Retroactive; public void Awake() { Instance = this; bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet; ((BaseUnityPlugin)this).Config.SaveOnConfigSet = false; Enabled = config("1 - General", "Enabled", Toggle.On, "Master switch. Off means the mod touches nothing."); ClearOnLoad = config("1 - General", "Clear On Load", Toggle.On, "Wipe the cheat flag every time a character is loaded from disk."); ClearOnSave = config("1 - General", "Clear On Save", Toggle.On, "Wipe the cheat flag before the character gets written to disk. This is the one that makes it stick."); ClearAfterCommand = config("1 - General", "Clear After Command", Toggle.On, "Wipe the flag right after a dev command sets it, so nothing ever reads it true."); ClearCheatStat = config("1 - General", "Clear Cheat Stat", Toggle.On, "Also zero the PlayerStatType.Cheats counter. Vanilla bumps it alongside the flag."); IgnoreModdedFlag = config("1 - General", "Ignore Modded Flag", Toggle.On, "Stop Game.isModded on its own from disqualifying you. BepInEx sets that flag for every modded install, so without this nobody running mods can earn an achievement. Cheat commands, cheated items and cheated worlds still disqualify you exactly like vanilla."); ShowPopups = config("1 - General", "Show Popups", Toggle.Off, "Show the vanilla unlock popup for each achievement the retroactive pass grants. Off because a big backlog means a lot of popups at once."); ForceTrue = config("2 - Overrides", "Force True Methods", "", "Comma separated Type::Method whose bool return gets forced true. A compatibility hatch for when a game patch moves something and the mod stops working."); ForceFalse = config("2 - Overrides", "Force False Methods", "", "Same thing the other way, bool return forced false."); Retroactive = config("3 - Retroactive", "Enable Retroactive", Toggle.Off, "Automatically grant EVERY achievement your character already earned, once, next time you load into a world. Off by default, and you do not need it: 'unshamed retro' in the console previews the list and lets you pick individual ones whether this is on or off. Turn this on only if you want the lot without looking."); Assembly executingAssembly = Assembly.GetExecutingAssembly(); _harmony.PatchAll(executingAssembly); ReturnOverrides.Apply(_harmony, ForceTrue.Value, ForceFalse.Value); SetupWatcher(); if (saveOnConfigSet) { ((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet; ((BaseUnityPlugin)this).Config.Save(); } UnshamedLogger.LogInfo((object)("Unshamed 1.0.0 loaded, watching " + CheatFlags.DescribeTargets())); } private void Update() { AutoRetro.Tick(); } private void OnDestroy() { ((BaseUnityPlugin)this).Config.Save(); } private void SetupWatcher() { FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(Paths.ConfigPath, ConfigFileName); fileSystemWatcher.Changed += ReadConfigValues; fileSystemWatcher.Created += ReadConfigValues; fileSystemWatcher.Renamed += ReadConfigValues; fileSystemWatcher.IncludeSubdirectories = true; fileSystemWatcher.SynchronizingObject = ThreadingHelper.SynchronizingObject; fileSystemWatcher.EnableRaisingEvents = true; } private void ReadConfigValues(object sender, FileSystemEventArgs e) { if (!File.Exists(ConfigFileFullPath)) { return; } try { UnshamedLogger.LogDebug((object)"ReadConfigValues called"); ((BaseUnityPlugin)this).Config.Reload(); } catch { UnshamedLogger.LogError((object)("There was an issue loading your " + ConfigFileName)); UnshamedLogger.LogError((object)"Please check your config entries for spelling and format!"); } } internal static bool On(ConfigEntry<Toggle> entry) { if (Enabled.Value == Toggle.On) { return entry.Value == Toggle.On; } return false; } private ConfigEntry<T> config<T>(string group, string name, T value, ConfigDescription description) { return ((BaseUnityPlugin)this).Config.Bind<T>(group, name, value, description); } private ConfigEntry<T> config<T>(string group, string name, T value, string description) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown return config(group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>())); } static UnshamedPlugin() { string configPath = Paths.ConfigPath; char directorySeparatorChar = Path.DirectorySeparatorChar; ConfigFileFullPath = configPath + directorySeparatorChar + ConfigFileName; UnshamedLogger = Logger.CreateLogSource("Unshamed"); Instance = null; Enabled = null; ClearOnLoad = null; ClearOnSave = null; ClearAfterCommand = null; ClearCheatStat = null; IgnoreModdedFlag = null; ShowPopups = null; ForceTrue = null; ForceFalse = null; Retroactive = null; } } public static class KeyboardExtensions { public static bool IsKeyDown(this KeyboardShortcut shortcut) { //IL_0002: 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) if ((int)((KeyboardShortcut)(ref shortcut)).MainKey != 0 && Input.GetKeyDown(((KeyboardShortcut)(ref shortcut)).MainKey)) { return ((KeyboardShortcut)(ref shortcut)).Modifiers.All((Func<KeyCode, bool>)Input.GetKey); } return false; } public static bool IsKeyHeld(this KeyboardShortcut shortcut) { //IL_0002: 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) if ((int)((KeyboardShortcut)(ref shortcut)).MainKey != 0 && Input.GetKey(((KeyboardShortcut)(ref shortcut)).MainKey)) { return ((KeyboardShortcut)(ref shortcut)).Modifiers.All((Func<KeyCode, bool>)Input.GetKey); } return false; } } internal static class Recon { private static readonly string[] Interesting = new string[5] { "achiev", "trophy", "unlock", "cheat", "milestone" }; private static readonly string[] GameAssemblies = new string[6] { "assembly_valheim", "assembly_utils", "assembly_guiutils", "Splatform", "Splatform.Steam", "SoftReferenceableAssets" }; internal static string Run() { StringBuilder stringBuilder = new StringBuilder(); Versions(stringBuilder); Flags(stringBuilder); Symbols(stringBuilder); Enums(stringBuilder); CheatCommands(stringBuilder); Achievements(stringBuilder); string text = Path.Combine(Paths.ConfigPath, "Unshamed_recon.txt"); File.WriteAllText(text, stringBuilder.ToString()); return text; } private static void Versions(StringBuilder sb) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) sb.AppendLine("=== versions ==="); sb.AppendLine("game " + Version.GetVersionString(false)); sb.AppendLine("mod 1.0.0"); sb.AppendLine("network " + 39u); sb.AppendLine("player " + ((object)(Player)46/*cast due to .constrained prefix*/).ToString()); sb.AppendLine("world " + ((object)(World)41/*cast due to .constrained prefix*/).ToString()); sb.AppendLine("item data " + ((object)(Item)109/*cast due to .constrained prefix*/).ToString()); sb.AppendLine("player data " + ((object)(PlayerData)33/*cast due to .constrained prefix*/).ToString()); sb.AppendLine("isModded " + Game.isModded); sb.AppendLine(); } private static void Flags(StringBuilder sb) { sb.AppendLine("=== bool fields worth a look ==="); Type[] array = new Type[4] { typeof(PlayerProfile), typeof(World), typeof(ItemData), typeof(ConsoleCommand) }; foreach (Type type in array) { foreach (FieldInfo item in from f in type.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where f.FieldType == typeof(bool) && Matches(f.Name) select f) { sb.AppendLine(type.Name + "." + item.Name); } } sb.AppendLine(); } private static void Symbols(StringBuilder sb) { sb.AppendLine("=== types and members matching " + string.Join("/", Interesting) + " ==="); foreach (Assembly item in from a in AppDomain.CurrentDomain.GetAssemblies() where GameAssemblies.Contains<string>(a.GetName().Name) select a) { foreach (Type item2 in Types(item)) { bool flag = Matches(item2.Name); if (flag) { sb.AppendLine("type " + item2.FullName); } foreach (MemberInfo item3 in from m in item2.GetMembers(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where Matches(m.Name) select m) { if (!flag || item3.MemberType != MemberTypes.Method || !item3.Name.StartsWith("get_")) { sb.AppendLine("member " + item2.FullName + "." + Signature(item3)); } } } } sb.AppendLine(); } private static string Signature(MemberInfo member) { if (!(member is MethodInfo methodInfo)) { return member.Name; } return methodInfo.Name + "(" + string.Join(", ", from p in methodInfo.GetParameters() select p.ParameterType.Name) + ") -> " + methodInfo.ReturnType.Name; } private static void Enums(StringBuilder sb) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected I4, but got Unknown //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_011d: 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_014a: Expected I4, but got Unknown //IL_014a: Unknown result type (might be due to invalid IL or missing references) PlayerProfile obj = (Object.op_Implicit((Object)(object)Game.instance) ? Game.instance.GetPlayerProfile() : null); Dictionary<PlayerStatType, float> dictionary = ((obj != null) ? obj.m_playerStats[0].m_stats : null); sb.AppendLine("=== PlayerStatType (" + Enum.GetValues(typeof(PlayerStatType)).Length + " members) ==="); foreach (PlayerStatType item in Enum.GetValues(typeof(PlayerStatType)).Cast<PlayerStatType>()) { float value; float num = ((dictionary != null && dictionary.TryGetValue(item, out value)) ? value : 0f); sb.AppendLine($"{(int)item,4} {item,-28} {num}"); } sb.AppendLine(); sb.AppendLine("=== GlobalKeys ==="); string text = default(string); foreach (GlobalKeys item2 in Enum.GetValues(typeof(GlobalKeys)).Cast<GlobalKeys>()) { string arg = ((Object.op_Implicit((Object)(object)ZoneSystem.instance) && ZoneSystem.instance.GetGlobalKey(item2, ref text)) ? (" = " + text) : ""); sb.AppendLine($"{(int)item2,4} {item2}{arg}"); } sb.AppendLine(); } private static void CheatCommands(StringBuilder sb) { sb.AppendLine("=== commands that set the flag ==="); foreach (KeyValuePair<string, ConsoleCommand> item in from c in Terminal.commands where c.Value.IsCheat orderby c.Key select c) { sb.AppendLine(item.Key); } sb.AppendLine(); } private static void Achievements(StringBuilder sb) { //IL_00e0: Unknown result type (might be due to invalid IL or missing references) sb.AppendLine("=== achievements ==="); List<Achievement> list = StatSync.All().ToList(); if (list.Count == 0) { sb.AppendLine("none loaded, get into a world first"); return; } sb.AppendLine(list.Count + " defined, " + list.Count((Achievement a) => a.m_unlocked) + " unlocked"); foreach (Achievement item in list.OrderBy((Achievement a) => a.m_id)) { sb.AppendLine(string.Format("{0} {1,-32} {2,-10} {3,-28} {4}", item.m_unlocked ? "[x]" : "[ ]", item.m_id, item.m_difficultyRequirement, item.m_name, StatSync.Name(item))); } } private static IEnumerable<Type> Types(Assembly assembly) { try { return assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { return ex.Types.Where((Type t) => t != null); } } private static bool Matches(string name) { return Interesting.Any((string i) => name.IndexOf(i, StringComparison.OrdinalIgnoreCase) >= 0); } } internal static class ReturnOverrides { private static readonly HarmonyMethod TruePostfix = new HarmonyMethod(AccessTools.Method(typeof(ReturnOverrides), "ReturnTrue", (Type[])null, (Type[])null)); private static readonly HarmonyMethod FalsePostfix = new HarmonyMethod(AccessTools.Method(typeof(ReturnOverrides), "ReturnFalse", (Type[])null, (Type[])null)); internal static void Apply(Harmony harmony, string forceTrue, string forceFalse) { foreach (MethodInfo item in Resolve(forceTrue)) { Patch(harmony, item, TruePostfix, "true"); } foreach (MethodInfo item2 in Resolve(forceFalse)) { Patch(harmony, item2, FalsePostfix, "false"); } } private static void Patch(Harmony harmony, MethodInfo method, HarmonyMethod postfix, string value) { try { harmony.Patch((MethodBase)method, (HarmonyMethod)null, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); UnshamedPlugin.UnshamedLogger.LogInfo((object)("Forcing " + method.DeclaringType?.Name + "." + method.Name + " to return " + value)); } catch (Exception ex) { UnshamedPlugin.UnshamedLogger.LogError((object)("Couldn't patch " + method.DeclaringType?.Name + "." + method.Name + ": " + ex.Message)); } } private static IEnumerable<MethodInfo> Resolve(string entries) { if (string.IsNullOrWhiteSpace(entries)) { yield break; } foreach (string item in from e in entries.Split(new char[1] { ',' }) select e.Trim() into e where e.Length > 0 select e) { string[] array = item.Split(new string[1] { "::" }, StringSplitOptions.RemoveEmptyEntries); if (array.Length != 2) { UnshamedPlugin.UnshamedLogger.LogError((object)("'" + item + "' isn't a Type::Method entry, skipping it")); continue; } Type type = AccessTools.TypeByName(array[0]); if (type == null) { UnshamedPlugin.UnshamedLogger.LogError((object)("No type named '" + array[0] + "', skipping '" + item + "'")); continue; } MethodInfo[] array2 = Candidates(type, array[1]).ToArray(); if (array2.Length == 0) { UnshamedPlugin.UnshamedLogger.LogError((object)(type.Name + " has no bool-returning '" + array[1] + "', skipping it")); continue; } MethodInfo[] array3 = array2; for (int num = 0; num < array3.Length; num++) { yield return array3[num]; } } } private static IEnumerable<MethodInfo> Candidates(Type type, string name) { foreach (MethodInfo item in from m in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where m.Name == name && m.ReturnType == typeof(bool) && !m.IsAbstract select m) { yield return item; } PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property?.PropertyType == typeof(bool) && property.GetMethod != null) { yield return property.GetMethod; } } private static void ReturnTrue(ref bool __result) { __result = true; } private static void ReturnFalse(ref bool __result) { __result = false; } } }