using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Logging; using DunGenReferenceFixer.Patches.Native; using DunGenReferenceFixer.Utilities.Native; using HarmonyLib; using Microsoft.CodeAnalysis; using Mono.Cecil; using Unity.Collections.LowLevel.Unsafe; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("DunGenReferenceFixer")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("DunGenReferenceFixer")] [assembly: AssemblyCopyright("Copyright © 2024")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("ee30c7a9-db89-425a-acff-2202e901ce36")] [assembly: AssemblyFileVersion("0.0.1")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.1.0")] [module: UnverifiableCode] [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 DunGenReferenceFixer { [BepInPlugin("Zaggy1024.DunGenReferenceFixer", "DunGenReferenceFixer", "0.0.1")] public static class DunGenReferenceFixerPreloader { public const string PluginName = "DunGenReferenceFixer"; public const string PluginGUID = "Zaggy1024.DunGenReferenceFixer"; public const string PluginVersion = "0.0.1"; public static ManualLogSource Log = Logger.CreateLogSource("DunGenReferenceFixer"); private static Harmony harmony = new Harmony("DunGenReferenceFixer"); public static IEnumerable TargetDLLs { get; } = Array.Empty(); public static void Patch(AssemblyDefinition assembly) { } public static void Initialize() { } public static void Finish() { harmony.PatchAll(typeof(DunGenReferenceFixerPreloader)); } [HarmonyPostfix] [HarmonyPatch(typeof(Chainloader), "Initialize")] private static void ApplyAllNativePatches() { PatchGetMonoClassWithAssemblyName.Apply(); } } } namespace DunGenReferenceFixer.Utilities.Native { internal static class NativeHelpers { internal static readonly IntPtr BaseAddress = GetUnityPlayerModule().BaseAddress; internal static readonly bool IsDebugBuild = Debug.isDebugBuild; private static ProcessModule GetUnityPlayerModule() { ProcessModuleCollection modules = Process.GetCurrentProcess().Modules; for (int i = 0; i < modules.Count; i++) { ProcessModule processModule = modules[i]; if (processModule.ModuleName.Contains("UnityPlayer")) { return processModule; } } return null; } } internal sealed class NativeHook { internal const int JumpSize = 14; private const uint MEM_COMMIT = 4096u; private const uint MEM_RESERVE = 8192u; private const uint PAGE_READWRITE = 4u; private const uint PAGE_EXECUTE_READ = 32u; private const uint PAGE_EXECUTE_READWRITE = 64u; internal IntPtr Trampoline { get; } internal NativeHook(IntPtr target, IntPtr detour, int prologueSize) { if (prologueSize < 14) { throw new ArgumentOutOfRangeException("prologueSize", $"At least {14} bytes of prologue are needed to fit the jump to the detour."); } Trampoline = BuildTrampoline(target, prologueSize); InstallJump(target, detour, prologueSize); } private unsafe static IntPtr BuildTrampoline(IntPtr target, int prologueSize) { int num = prologueSize + 14; IntPtr intPtr = VirtualAlloc(IntPtr.Zero, (UIntPtr)(ulong)num, 12288u, 4u); if (intPtr == IntPtr.Zero) { throw new InvalidOperationException($"Failed to allocate the trampoline (VirtualAlloc error {Marshal.GetLastWin32Error()})."); } Buffer.MemoryCopy((void*)target, (void*)intPtr, num, prologueSize); WriteJump(IntPtr.Add(intPtr, prologueSize), IntPtr.Add(target, prologueSize)); if (!VirtualProtect(intPtr, (UIntPtr)(ulong)num, 32u, out var _)) { throw new InvalidOperationException($"Failed to make the trampoline executable (VirtualProtect error {Marshal.GetLastWin32Error()})."); } FlushInstructionCache(GetCurrentProcess(), intPtr, (UIntPtr)(ulong)num); return intPtr; } private unsafe static void InstallJump(IntPtr target, IntPtr detour, int prologueSize) { if (!VirtualProtect(target, (UIntPtr)(ulong)prologueSize, 64u, out var oldProtect)) { throw new InvalidOperationException($"Failed to make the patch site writable (VirtualProtect error {Marshal.GetLastWin32Error()})."); } WriteJump(target, detour); byte* ptr = (byte*)(void*)target; for (int i = 14; i < prologueSize; i++) { ptr[i] = 204; } VirtualProtect(target, (UIntPtr)(ulong)prologueSize, oldProtect, out var _); FlushInstructionCache(GetCurrentProcess(), target, (UIntPtr)(ulong)prologueSize); } private unsafe static void WriteJump(IntPtr at, IntPtr destination) { byte* ptr = (byte*)(void*)at; *ptr = byte.MaxValue; ptr[1] = 37; ptr[2] = 0; ptr[3] = 0; ptr[4] = 0; ptr[5] = 0; *(long*)(ptr + 6) = destination.ToInt64(); } [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr VirtualAlloc(IntPtr address, UIntPtr size, uint allocationType, uint protect); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool VirtualProtect(IntPtr address, UIntPtr size, uint newProtect, out uint oldProtect); [DllImport("kernel32.dll")] private static extern IntPtr GetCurrentProcess(); [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool FlushInstructionCache(IntPtr process, IntPtr baseAddress, UIntPtr size); } } namespace DunGenReferenceFixer.Patches.Native { internal static class PatchGetMonoClassWithAssemblyName { [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate IntPtr GetMonoClassWithAssemblyNameDelegate(IntPtr thisMonoManager, IntPtr result, IntPtr param_2, IntPtr param_3, ref BasicStringRef param_4); private struct BasicStringRef { public unsafe char* Characters; public ulong Size; public unsafe BasicStringRef(ReadOnlySpan data) { Characters = (char*)UnsafeUtility.AddressOf(ref MemoryMarshal.GetReference(data)); Size = (ulong)data.Length; } public unsafe static implicit operator ReadOnlySpan(BasicStringRef str) { if (str.Size > int.MaxValue) { throw new InvalidCastException("String is too large"); } return new ReadOnlySpan(str.Characters, (int)str.Size); } public unsafe override readonly string ToString() { return Encoding.UTF8.GetString((byte*)Characters, (int)Math.Min(Size, 2147483647uL)); } } private static NativeHook getMonoClassWithAssemblyNameHook; private static GetMonoClassWithAssemblyNameDelegate getMonoClassWithAssemblyNameDetour; private static GetMonoClassWithAssemblyNameDelegate getMonoClassWithAssemblyNameOriginal; private static ReadOnlySpan ExpectedPrologue => new byte[15] { 76, 137, 76, 36, 32, 76, 137, 68, 36, 24, 72, 137, 76, 36, 8 }; [MethodImpl(MethodImplOptions.NoInlining)] internal unsafe static void Apply() { int num = 7881440; if (NativeHelpers.IsDebugBuild) { num = 16836128; } IntPtr intPtr = NativeHelpers.BaseAddress + num; if (!ExpectedPrologue.SequenceEqual(new ReadOnlySpan((void*)intPtr, ExpectedPrologue.Length))) { DunGenReferenceFixerPreloader.Log.LogError((object)$"MonoManager::GetMonoClassWithAssemblyName() does not begin with the expected instructions at UnityPlayer+0x{num:X}. This build of the game is not supported, DunGen references will be left alone."); return; } getMonoClassWithAssemblyNameDetour = GetMonoClassWithAssemblyNameDetour; IntPtr functionPointerForDelegate = Marshal.GetFunctionPointerForDelegate(getMonoClassWithAssemblyNameDetour); getMonoClassWithAssemblyNameHook = new NativeHook(intPtr, functionPointerForDelegate, ExpectedPrologue.Length); getMonoClassWithAssemblyNameOriginal = Marshal.GetDelegateForFunctionPointer(getMonoClassWithAssemblyNameHook.Trampoline); DunGenReferenceFixerPreloader.Log.LogInfo((object)"Patched MonoManager::GetMonoClassWithAssemblyName() to fix DunGen references."); } private unsafe static bool CheckNamespace(IntPtr namespaceName, ReadOnlySpan bytes) { if (namespaceName == IntPtr.Zero) { return false; } byte* ptr = (byte*)(void*)namespaceName; for (int i = 0; i < bytes.Length; i++) { if (ptr[i] != bytes[i]) { return false; } } byte b = ptr[bytes.Length]; if (b != 0) { return b == 46; } return true; } private static IntPtr GetMonoClassWithAssemblyNameDetour(IntPtr thisMonoManager, IntPtr result, IntPtr className, IntPtr namespaceName, ref BasicStringRef assemblyName) { ReadOnlySpan bytes = "DunGen"u8; ReadOnlySpan span = "Assembly-CSharp"u8; if (CheckNamespace(namespaceName, bytes) && span.SequenceEqual(assemblyName)) { ReadOnlySpan data = "DunGen"u8; BasicStringRef param_ = new BasicStringRef(data); getMonoClassWithAssemblyNameOriginal(thisMonoManager, result, className, namespaceName, ref param_); } else { getMonoClassWithAssemblyNameOriginal(thisMonoManager, result, className, namespaceName, ref assemblyName); } return result; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }