using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using Gear;
using HarmonyLib;
using Iced.Intel;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.Runtime;
using Il2CppInterop.Runtime.Runtime.VersionSpecific.Class;
using Il2CppInterop.Runtime.Runtime.VersionSpecific.MethodInfo;
using Microsoft.CodeAnalysis;
using PierceBugFix.Imports;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("PierceBugFix")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+c7b2b13c68d5e8a5a8d5c100e6f6591f27dd62f8")]
[assembly: AssemblyProduct("PierceBugFix")]
[assembly: AssemblyTitle("PierceBugFix")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.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]
[Microsoft.CodeAnalysis.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;
}
}
}
namespace PierceBugFix
{
internal static class Logger
{
private static ManualLogSource m_LogSource;
public static void SetupFromInit(ManualLogSource logSource)
{
m_LogSource = logSource;
}
private static string Format(object data)
{
return data.ToString();
}
public static void Debug(object msg)
{
m_LogSource.LogDebug((object)Format(msg));
}
public static void Info(object msg)
{
m_LogSource.LogInfo((object)Format(msg));
}
public static void Warn(object msg)
{
m_LogSource.LogWarning((object)Format(msg));
}
public static void Error(object msg)
{
m_LogSource.LogError((object)Format(msg));
}
public static void Fatal(object msg)
{
m_LogSource.LogFatal((object)Format(msg));
}
}
internal struct MemoryProtectionCookie : IDisposable
{
private Kernel32.MemoryProtectionConstant m_OldProtection;
private readonly IntPtr m_Address;
private readonly IntPtr m_Size;
public MemoryProtectionCookie(IntPtr address, Kernel32.MemoryProtectionConstant newProtection, IntPtr size)
{
m_Address = address;
m_Size = size;
if (!Kernel32.VirtualProtect(m_Address, m_Size, newProtection, out m_OldProtection))
{
Environment.FailFast($"Failed to protect address 0x{m_Address:X2} with protection {newProtection}");
}
Logger.Debug($"MemoryProtectionCookie: Modified protection of address 0x{m_Address:X2} from {m_OldProtection} to {newProtection}");
}
public void Dispose()
{
Kernel32.MemoryProtectionConstant oldProtection = m_OldProtection;
if (!Kernel32.VirtualProtect(m_Address, m_Size, m_OldProtection, out m_OldProtection))
{
Environment.FailFast($"Failed to revert memory protection of address 0x{m_Address:X2}");
}
Logger.Debug($"MemoryProtectionCookie: Reverted protection of address 0x{m_Address:X2} to {oldProtection}");
}
}
public class PierceBugDisassembler
{
public unsafe static IntPtr FindInc(IntPtr methodPointer, int index, int totalExpected, int widthExpected)
{
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_0024: Expected O, but got Unknown
//IL_0042: Unknown result type (might be due to invalid IL or missing references)
//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
//IL_00b6: Invalid comparison between Unknown and I4
//IL_0057: Unknown result type (might be due to invalid IL or missing references)
//IL_0061: Invalid comparison between Unknown and I4
IntPtr intPtr = IntPtr.Zero;
StreamCodeReader val = new StreamCodeReader((Stream)new UnmanagedMemoryStream((byte*)(void*)methodPointer, 65536L, 65536L, FileAccess.Read));
Decoder val2 = Decoder.Create(sizeof(void*) * 8, (CodeReader)(object)val, (DecoderOptions)0);
val2.IP = (ulong)(long)methodPointer;
Instruction val3 = default(Instruction);
val2.Decode(ref val3);
int num = 0;
while ((int)((Instruction)(ref val3)).Mnemonic != 1620)
{
if ((int)((Instruction)(ref val3)).Mnemonic == 279)
{
num++;
if (num == index)
{
intPtr = (IntPtr)(long)((Instruction)(ref val3)).IP;
if (((Instruction)(ref val3)).NextIP - ((Instruction)(ref val3)).IP != (ulong)widthExpected)
{
Logger.Error("PierceBugFix found an instruction with an unexpected width, this probably means the method has changed in some way and we should avoid changing it.");
Environment.FailFast("PierceBugFix found an instruction with an unexpected width, this probably means the method has changed in some way and we should avoid changing it.");
}
}
}
val2.Decode(ref val3);
}
val.Stream.Dispose();
if (num != totalExpected)
{
Logger.Error("PierceBugFix didn't find the correct number of `Inc` instructions, this probably means the method has changed in some way and we should avoid changing it.");
Environment.FailFast("PierceBugFix didn't find the correct number of `Inc` instructions, this probably means the method has changed in some way and we should avoid changing it.");
}
if (intPtr == IntPtr.Zero)
{
Logger.Error("PierceBugFix found a zero instruction int pointer for our `Inc` instruction, something has gone terribly wrong.");
Environment.FailFast("PierceBugFix found a zero instruction int pointer for our `Inc` instruction, something has gone terribly wrong.");
}
return intPtr;
}
}
[HarmonyPatch]
internal static class PierceSpreadPatch
{
[HarmonyTargetMethod]
private static MethodBase FindWeaponRayFunc(Harmony harmony)
{
return AccessTools.Method(typeof(Weapon), "CastWeaponRay", new Type[4]
{
typeof(Transform),
typeof(WeaponHitData).MakeByRefType(),
typeof(Vector3),
typeof(int)
}, (Type[])null);
}
[HarmonyPostfix]
private static void PostRayCallback(ref WeaponHitData weaponRayData, bool __result)
{
weaponRayData.randomSpread = 0f;
weaponRayData.angOffsetX = 0f;
weaponRayData.angOffsetY = 0f;
if (!__result)
{
weaponRayData.maxRayDist = 0f;
}
}
}
[BepInPlugin("tru0067.PierceBugFix", "PierceBugFix", "1.0.0")]
public class Plugin : BasePlugin
{
internal const string GUID = "tru0067.PierceBugFix";
public const int NOP = 144;
public override void Load()
{
//IL_002c: Unknown result type (might be due to invalid IL or missing references)
Logger.SetupFromInit(((BasePlugin)this).Log);
Logger.Info("PierceBugFix is loading...");
PatchBulletWeaponFire<BulletWeapon>(3, 4, 3);
PatchBulletWeaponFire<Shotgun>(3, 5, 3);
new Harmony("tru0067.PierceBugFix").PatchAll();
Logger.Info("PierceBugFix is loaded!");
}
private unsafe void PatchBulletWeaponFire<T>(int incIndex, int incTotalExpected, int incWidthExpected) where T : BulletWeapon
{
string name = typeof(T).Name;
Logger.Info("Patching `" + name + ".Fire`...");
INativeClassStruct val = UnityVersionHandler.Wrap((Il2CppClass*)(void*)Il2CppClassPointerStore<T>.NativeClassPtr);
for (int i = 0; i < val.MethodCount; i++)
{
INativeMethodInfoStruct val2 = UnityVersionHandler.Wrap(val.Methods[i]);
if (!(Marshal.PtrToStringAnsi(val2.Name) == "Fire"))
{
continue;
}
IntPtr intPtr = PierceBugDisassembler.FindInc(val2.MethodPointer, incIndex, incTotalExpected, incWidthExpected);
using (new MemoryProtectionCookie(intPtr, Kernel32.MemoryProtectionConstant.ExecuteReadWrite, new IntPtr(16)))
{
for (int j = 0; j < incWidthExpected; j++)
{
*(sbyte*)((long)intPtr + (long)new IntPtr(j)) = -112;
}
}
Logger.Info("`" + name + ".Fire` is patched!");
return;
}
Logger.Error("PierceBugFix failed to find `" + name + ".Fire`");
Environment.FailFast("PierceBugFix failed to find `" + name + ".Fire`");
}
}
}
namespace PierceBugFix.Imports
{
internal static class Kernel32
{
[Flags]
public enum MemoryAllocationConstant : uint
{
Commit = 0x1000u,
Reserve = 0x2000u,
Reset = 0x80000u,
ResetUndo = 0x1000000u,
LargePages = 0x20000000u,
Physical = 0x400000u,
TopDown = 0x100000u,
WriteWatch = 0x200000u
}
[Flags]
public enum MemoryProtectionConstant : uint
{
NoAccess = 1u,
ReadOnly = 2u,
ReadWrite = 4u,
WriteCopy = 8u,
Execute = 0x10u,
ExecuteRead = 0x20u,
ExecuteReadWrite = 0x40u,
ExecuteWriteCopy = 0x80u
}
[DllImport("kernel32.dll")]
public static extern IntPtr VirtualAlloc(IntPtr lpAddress, IntPtr dwSize, MemoryAllocationConstant flAllocationType, MemoryProtectionConstant flProtect);
[DllImport("kernel32.dll")]
public static extern bool VirtualProtect(IntPtr lpAddress, IntPtr dwSize, MemoryProtectionConstant flNewProtect, out MemoryProtectionConstant lpflOldProtect);
}
}