Decompiled source of NoCheatText v1.4.0

NoCheatText.dll

Decompiled a month ago
using System;
using System.Collections;
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.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("NoCheatText")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.4.0.0")]
[assembly: AssemblyInformationalVersion("1.4.0")]
[assembly: AssemblyProduct("NoCheatText")]
[assembly: AssemblyTitle("NoCheatText")]
[assembly: AssemblyVersion("1.4.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace NoCheatText
{
	[BepInPlugin("codex.valheim.nocheattext", "No Cheat Text", "1.4.0")]
	[BepInProcess("valheim.exe")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "codex.valheim.nocheattext";

		public const string PluginName = "No Cheat Text";

		public const string PluginVersion = "1.4.0";

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<bool> EnableAchievements;

		internal static ConfigEntry<bool> HideCommandConfirmation;

		internal static ConfigEntry<bool> RepairMissingRecipes;

		private static Plugin _instance;

		private static FieldRef<Player, HashSet<string>> _knownMaterials;

		private Action<Player> _updateKnownRecipes;

		private Harmony _harmony;

		private Coroutine _recovery;

		private Player _pendingPlayer;

		private Player _reconciledPlayer;

		private FieldInfo _localizationCache;

		private MethodInfo _evictLocalizationCache;

		private readonly List<MethodInfo> _installingTargets = new List<MethodInfo>();

		private void Awake()
		{
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Expected O, but got Unknown
			_instance = this;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Hide cheat-related UI without changing saved cheat flags.");
			EnableAchievements = ((BaseUnityPlugin)this).Config.Bind<bool>("Achievements", "Enabled", true, "Allow achievements despite modded or cheated state. Does not grant achievements automatically.");
			HideCommandConfirmation = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "HideCommandConfirmation", true, "Hide the cheat-command confirmation text, not the confirmation requirement.");
			RepairMissingRecipes = ((BaseUnityPlugin)this).Config.Bind<bool>("Recipes", "RepairMissingRecipesOnLogin", true, "Fix upgrader-only resource discovery gates and reconcile missing unlocks once per local player login. Crafting costs remain unchanged.");
			_harmony = new Harmony("codex.valheim.nocheattext");
			InstallFeature("cheat text", delegate
			{
				_localizationCache = AccessTools.Field(typeof(Localization), "m_cache");
				if (_localizationCache == null)
				{
					throw new MissingFieldException("Localization.m_cache");
				}
				_evictLocalizationCache = AccessTools.Method(_localizationCache.FieldType, "EvictAll", Type.EmptyTypes, (Type[])null);
				if (_evictLocalizationCache == null)
				{
					throw new MissingMethodException("Localization cache EvictAll");
				}
				Patch(AccessTools.Method(typeof(Localization), "Translate", new Type[1] { typeof(string) }, (Type[])null), "HideTranslation", null, null, typeof(TextPatches));
				Patch(AccessTools.Method(typeof(TextsDialog), "AddStats", Type.EmptyTypes, (Type[])null), null, null, "FilterStatsLiteral", typeof(TextPatches));
				Enabled.SettingChanged += OnTextSettingChanged;
				HideCommandConfirmation.SettingChanged += OnTextSettingChanged;
				EvictTextCache();
			});
			InstallFeature("achievements", delegate
			{
				Patch(AccessTools.Method(typeof(Achievements), "CanGetAchievements", new Type[1] { typeof(bool) }, (Type[])null), "AllowAchievements", null, null, typeof(TextPatches));
				Patch(AccessTools.Method(typeof(Achievements), "IsCheatedAtAll", Type.EmptyTypes, (Type[])null), "ReportNotCheated", null, null, typeof(TextPatches));
				Patch(AccessTools.Method(typeof(Achievements), "IsWorldCheated", Type.EmptyTypes, (Type[])null), null, "OverrideWorldStatus", null, typeof(TextPatches));
			});
			InstallFeature("recipe discovery", delegate
			{
				_knownMaterials = AccessTools.FieldRefAccess<Player, HashSet<string>>("m_knownMaterial");
				MethodInfo methodInfo = AccessTools.Method(typeof(Player), "UpdateKnownRecipesList", Type.EmptyTypes, (Type[])null);
				if (methodInfo == null)
				{
					throw new MissingMethodException("Player.UpdateKnownRecipesList");
				}
				_updateKnownRecipes = AccessTools.MethodDelegate<Action<Player>>(methodInfo, (object)null, true);
				Patch(AccessTools.Method(typeof(Player), "HaveRequirementItems", new Type[4]
				{
					typeof(Recipe),
					typeof(bool),
					typeof(int),
					typeof(int)
				}, (Type[])null), null, "RecoverDiscovery");
				Patch(AccessTools.Method(typeof(Player), "OnSpawned", new Type[1] { typeof(bool) }, (Type[])null), null, "OnPlayerSpawned");
			});
			((BaseUnityPlugin)this).Logger.LogInfo((object)"No Cheat Text 1.4.0 loaded. Client-only; saved cheat markers and recipe costs are unchanged.");
		}

		private void InstallFeature(string name, Action install)
		{
			_installingTargets.Clear();
			try
			{
				install();
			}
			catch (Exception ex)
			{
				for (int i = 0; i < _installingTargets.Count; i++)
				{
					_harmony.Unpatch((MethodBase)_installingTargets[i], (HarmonyPatchType)0, "codex.valheim.nocheattext");
				}
				((BaseUnityPlugin)this).Logger.LogError((object)("Disabled incompatible feature '" + name + "': " + ex.GetBaseException().Message + ". Other features are independent."));
			}
			finally
			{
				_installingTargets.Clear();
			}
		}

		private void Patch(MethodInfo target, string prefix = null, string postfix = null, string transpiler = null, Type patchType = null)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			if (target == null)
			{
				throw new MissingMethodException("Required Valheim patch target is absent");
			}
			Type type = patchType ?? typeof(Plugin);
			_installingTargets.Add(target);
			_harmony.Patch((MethodBase)target, (prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(type, prefix, (Type[])null), (postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(type, postfix, (Type[])null), (transpiler == null) ? ((HarmonyMethod)null) : new HarmonyMethod(type, transpiler, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private static void RecoverDiscovery(Player __instance, Recipe __0, bool __1, ref bool __result)
		{
			if (!__result && __1 && RepairMissingRecipes.Value && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !((Object)(object)__0 == (Object)null) && RecipeRequirements.TryRecover(__0, _knownMaterials.Invoke(__instance), out var result))
			{
				__result = result;
			}
		}

		private static void OnPlayerSpawned(Player __instance)
		{
			if (!((Object)(object)_instance == (Object)null) && RepairMissingRecipes.Value && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer))
			{
				_instance.QueueRecovery(__instance);
			}
		}

		private void QueueRecovery(Player player)
		{
			if (!((Object)(object)player == (Object)(object)_pendingPlayer) && !((Object)(object)player == (Object)(object)_reconciledPlayer))
			{
				if (_recovery != null)
				{
					((MonoBehaviour)this).StopCoroutine(_recovery);
				}
				_pendingPlayer = player;
				_recovery = ((MonoBehaviour)this).StartCoroutine(ReconcileAfterLogin(player));
			}
		}

		private IEnumerator ReconcileAfterLogin(Player player)
		{
			yield return (object)new WaitForSecondsRealtime(5f);
			float deadline = Time.realtimeSinceStartup + 25f;
			WaitForSecondsRealtime poll = new WaitForSecondsRealtime(0.5f);
			while ((Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer && RepairMissingRecipes.Value && ((Object)(object)Game.instance == (Object)null || (Object)(object)ObjectDB.instance == (Object)null || (Object)(object)MessageHud.instance == (Object)null) && Time.realtimeSinceStartup < deadline)
			{
				yield return poll;
			}
			if ((Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer && RepairMissingRecipes.Value && (Object)(object)Game.instance != (Object)null && (Object)(object)ObjectDB.instance != (Object)null && (Object)(object)MessageHud.instance != (Object)null)
			{
				try
				{
					_updateKnownRecipes(player);
					_reconciledPlayer = player;
					((BaseUnityPlugin)this).Logger.LogInfo((object)"Reconciled missing recipe/build-piece unlocks from this character's discovery history.");
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Login recipe reconciliation failed: " + ex.GetBaseException().Message));
				}
			}
			else if ((Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer && RepairMissingRecipes.Value)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Login recipe reconciliation skipped: game databases/HUD were not ready within 30 seconds.");
			}
			_pendingPlayer = null;
			_recovery = null;
		}

		private void OnTextSettingChanged(object sender, EventArgs args)
		{
			EvictTextCache();
		}

		private void EvictTextCache()
		{
			if (Localization.instance == null || _localizationCache == null || _evictLocalizationCache == null)
			{
				return;
			}
			try
			{
				_evictLocalizationCache.Invoke(_localizationCache.GetValue(Localization.instance), null);
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not refresh localization cache: " + ex.GetBaseException().Message));
			}
		}

		private void OnDestroy()
		{
			if (_recovery != null)
			{
				((MonoBehaviour)this).StopCoroutine(_recovery);
			}
			if (Enabled != null)
			{
				Enabled.SettingChanged -= OnTextSettingChanged;
			}
			if (HideCommandConfirmation != null)
			{
				HideCommandConfirmation.SettingChanged -= OnTextSettingChanged;
			}
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			EvictTextCache();
			_pendingPlayer = null;
			_reconciledPlayer = null;
			if ((Object)(object)_instance == (Object)(object)this)
			{
				_instance = null;
			}
		}
	}
	internal static class RecipeRequirements
	{
		internal static bool TryRecover(Recipe recipe, HashSet<string> knownMaterials, out bool result)
		{
			bool result2 = false;
			bool flag = true;
			bool flag2 = false;
			Requirement[] resources = recipe.m_resources;
			if (resources != null)
			{
				foreach (Requirement val in resources)
				{
					if (val != null && !((Object)(object)val.m_resItem == (Object)null))
					{
						if (val.m_upgraderResource)
						{
							result2 = true;
						}
						else if (val.m_amount > 0)
						{
							bool flag3 = knownMaterials.Contains(val.m_resItem.m_itemData.m_shared.m_name);
							flag2 = flag2 || flag3;
							flag = flag && flag3;
						}
					}
				}
			}
			result = (recipe.m_requireOnlyOneIngredient ? flag2 : flag);
			return result2;
		}
	}
	internal static class TextPatches
	{
		private static readonly MethodInfo FilterLiteralMethod = typeof(TextPatches).GetMethod("FilterLiteral", BindingFlags.Static | BindingFlags.NonPublic);

		internal static bool HideTranslation(string __0, ref string __result)
		{
			if (!Plugin.Enabled.Value)
			{
				return true;
			}
			switch (__0)
			{
			case "achievements_cheated_item_inventory":
			case "achievements_picked_up_cheated_item":
			case "achievements_dropped_cheated_item":
			case "achievements_temporarily_cheated":
			case "achievements_permanently_cheated_character":
			case "achievements_permanently_cheated_world":
			case "achievements_permanently_cheated_bypass":
				__result = string.Empty;
				return false;
			case "achievements_confirm_cheat":
				if (!Plugin.HideCommandConfirmation.Value)
				{
					return true;
				}
				__result = string.Empty;
				return false;
			default:
				return true;
			}
		}

		internal static bool AllowAchievements(ref bool __result)
		{
			if (!Plugin.EnableAchievements.Value)
			{
				return true;
			}
			__result = true;
			return false;
		}

		internal static bool ReportNotCheated(ref bool __result)
		{
			if (!Plugin.EnableAchievements.Value)
			{
				return true;
			}
			__result = false;
			return false;
		}

		internal static void OverrideWorldStatus(ref bool __result)
		{
			if (Plugin.EnableAchievements.Value)
			{
				__result = false;
			}
		}

		internal static IEnumerable<CodeInstruction> FilterStatsLiteral(IEnumerable<CodeInstruction> instructions)
		{
			bool found = false;
			foreach (CodeInstruction instruction in instructions)
			{
				yield return instruction;
				if (instruction.opcode == OpCodes.Ldstr && instruction.operand is string text && text.IndexOf("Cheater!", StringComparison.Ordinal) >= 0)
				{
					yield return new CodeInstruction(OpCodes.Call, (object)FilterLiteralMethod);
					found = true;
				}
			}
			if (!found)
			{
				throw new InvalidOperationException("TextsDialog.AddStats cheat literal changed; refusing a silent no-op patch.");
			}
		}

		internal static string FilterLiteral(string original)
		{
			if (!Plugin.Enabled.Value)
			{
				return original;
			}
			return string.Empty;
		}
	}
}