上一篇認識了 DLL 劫持的原理和開發環境。這篇進入 MapleEzorsia-v2 的專案本身:.sln/.vcxproj 各自的角色、每個檔案的職責,以及 DLL 真正的入口點 dllmain.cpp 怎麼運作,最後完整走一次編譯到放進遊戲目錄的流程。

.sln 與 .vcxproj:工廠大門與建造藍圖

.sln(Solution)是 Visual Studio 的方案檔,本身不含任何程式碼,只是一份清單,告訴 Visual Studio「這個方案底下有哪些子專案」。永遠雙擊 .sln 開啟專案,不要直接點 .vcxproj,才能讓環境設定一起載入。

.vcxproj 才是真正的建造藍圖,記錄了要編譯哪些 .cpp、要連結哪些函式庫、輸出檔案叫什麼名字放哪裡。MapleEzorsia-v2 的關鍵設定:

設定說明
ConfigurationTypeDynamicLibrary這是DLL專案,不是exe
TargetNamedinput8輸出檔名叫dinput8.dll,不是ezorsia.dll
OutDirout\Release\編譯完成的位置
LanguageStandardstdcpp17使用C++17標準

專案名稱叫 ezorsia,但輸出檔名故意設定成 dinput8.dll——因為 MapleStory 啟動時會自動讀取遊戲資料夾裡的 dinput8.dll,這正是上一篇介紹的 DLL Proxy 劫持手法。

連結的函式庫:Winmm.lib(多媒體/計時器)、Ws2_32.lib(Socket網路連線)、detours.lib(Hook核心,攔截並替換函式)。

各檔案職責一覽

檔案職責
dllmain.cppDLL入口點,Windows載入DLL時第一個執行的地方
dinput8.cppProxy轉發,把DirectInput呼叫轉給真正的系統DLL
MainMain.cpp初始化主體,讀取config.ini、決定開啟哪些功能
Client.cpp客戶端修改核心,記憶體修改/封包攔截/功能注入都在這
Memory.cpp記憶體讀寫工具
ZAllocEx.cpp自訂記憶體分配器,讓程式碼相容MapleStory的記憶體管理方式

另外還有一個 MapleClientCollectionTypes\ 子資料夾,裡面是社群專案 MapleClientEditTemplate 提供的型別定義(ZArrayZListZMapZXStringZRef 等,命名都以 Z 開頭,對應 MapleStory 引擎自研的 ZLib 架構)。這些是「地基」,除非要追查特定內部資料結構,否則不需要修改這個資料夾。

dllmain.cpp:DLL 的入口點

每個 Windows DLL 都必須有一個 DllMain 函式,Windows 在載入或卸載 DLL 時會自動呼叫它:

cpp
📋 複製
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:   // 有人把我們載入了
        ...
    case DLL_PROCESS_DETACH:   // 有人要卸載我們了
        ...
    }
    return TRUE;
}

為什麼不能在 DllMain 裡做太多事

Windows 呼叫 DllMain 時內部有一把 Loader Lock(載入器鎖):不能在裡面載入其他DLL、不能呼叫大多數Win32 API、不能做複雜初始化,違反會導致死鎖。所以 DllMain 只做兩件事:安裝幾個最早期的 Windows API Hook,然後開一條新執行緒讓真正的初始化工作在裡面進行:

cpp
📋 複製
case DLL_PROCESS_ATTACH:
{
    MainMain::mainTHread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, GetCurrentThreadId());
    Hook_CreateMutexA(true);
    Hook_WSPStartup(true);
    // ...
    DisableThreadLibraryCalls(hModule);
    CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&MainProc, NULL, 0, 0);
    break;
}

這裡安裝的 Windows API Hook 攔截的是系統呼叫本身,目的是在遊戲真正啟動前就改變行為:

