鉤子指南
介紹
Hook 是一種在運行時修改函數行為的技術。通常用於在不修改源代碼的情況下修改函數行為。
wikipedia 關於 Hook 的解釋:Hooking
在 LeviLamina 中,我們提供了封裝好的Hook API,使得你可以快速便捷地對 Minecraft 基岩版中的遊戲函數進行行為修改。
Hook的類型
在ll/api/memory/Hook.h 中,我們定義了以下幾種Hook 宏:
#define LL_TYPE_STATIC_HOOK(DEF_TYPE, PRIORITY, TYPE, IDENTIFIER, RET_TYPE, ...)
#define LL_STATIC_HOOK(DEF_TYPE, PRIORITY, IDENTIFIER, RET_TYPE, ...)
#define LL_TYPE_INSTANCE_HOOK(DEF_TYPE, PRIORITY, TYPE, IDENTIFIER, RET_TYPE, ...)
#define LL_INSTANCE_HOOK(DEF_TYPE, PRIORITY, IDENTIFIER, RET_TYPE, ...)
#define LL_AUTO_TYPE_STATIC_HOOK(DEF_TYPE, PRIORITY, TYPE, IDENTIFIER, RET_TYPE, ...)
#define LL_AUTO_STATIC_HOOK(DEF_TYPE, PRIORITY, IDENTIFIER, RET_TYPE, ...)
#define LL_AUTO_TYPE_INSTANCE_HOOK(DEF_TYPE, PRIORITY, TYPE, IDENTIFIER, RET_TYPE, ...)
#define LL_AUTO_INSTANCE_HOOK(DEF_TYPE, PRIORITY, IDENTIFIER, RET_TYPE, ...)其中,AUTO 標註的 Hook 會自動註冊,即運行時自動調用hook()函數進行註冊。 TYPE 標註的 Hook 會給你的 DEF_TYPE 繼承到你指定的類型。
STATIC_HOOK
針對靜態函數的Hook。
INSTANCE_HOOK
針對成員函數的Hook。
Hook的參數解釋
DEF_TYPE:你給你這個 Hook 的起的類型名。
PRIORITY:Hook的優先級,例如ll::memory::HookPriority::Normal
Note
一般不是特殊需求,我們不推薦過高的優先級,Normal 即可。
TYPE:你的定義的 DEF_TYPE 繼承到的類型。
IDENTIFIER:Hook 的查詢函數使用的標識符,可以為:函數修飾後名稱,函數的字節碼,函數定義。
RET_TYPE:Hook 的函數的返回值類型。
...:Hook 的函數的參數列表。
Hook的使用
你可以查閱 LeviLamina 提供的Fake Header來獲取你想要 Hook 的函數的定義。
簡單的Hook示例
#include "ll/api/memory/Hook.h"
#include "ll/api/io/LoggerRegistry.h"
#include "mc/server/DedicatedServer.h"
auto dedicatedServerLogger = ll::io::LoggerRegistry::getInstance().getOrCreate("DedicatedServer");
LL_AUTO_TYPE_INSTANCE_HOOK(
DedicatedServerCtorHook,
ll::memory::HookPriority::Normal,
DedicatedServer,
&DedicatedServer::$ctor,
void*
) {
dedicatedServerLogger->info("DedicatedServer::DedicatedServer");
return origin();
}這段代碼會 Hook DedicatedServer 的構造函數,並在構造函數被調用時打印一條日誌。
解析:
根據查閱 LeviLamina 提供的DedicatedServer.h,我們可以得到 DedicatedServer 的構造函數的thunk函數。
又由於構造函數是類的成員函數,所以我們使用了INSTANCE_HOOK類型的Hook,這使我們不需要填寫由編譯器產生的第一個this指针參數。
又由於我們希望在模組被加載的時候自動註冊,所以我們使用了 AUTO標註的 Hook。
最後由於方便,我們使用了 TYPE標註的 Hook,使得我們可以直接在函數體內調用 DedicatedServer 類型下的函數,雖然在這段代碼中我們並沒有使用到,但是這是一個好習慣。
Hook的註冊和卸載
註冊
針對非自動註冊的 Hook,你需要在你模組需要註冊 Hook 的時機調用hook()函數進行註冊。
卸載
所有的 Hook 都會在 Minecraft 卸載時自動卸載,你也可以在你模組需要卸載 Hook 的時機調用unhook()函數進行卸載。
寫在最後
Hook 是一種非常強大的技術,但是也是一把雙刃劍,如果使用不當,可能會導致Minecraft本體崩潰,或者模組崩潰,嚴重的甚至會對存檔產生影響。
所以請在使用 Hook 的時候一定要謹慎,仔細檢查你的代碼,避免出現不必要的錯誤。