Decompiled source of IDGAFMan v1.0.0

BepInEx/plugins/IDGAFMan/IDGAFMan.dll

Decompiled 14 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using BepInEx;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using CI.QuickSave;
using CI.QuickSave.Core.Storage;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("IDGAFMan")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("IDGAFMan")]
[assembly: AssemblyTitle("IDGAFMan")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace IDGAFMan;

internal static class PatchEngine
{
	internal sealed class Manifest
	{
		public string OriginalSha256 { get; set; } = "";

		public string PatchedSha256 { get; set; } = "";

		public Patch[] Patches { get; set; } = Array.Empty<Patch>();
	}

	internal sealed class Patch
	{
		public string Name { get; set; } = "";

		public string Rva { get; set; } = "";

		public string Before { get; set; } = "";

		public string After { get; set; } = "";
	}

	internal static bool Apply(IntPtr module, int moduleSize, string dllPath)
	{
		if (!OperatingSystem.IsWindows() || IntPtr.Size != 8)
		{
			throw new PlatformNotSupportedException("IDGAFMan requires Windows x64.");
		}
		using Stream utf8Json = Assembly.GetExecutingAssembly().GetManifestResourceStream("IDGAFMan.patches.json");
		Manifest manifest = JsonSerializer.Deserialize<Manifest>(utf8Json, new JsonSerializerOptions
		{
			PropertyNameCaseInsensitive = true
		});
		using FileStream inputStream = File.OpenRead(dllPath);
		using SHA256 sHA = SHA256.Create();
		string text = Convert.ToHexString(sHA.ComputeHash(inputStream)).ToLowerInvariant();
		if (text != manifest.OriginalSha256 && text != manifest.PatchedSha256)
		{
			throw new InvalidOperationException("Unsupported GameAssembly.dll build (" + text + "). No memory changed.");
		}
		(IntPtr, byte[], byte[], string)[] array = manifest.Patches.Select(delegate(Patch p)
		{
			int num5 = int.Parse(p.Rva.Substring(2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
			byte[] array4 = Convert.FromHexString(p.Before);
			byte[] array5 = Convert.FromHexString(p.After);
			if (num5 < 0 || array4.Length != array5.Length || array4.Length == 0 || num5 > moduleSize - array4.Length)
			{
				throw new InvalidOperationException("Invalid patch bounds: " + p.Name);
			}
			return (Address: IntPtr.Add(module, num5), Before: array4, After: array5, Name: p.Name);
		}).ToArray();
		bool flag = true;
		bool flag2 = true;
		(IntPtr, byte[], byte[], string)[] array2 = array;
		for (int num = 0; num < array2.Length; num++)
		{
			(IntPtr, byte[], byte[], string) tuple = array2[num];
			byte[] array3 = new byte[tuple.Item2.Length];
			Marshal.Copy(tuple.Item1, array3, 0, array3.Length);
			flag &= array3.SequenceEqual(tuple.Item2);
			flag2 &= array3.SequenceEqual(tuple.Item3);
		}
		if (flag2)
		{
			return false;
		}
		if (!flag)
		{
			throw new InvalidOperationException("Conflicting native patches detected. No memory changed; check other scoring mods.");
		}
		int num2 = -1;
		try
		{
			for (int num3 = 0; num3 < array.Length; num3++)
			{
				num2 = num3;
				Write(array[num3].Item1, array[num3].Item3);
			}
		}
		catch (Exception item)
		{
			List<Exception> list = new List<Exception> { item };
			for (int num4 = num2; num4 >= 0; num4--)
			{
				try
				{
					Write(array[num4].Item1, array[num4].Item2);
				}
				catch (Exception item2)
				{
					list.Add(item2);
				}
			}
			throw new AggregateException("Patch failed; original memory restoration was attempted for every changed site. Restart the game before playing.", list);
		}
		return true;
	}

	private static void Write(IntPtr address, byte[] bytes)
	{
		if (!VirtualProtect(address, (UIntPtr)(ulong)bytes.Length, 64u, out var oldProtection))
		{
			throw new Win32Exception(Marshal.GetLastWin32Error());
		}
		bool flag;
		try
		{
			Marshal.Copy(bytes, 0, address, bytes.Length);
			if (!FlushInstructionCache(GetCurrentProcess(), address, (UIntPtr)(ulong)bytes.Length))
			{
				throw new Win32Exception(Marshal.GetLastWin32Error());
			}
			byte[] array = new byte[bytes.Length];
			Marshal.Copy(address, array, 0, array.Length);
			if (!array.SequenceEqual(bytes))
			{
				throw new IOException("Memory patch verification failed.");
			}
		}
		finally
		{
			flag = VirtualProtect(address, (UIntPtr)(ulong)bytes.Length, oldProtection, out var _);
		}
		if (!flag)
		{
			throw new Win32Exception(Marshal.GetLastWin32Error());
		}
	}

	[DllImport("kernel32", SetLastError = true)]
	[return: MarshalAs(UnmanagedType.Bool)]
	private static extern bool VirtualProtect(IntPtr address, UIntPtr size, uint protection, out uint oldProtection);

	[DllImport("kernel32", SetLastError = true)]
	[return: MarshalAs(UnmanagedType.Bool)]
	private static extern bool FlushInstructionCache(IntPtr process, IntPtr address, UIntPtr size);

	[DllImport("kernel32")]
	private static extern IntPtr GetCurrentProcess();
}
[BepInPlugin("idgafman.gunmancontracts", "IDGAFMan", "1.0.0")]
[BepInProcess("GunmanContracts.exe")]
public sealed class Plugin : BasePlugin
{
	internal static ManualLogSource Logger;

	public override void Load()
	{
		Logger = ((BasePlugin)this).Log;
		Start();
	}

	public override bool Unload()
	{
		return false;
	}

	private static void Start()
	{
		//IL_008f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0096: Expected O, but got Unknown
		//IL_011f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0126: Expected O, but got Unknown
		//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
		bool flag2 = default(bool);
		try
		{
			using Process process = Process.GetCurrentProcess();
			ProcessModule processModule = ((IEnumerable)process.Modules).Cast<ProcessModule>().Single((ProcessModule m) => string.Equals(m.ModuleName, "GameAssembly.dll", StringComparison.OrdinalIgnoreCase));
			bool flag = PatchEngine.Apply(processModule.BaseAddress, processModule.ModuleMemorySize, processModule.FileName ?? throw new IOException("Cannot resolve the loaded game DLL path."));
			Logger.LogInfo((object)(flag ? "IDGAFMan enabled: cheats always available, cheat-compatible personal scores, every score deduction zeroed, all-weapon bonus. Steam leaderboard uploads blocked." : "IDGAFMan patches already present (for example, standalone installation). No duplicate changes applied."));
		}
		catch (Exception ex)
		{
			ManualLogSource logger = Logger;
			BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(29, 1, ref flag2);
			if (flag2)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("IDGAFMan could not activate: ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex);
			}
			logger.LogError(val);
		}
		try
		{
			Harmony val2 = new Harmony("idgafman.gunmancontracts");
			val2.PatchAll(typeof(ResultsScreen));
			val2.PatchAll(typeof(TextOverrides));
			val2.PatchAll(typeof(SaveRedirect.Reader));
			val2.PatchAll(typeof(SaveRedirect.Writer));
			val2.PatchAll(typeof(SaveRedirect.Disk));
		}
		catch (Exception ex2)
		{
			ManualLogSource logger2 = Logger;
			BepInExWarningLogInterpolatedStringHandler val3 = new BepInExWarningLogInterpolatedStringHandler(44, 1, ref flag2);
			if (flag2)
			{
				((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("IDGAFMan results-screen tweaks unavailable: ");
				((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<Exception>(ex2);
			}
			logger2.LogWarning(val3);
		}
	}
}
[HarmonyPatch(typeof(ANBHighscoreDisplay), "UpdateHighscoreDisplay")]
internal static class ResultsScreen
{
	private const string PistolBonusLabel = "Special Bonus, just for you <3";

	private static bool failed;

	private static bool described;

	private static (GameObject Row, TextMeshProUGUI Value, TextMeshProUGUI Points)[] Deductions(ANBHighscoreDisplay display)
	{
		return new(GameObject, TextMeshProUGUI, TextMeshProUGUI)[4]
		{
			(display.civilianCasultiesElement, display.displayCivilianCasulties, display.displayCivilianCasultiesPts),
			(display.hitsTakenElement, display.displayHitstaken, display.displayHitstakenPts),
			(display.checkpointsUsedElement, display.displayCheckpointsUsed, display.displayCheckpointsUsedPts),
			(display.slomoTimeElement, display.displaySlomotime, display.displaySlomotimePts)
		};
	}

	private static void Postfix(ANBHighscoreDisplay __instance)
	{
		//IL_0111: Unknown result type (might be due to invalid IL or missing references)
		//IL_0118: Expected O, but got Unknown
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: 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)
		if (failed)
		{
			return;
		}
		try
		{
			Describe(__instance);
			TextMeshProUGUI displayNoDamgebonus = __instance.displayNoDamgebonus;
			Color valueColor = (((Object)(object)displayNoDamgebonus != (Object)null) ? ((Graphic)displayNoDamgebonus).color : Color.white);
			(GameObject, TextMeshProUGUI, TextMeshProUGUI)[] array = Deductions(__instance);
			for (int i = 0; i < array.Length; i++)
			{
				var (row, value, points) = array[i];
				Restyle(row, (TMP_Text)(object)value, (TMP_Text)(object)points, valueColor);
			}
			TMP_Text val = Others(__instance.pistolOnlyElement, (TMP_Text)__instance.displayPistolOnlybonus).FirstOrDefault((Func<TMP_Text, bool>)((TMP_Text text) => !IsUnit(text.text)));
			if ((Object)(object)val != (Object)null && val.text != "Special Bonus, just for you <3")
			{
				ANBLanguageText component = ((Component)val).GetComponent<ANBLanguageText>();
				if ((Object)(object)component != (Object)null)
				{
					((Behaviour)component).enabled = false;
				}
				val.richText = false;
				val.text = "Special Bonus, just for you <3";
			}
		}
		catch (Exception ex)
		{
			failed = true;
			ManualLogSource logger = Plugin.Logger;
			if (logger != null)
			{
				bool flag = default(bool);
				BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(40, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("IDGAFMan results-screen tweaks stopped: ");
					((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<Exception>(ex);
				}
				logger.LogWarning(val2);
			}
		}
	}

	private static void Restyle(GameObject row, TMP_Text value, TMP_Text points, Color valueColor)
	{
		//IL_002c: 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)
		if ((Object)(object)value == (Object)null || (Object)(object)points == (Object)null)
		{
			return;
		}
		((Component)value).gameObject.SetActive(false);
		points.text = value.text;
		((Graphic)points).color = valueColor;
		foreach (TMP_Text item in Others(row, value, points))
		{
			if (IsUnit(item.text))
			{
				((Component)item).gameObject.SetActive(false);
			}
			else
			{
				((Graphic)item).color = Color.white;
			}
		}
	}

	private static IEnumerable<TMP_Text> Others(GameObject row, params TMP_Text[] known)
	{
		if (!((Object)(object)row == (Object)null))
		{
			return ((IEnumerable<TMP_Text>)row.GetComponentsInChildren<TMP_Text>(true)).Where((TMP_Text text) => known.All((TMP_Text other) => (Object)(object)other == (Object)null || ((Il2CppObjectBase)other).Pointer != ((Il2CppObjectBase)text).Pointer));
		}
		return Enumerable.Empty<TMP_Text>();
	}

	private static bool IsUnit(string text)
	{
		switch (text?.Trim().ToLowerInvariant())
		{
		case "pts":
		case "pkt":
		case "pt":
			return true;
		default:
			return false;
		}
	}

	private static void Describe(ANBHighscoreDisplay display)
	{
		if (described)
		{
			return;
		}
		described = true;
		foreach (GameObject item in (from entry in Deductions(display)
			select entry.Row).Append(display.pistolOnlyElement))
		{
			if (!((Object)(object)item != (Object)null))
			{
				continue;
			}
			ManualLogSource logger = Plugin.Logger;
			if (logger != null)
			{
				logger.LogInfo((object)("IDGAFMan row '" + ((Object)item).name + "': " + string.Join(", ", ((IEnumerable<TMP_Text>)item.GetComponentsInChildren<TMP_Text>(true)).Select((TMP_Text text) => ((Object)text).name + "='" + text.text + "'"))));
			}
		}
	}
}
internal static class SaveRedirect
{
	[HarmonyPatch(typeof(QuickSaveReader), "Create", new Type[] { typeof(string) })]
	internal static class Reader
	{
		private static void Prefix(ref string root)
		{
			Redirect(ref root);
		}
	}

	[HarmonyPatch(typeof(QuickSaveWriter), "Create", new Type[] { typeof(string) })]
	internal static class Writer
	{
		private static void Prefix(ref string root)
		{
			Redirect(ref root);
		}
	}

	[HarmonyPatch(typeof(FileAccess))]
	internal static class Disk
	{
		[HarmonyPrefix]
		[HarmonyPatch("LoadString")]
		private static void Load(ref string filename)
		{
			Redirect(ref filename);
		}

		[HarmonyPrefix]
		[HarmonyPatch("SaveString")]
		private static void Save(ref string filename, ref string value)
		{
			bool num = filename == "SaveData_Highscores" || filename == "SaveData_Highscores_IDGAFMan";
			Redirect(ref filename);
			if (num)
			{
				value = ScoreVault.Protect(value);
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch("LoadString")]
		private static void Loaded(string filename, ref string __result)
		{
			if (filename == "SaveData_Highscores_IDGAFMan")
			{
				__result = ScoreVault.Unprotect(__result);
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch("Exists")]
		private static void Exists(ref string filename)
		{
			Redirect(ref filename);
		}

		[HarmonyPrefix]
		[HarmonyPatch("Delete")]
		private static void Delete(ref string filename)
		{
			Redirect(ref filename);
		}

		[HarmonyPrefix]
		[HarmonyPatch("getBasePath")]
		private static void BasePath(ref string filename)
		{
			Redirect(ref filename);
		}
	}

	private const string Stock = "SaveData_Highscores";

	private const string Modded = "SaveData_Highscores_IDGAFMan";

	private static bool logged;

	private static void Redirect(ref string name)
	{
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Expected O, but got Unknown
		if (name != "SaveData_Highscores")
		{
			return;
		}
		name = "SaveData_Highscores_IDGAFMan";
		if (logged)
		{
			return;
		}
		logged = true;
		ManualLogSource logger = Plugin.Logger;
		if (logger != null)
		{
			bool flag = default(bool);
			BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(73, 2, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("IDGAFMan: high scores read and written as ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("SaveData_Highscores_IDGAFMan");
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(".json; ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("SaveData_Highscores");
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(".json is left untouched.");
			}
			logger.LogInfo(val);
		}
	}
}
internal static class ScoreVault
{
	private const string Header = "IDGAFMan1:";

	private static readonly byte[] Key = SHA256.HashData(Encoding.UTF8.GetBytes("IDGAFMan/GunmanContracts/local-scores/v1"));

	internal static string Protect(string plain)
	{
		if (string.IsNullOrEmpty(plain))
		{
			return plain;
		}
		using Aes aes = Aes.Create();
		aes.Key = Key;
		aes.GenerateIV();
		using ICryptoTransform cryptoTransform = aes.CreateEncryptor();
		byte[] bytes = Encoding.UTF8.GetBytes(plain);
		byte[] array = cryptoTransform.TransformFinalBlock(bytes, 0, bytes.Length);
		byte[] array2 = new byte[aes.IV.Length + array.Length];
		aes.IV.CopyTo(array2, 0);
		array.CopyTo(array2, aes.IV.Length);
		return "IDGAFMan1:" + Convert.ToBase64String(array2);
	}

	internal static string Unprotect(string stored)
	{
		if (string.IsNullOrEmpty(stored) || !stored.StartsWith("IDGAFMan1:", StringComparison.Ordinal))
		{
			return stored;
		}
		try
		{
			byte[] array = Convert.FromBase64String(stored.Substring("IDGAFMan1:".Length));
			using Aes aes = Aes.Create();
			aes.Key = Key;
			aes.IV = array[..16];
			using ICryptoTransform cryptoTransform = aes.CreateDecryptor();
			return Encoding.UTF8.GetString(cryptoTransform.TransformFinalBlock(array, 16, array.Length - 16));
		}
		catch (Exception)
		{
			return "";
		}
	}
}
[HarmonyPatch(typeof(ANBLanguageText), "SetText")]
internal static class TextOverrides
{
	private static readonly Dictionary<string, string> Replacements = new Dictionary<string, string>
	{
		["cheats_unlock"] = "Even though you can gain score and achievements, your score won't be published to steam leaderboards with this mod.",
		["hs_nodmage"] = "Bonus for being Sexy"
	};

	private static readonly HashSet<string> logged = new HashSet<string>();

	private static bool failed;

	private static void Postfix(ANBLanguageText __instance)
	{
		//IL_00db: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e2: Expected O, but got Unknown
		//IL_0057: Unknown result type (might be due to invalid IL or missing references)
		//IL_005e: Expected O, but got Unknown
		if (failed)
		{
			return;
		}
		bool flag = default(bool);
		try
		{
			string textID = __instance.TextID;
			if (textID == null || !Replacements.TryGetValue(textID, out var value))
			{
				return;
			}
			TextMeshProUGUI txtField = __instance.txtField;
			if ((Object)(object)txtField == (Object)null)
			{
				return;
			}
			if (logged.Add(textID))
			{
				ManualLogSource logger = Plugin.Logger;
				if (logger != null)
				{
					BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(34, 3, ref flag);
					if (flag)
					{
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("IDGAFMan rewrote '");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(textID);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' on '");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(((Object)txtField).name);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("', was '");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(((TMP_Text)txtField).text);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'.");
					}
					logger.LogInfo(val);
				}
			}
			((TMP_Text)txtField).text = value;
		}
		catch (Exception ex)
		{
			failed = true;
			ManualLogSource logger = Plugin.Logger;
			if (logger != null)
			{
				BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(33, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("IDGAFMan text overrides stopped: ");
					((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<Exception>(ex);
				}
				logger.LogWarning(val2);
			}
		}
	}
}