Hook攔截的API目的
Hook_CreateMutexACreateMutexA讓遊戲可以多開視窗(原版只允許一個)
Hook_WSPStartupWSPStartup讓遊戲連到自訂IP(私服)
Hook_CreateWindowExACreateWindowExA替遊戲視窗加上最小化按鈕
Hook_FindFirstFileAFindFirstFileA阻止遊戲偵測到假DLL
Hook_GetACPGetACP處理語系問題,避免文字亂碼

MainProc / MainFunc:為什麼要分兩批 Hook

新執行緒執行 MainProc → MainMain::CreateInstance(MainFunc)MainMain 負責讀取設定檔,並等待遊戲的 Themida 保護層解壓縮完畢才呼叫 MainFunc——因為遊戲內部函式的記憶體位址在解壓縮前是無效的,太早安裝 Hook 會打在錯誤位置導致崩潰。

MainFunc 才是真正安裝遊戲內部函式 Hook 的地方(以記憶體位址識別,例如 sub_9F5239):

代號對應函式作用
sub_494CA3CClientSocket::Connect讓連線指向私服
sub_9F4E54Crc32檢查器關閉完整性驗證
sub_9F5239CWvsApp::SetUp攔截遊戲初始化設定
sub_9F5C50CWvsApp::Run攔截遊戲主迴圈
sub_9F7964IWzFileSystem::Init攔截資源檔案系統初始化

所有 Hook 裝完後,最後才呼叫 Client::UpdateResolution() 套用解析度設定,並呼叫 dinput8::CreateHook()(上一篇介紹過的Proxy轉發)初始化。順序很重要:如果Proxy先啟動,遊戲輸入系統開始運作,但Hook還沒裝完,會造成競爭條件或記憶體存取衝突。

DLL_PROCESS_DETACH(遊戲關閉時)則手動呼叫 MainMain 的解構子,清理Socket連線和記憶體,避免殘留指標造成問題。

完整編譯流程

1. 開啟並確認設定

雙擊 Ezorsia V2.sln 開啟專案(第一次載入可能要等 30 秒到 1 分鐘),確認頂端工具列兩個下拉選單:Configuration 選 Release,Platform 選 x86。Platform 選錯成 x64 的話,編譯出來的 DLL 放進遊戲目錄會完全沒有反應,不會有任何錯誤提示。

2. 建置

Ctrl+Shift+B 或選單 Build → Build Solution,Output 視窗最後出現:

text
📋 複製
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

3. 找到輸出檔案並複製到遊戲目錄

編譯成功後,out\Release\ 資料夾會出現三個檔案:

text
📋 複製
out\Release\
├── dinput8.dll       ← 主要DLL
├── config.ini        ← 設定檔(自動複製)
└── EzorsiaV2_UI.wz   ← UI資源(自動複製)

把這三個檔案全部複製進 MapleStory 遊戲目錄——dinput8.dll 啟動時會讀取另外兩個檔案,缺任何一個都會導致閃退或功能失效。啟動遊戲後觀察畫面是否變成HD解析度,有變化就代表DLL已成功載入。

常見編譯錯誤

錯誤訊息原因解法
LNK1104: cannot open file 'detours.lib'detours\ 資料夾沒跟 .sln 放同一層確認路徑存在,必要時重新完整下載倉庫
C1010: unexpected end of file while looking for precompiled headerstdafx.cpp 沒設定為建立PCH右鍵stdafx.cpp → Properties → C/C++ → Precompiled Headers → 設為 Create (/Yc)
C2065: 'xxx': undeclared identifier某個.h標頭檔遺失或Include路徑錯誤確認所有原始碼檔案都完整下載
編譯成功但放進遊戲後沒效果Platform選了x64改回x86重新編譯
LNK2019: unresolved external symbol函式庫連結問題確認vcxproj未被修改,或重新下載原始碼
開啟.sln要求「Retarget」Toolset版本不符(需要v143)安裝Visual Studio 2022或v143 Build Tools

下一篇進入記憶體操作與 Hook 技術:了解 Memory.cppAddyLocations.h 這些工具怎麼讀寫遊戲記憶體,以及 Detours 函式庫實際攔截函式呼叫的原理。