Resource.rc — 資源嵌入機制
問題:WZ 檔案要怎麼跟著 DLL 走?
Phase 7 前三篇介紹了 config.ini、INIReader.h 和 WZ 資源整合原理。但有個關鍵問題沒解答:
config.ini、EzorsiaV2_UI.wz、MapleEzorsiaV2wzfiles.img 是怎麼打包進 DLL 的?
答案在 Resource.rc 和 resource.h——Windows 的資源嵌入(Resource Embedding)機制。
什麼是 Windows 資源(Resource)?
Windows PE 格式(.exe/.dll)有一個專門的資源節(.rsrc section),可以嵌入任意二進位資料:圖示、字串、對話框、或自訂的二進位內容(RCDATA)。
這些資料在執行時透過 FindResource() + LoadResource() 從記憶體中讀取,完全不需要外部檔案。
resource.h — 資源 ID 定義
// resource.h(由 Visual Studio 自動產生)
#define IDR_RCDATA1 101 // config.ini
#define IDR_RCDATA2 102 // EzorsiaV2_UI.wz
#define IDR_RCDATA3 103 // MapleEzorsiaV2wzfiles.img這些整數常數是每個嵌入資源的唯一識別碼。resource.h 讓 C++ 程式碼可以用有意義的名稱(而非硬編碼 101)引用資源。
Resource.rc — 嵌入宣告
IDR_RCDATA1 RCDATA "config.ini"
IDR_RCDATA2 RCDATA "EzorsiaV2_UI.wz"
IDR_RCDATA3 RCDATA "MapleEzorsiaV2wzfiles.img"每行的格式:<ID> <型別> <檔案路徑>
- ID:對應
resource.h中定義的數字(101/102/103) - RCDATA:Windows 資源型別,代表「原始二進位資料」
- 檔案路徑:編譯時相對於
.rc檔案的位置(ezorsia/目錄下)
編譯時行為
編譯時,Resource Compiler(
rc.exe)讀取.rc檔案,把三個檔案的內容嵌入到.dll的.rsrc節中。發布 DLL 時不需要附帶這三個檔案。
三個嵌入資源的用途
| 資源 ID | 檔案 | 用途 |
|---|---|---|
IDR_RCDATA1 (101) | config.ini | 預設設定值(解析度、字體等) |
IDR_RCDATA2 (102) | EzorsiaV2_UI.wz | Ezorsia 客製化 UI 圖形資源 |
IDR_RCDATA3 (103) | MapleEzorsiaV2wzfiles.img | WZ IMG 索引檔 |
執行時讀取嵌入資源
在 MainMain.cpp(或任何初始化程式碼)中,透過以下方式從 DLL 自身讀取嵌入資料:
#include "resource.h"
// 從 DLL 自身讀取嵌入的 config.ini
HMODULE hModule = GetModuleHandle(NULL); // 取得目前 DLL 的模組控制代碼
HRSRC hRes = FindResource(hModule, MAKEINTRESOURCE(IDR_RCDATA1), RT_RCDATA);
HGLOBAL hGlobal = LoadResource(hModule, hRes);
LPVOID pData = LockResource(hGlobal);
DWORD dwSize = SizeofResource(hModule, hRes);
// pData 指向 config.ini 的原始位元組,dwSize 是檔案大小
// 可以傳給 INIReader 解析讀取 WZ 資源的簡化版本
LPVOID LoadEmbeddedResource(int nResourceID, DWORD* pdwSize) {
HMODULE hMod = GetModuleHandle(NULL);
HRSRC hRes = FindResource(hMod, MAKEINTRESOURCE(nResourceID), RT_RCDATA);
if (!hRes) return nullptr;
HGLOBAL hGlobal = LoadResource(hMod, hRes);
*pdwSize = SizeofResource(hMod, hRes);
return LockResource(hGlobal);
}
// 使用
DWORD dwWzSize;
LPVOID pWzData = LoadEmbeddedResource(IDR_RCDATA2, &dwWzSize);
// 用 pWzData 初始化 WZ 解析器LockResource 不需要 Unlock
LockResource在現代 Windows 只是回傳指標(不真的鎖定)。資源記憶體是 DLL 影像的一部分,程式結束時自動釋放,不需要手動FreeResource。
視覺化:DLL 檔案結構
dinput8.dll(編譯後)
├── .text ← 程式碼(Hook、ZAllocEx、Client 等)
├── .data ← 全域變數
├── .rdata ← 唯讀資料(字串常數等)
└── .rsrc ← 資源節
├── RCDATA
│ ├── 101: [config.ini 的所有位元組] ← IDR_RCDATA1
│ ├── 102: [EzorsiaV2_UI.wz 的所有位元組] ← IDR_RCDATA2
│ └── 103: [MapleEzorsiaV2wzfiles.img] ← IDR_RCDATA3
└── VERSION (若有版本資訊)
為什麼選擇嵌入而不是外部檔案?
| 嵌入(RCDATA) | 外部檔案 | |
|---|---|---|
| 發布便利性 | 只需一個 .dll | 需附帶多個檔案 |
| 防竄改 | 資料在 DLL 保護下 | 使用者可直接修改 |
| 更新成本 | 需重新編譯 DLL | 直接替換檔案 |
| 開發除錯 | 不方便快速測試 | 直接編輯後重啟 |
Ezorsia 選擇嵌入的主因:最終用戶只需要把一個 dinput8.dll 放進 MapleStory 目錄,不需要理解任何其他設定檔案的存放位置。
如何在 Visual Studio 中添加新資源
- 右鍵點擊專案 → Add → Resource…
- 選擇 Import,選取任意二進位檔案
- 型別選 “RCDATA”
- 右鍵新資源 → Properties → 修改 ID(如
IDR_RCDATA4) - 在
resource.h新增對應#define