Resource.rc — 資源嵌入機制

問題:WZ 檔案要怎麼跟著 DLL 走?

Phase 7 前三篇介紹了 config.iniINIReader.h 和 WZ 資源整合原理。但有個關鍵問題沒解答:

config.ini、EzorsiaV2_UI.wz、MapleEzorsiaV2wzfiles.img 是怎麼打包進 DLL 的?

答案在 Resource.rcresource.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.wzEzorsia 客製化 UI 圖形資源
IDR_RCDATA3 (103)MapleEzorsiaV2wzfiles.imgWZ 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 中添加新資源

  1. 右鍵點擊專案 → AddResource…
  2. 選擇 Import,選取任意二進位檔案
  3. 型別選 “RCDATA”
  4. 右鍵新資源 → Properties → 修改 ID(如 IDR_RCDATA4
  5. resource.h 新增對應 #define

相關筆記

  • 01 — 讀取後的設定解析
  • 03 — WZ 資料的使用方式
  • 03 — 資源在初始化時讀取