You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用MinHook挂钩游戏函数后触发Hook却游戏崩溃的问题排查

问题描述

我写了一段用MinHook挂钩游戏函数的C++代码,目标是GameAssembly.dll里的Game.Managers.BuildingManager.BuildingManager$$RequestStartBuilding函数,偏移量设为0x341C20。代码如下:

#include "pch.h"
#include <Windows.h>
#include <cstdio>
#include <MinHook.h>

uintptr_t base = (uintptr_t)GetModuleHandle(NULL);
uintptr_t GameAssembly = (uintptr_t)GetModuleHandle(L"GameAssembly.dll");
uintptr_t SetOffset = 0x341C20;

void CreateConsole()
{
    AllocConsole();
    FILE* f;
    freopen_s(&f, "CONOUT$", "w", stdout);
}

void init()
{
    MH_Initialize();
    CreateConsole();
    printf("started");
}

bool(__fastcall* build)(DWORD*, DWORD*, DWORD*);
bool __stdcall build_hook(DWORD* __this, DWORD* buildingDef, DWORD* methodinfo)
{
    printf("buildingDef is called!!!");
    return true;
}

void main()
{
    init();
    MH_CreateHook(reinterpret_cast<LPVOID*>(GameAssembly + SetOffset), &build_hook, (LPVOID*)&build);
    MH_EnableHook(reinterpret_cast<LPVOID*>(GameAssembly + SetOffset));
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        CreateThread(0, 0, (LPTHREAD_START_ROUTINE)main, 0, 0, 0);
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

目标函数在dump中的信息:

{
  "Address": 3415072,
  "Name": "Game.Managers.BuildingManager.BuildingManager$$RequestStartBuilding",
  "Signature": "bool Game_Managers_BuildingManager_BuildingManager__RequestStartBuilding (Game_Managers_BuildingManager_BuildingManager_o* __this, Game_Managers_BuildingManager_BuildingConfiguration_BuildingDef_o* buildingDef, const MethodInfo* method);",
  "TypeSignature": "iiii"
}

现在代码能触发hook里的printf,但之后游戏直接崩溃,改返回值为true或false都没用,请问我用MinHook的时候哪里错了?


问题分析与修正

崩溃的核心原因是函数调用约定不匹配+未执行原函数破坏游戏逻辑,具体问题和修正方案如下:

1. 调用约定不匹配导致栈平衡破坏

从dump签名看,目标是C++成员函数,默认调用约定为thiscall,但你声明原函数指针用了__fastcall,hook函数用了__stdcall——不同调用约定的参数传递、栈清理规则完全不同,直接导致栈结构混乱,触发崩溃。

对于Unity IL2CPP导出的成员函数,实际调用约定通常为__fastcall,必须保证原函数指针和hook函数的调用约定完全一致。

2. 未调用原函数导致逻辑断裂

你直接返回true/false跳过了原函数执行,但游戏后续逻辑依赖原函数修改的内部状态,缺失这些状态必然引发崩溃,必须调用原函数维持逻辑完整性。

3. MinHook API使用错误

MH_CreateHook的第一个参数是目标函数的地址(LPVOID),而非LPVOID*,你的强制转换会导致地址解析错误;另外没有检查MinHook返回值,无法排查hook创建/启用是否成功。


修正后的代码

#include "pch.h"
#include <Windows.h>
#include <cstdio>
#include <MinHook.h>

uintptr_t GameAssembly = (uintptr_t)GetModuleHandle(L"GameAssembly.dll");
const uintptr_t SetOffset = 0x341C20;

void CreateConsole()
{
    AllocConsole();
    FILE* f;
    freopen_s(&f, "CONOUT$", "w", stdout);
}

// 统一调用约定为__fastcall,匹配IL2CPP成员函数的实际调用规则
using BuildFunc = bool(__fastcall*)(void* __this, void* buildingDef, const void* methodInfo);
BuildFunc originalBuild = nullptr;

bool __fastcall build_hook(void* __this, void* buildingDef, const void* methodInfo)
{
    printf("buildingDef is called!!!\n");
    // 必须调用原函数,保证游戏内部逻辑正常执行
    return originalBuild(__this, buildingDef, methodInfo);
}

void InitHook()
{
    MH_STATUS status = MH_Initialize();
    if (status != MH_OK)
    {
        printf("MH_Initialize failed: %d\n", status);
        return;
    }

    CreateConsole();
    printf("started\n");

    LPVOID targetAddr = reinterpret_cast<LPVOID>(GameAssembly + SetOffset);
    status = MH_CreateHook(targetAddr, &build_hook, reinterpret_cast<LPVOID*>(&originalBuild));
    if (status != MH_OK)
    {
        printf("MH_CreateHook failed: %d\n", status);
        return;
    }

    status = MH_EnableHook(targetAddr);
    if (status != MH_OK)
    {
        printf("MH_EnableHook failed: %d\n", status);
    }
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)InitHook, nullptr, 0, nullptr);
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

额外注意事项

  • 确认偏移量正确性:dump中的Address是3415072,转十六进制为0x341500,和你用的0x341C20存在差异,需确认是否是基址计算方式不同,确保GameAssembly + SetOffset指向正确的函数地址。
  • 控制台输出建议加\n,避免缓存导致输出不及时。
  • IL2CPP的MethodInfo参数尽量不要修改或忽略,部分函数依赖它做内部逻辑处理。

内容的提问来源于stack exchange,提问作者Blisa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 22:32:03