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.Json;
using BepInEx;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("FeelingBetterAlready")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("FeelingBetterAlready")]
[assembly: AssemblyTitle("FeelingBetterAlready")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace FeelingBetterAlready;
[HarmonyPatch(typeof(ANBGameLogic), "Update")]
internal static class KeepRegenerating
{
private static bool failed;
private static void Postfix(ANBGameLogic __instance)
{
//IL_004f: Unknown result type (might be due to invalid IL or missing references)
//IL_0055: Expected O, but got Unknown
if (failed)
{
return;
}
try
{
if (!(__instance.regainHealthTimeCurrent > 0f))
{
float playerHitPoints = __instance.playerHitPoints;
if (!(playerHitPoints <= 0f) && !(playerHitPoints >= __instance.playerHitPointsMaxRecharge))
{
__instance.StartRegainHealth();
}
}
}
catch (Exception ex)
{
failed = true;
ManualLogSource logger = Plugin.Logger;
if (logger != null)
{
bool flag = default(bool);
BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(49, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("FeelingBetterAlready regeneration timer stopped: ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex);
}
logger.LogWarning(val);
}
}
}
}
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("FeelingBetterAlready requires Windows x64.");
}
using Stream utf8Json = Assembly.GetExecutingAssembly().GetManifestResourceStream("FeelingBetterAlready.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("feelingbetteralready.gunmancontracts", "FeelingBetterAlready", "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_00df: Unknown result type (might be due to invalid IL or missing references)
//IL_00e6: Expected O, but got Unknown
//IL_00bd: 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 ? "FeelingBetterAlready enabled: health regenerates up to the difficulty's own maximum instead of stopping at 2 HP." : "FeelingBetterAlready patches already present. No duplicate changes applied."));
}
catch (Exception ex)
{
ManualLogSource logger = Logger;
BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(41, 1, ref flag2);
if (flag2)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("FeelingBetterAlready could not activate: ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex);
}
logger.LogError(val);
}
try
{
new Harmony("feelingbetteralready.gunmancontracts").PatchAll(typeof(KeepRegenerating));
}
catch (Exception ex2)
{
ManualLogSource logger2 = Logger;
BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(63, 1, ref flag2);
if (flag2)
{
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("FeelingBetterAlready could not restart the regeneration timer: ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<Exception>(ex2);
}
logger2.LogWarning(val2);
}
}
}