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);
}
}
}
}