PE 檔案資源區 (`.rsrc` section) 採三層樹狀結構:**Type → Name/ID → Language**。本文依照 Microsoft PE/COFF 規格,詳細拆解各層資料結構,並給出對應 Delphi `packed record` 宣告,便於開發資源檢視/編輯工具。
// 對應 IMAGE_RESOURCE_DIRECTORY
PIMAGE_RESOURCE_DIRECTORY = ^IMAGE_RESOURCE_DIRECTORY;
IMAGE_RESOURCE_DIRECTORY = packed record
Characteristics: DWORD; // 保留,通常 0
TimeDateStamp: DWORD; // 資源建立時間戳
MajorVersion: WORD; // 主版本
MinorVersion: WORD; // 次版本
NumberOfNamedEntries: WORD; // 以字串命名的項目數
NumberOfIdEntries: WORD; // 以數字 ID 的項目數
// 緊接著 NumberOfNamedEntries + NumberOfIdEntries 個 IMAGE_RESOURCE_DIRECTORY_ENTRY
end;
// 對應 IMAGE_RESOURCE_DIRECTORY_ENTRY
PIMAGE_RESOURCE_DIRECTORY_ENTRY = ^IMAGE_RESOURCE_DIRECTORY_ENTRY;
IMAGE_RESOURCE_DIRECTORY_ENTRY = packed record
// Name/ID 聯合體:高位元判斷類型
NameOrId: DWORD; // 高位=1 → Name (RVA);高位=0 → ID (低 16 位)
// Offset/Subdirectory 聯合體
OffsetToData: DWORD; // 高位=1 → 子目錄 RVA (Mask $7FFFFFFF);高位=0 → Data Entry RVA
end;
輔助函數:
const
IMAGE_RESOURCE_NAME_IS_STRING = $80000000;
IMAGE_RESOURCE_DATA_IS_DIRECTORY = $80000000;
IMAGE_OFFSET_STRIP_HIGH = $7FFFFFFF;
function IsStringName(Entry: IMAGE_RESOURCE_DIRECTORY_ENTRY): Boolean;
begin
Result := (Entry.NameOrId and IMAGE_RESOURCE_NAME_IS_STRING) <> 0;
end;
function GetId(Entry: IMAGE_RESOURCE_DIRECTORY_ENTRY): Word;
begin
Result := Entry.NameOrId and $FFFF;
end;
function GetNameRva(Entry: IMAGE_RESOURCE_DIRECTORY_ENTRY): DWORD;
begin
Result := Entry.NameOrId and IMAGE_OFFSET_STRIP_HIGH;
end;
function IsSubdirectory(Entry: IMAGE_RESOURCE_DIRECTORY_ENTRY): Boolean;
begin
Result := (Entry.OffsetToData and IMAGE_RESOURCE_DATA_IS_DIRECTORY) <> 0;
end;
function GetDataRvaOrSubdirRva(Entry: IMAGE_RESOURCE_DIRECTORY_ENTRY): DWORD;
begin
Result := Entry.OffsetToData and IMAGE_OFFSET_STRIP_HIGH;
end;
| ID | 類型 |
|---|---|
| 1 | RT_CURSOR |
| 2 | RT_BITMAP |
| 3 | RT_ICON |
| 4 | RT_MENU |
| 5 | RT_DIALOG |
| 6 | RT_STRING |
| 7 | RT_FONTDIR |
| 8 | RT_FONT |
| 9 | RT_ACCELERATOR |
| 10 | RT_RCDATA |
| 11 | RT_MESSAGETABLE |
| 12 | RT_GROUP_CURSOR |
| 14 | RT_GROUP_ICON |
| 16 | RT_VERSION |
// 對應 IMAGE_RESOURCE_DATA_ENTRY
PIMAGE_RESOURCE_DATA_ENTRY = ^IMAGE_RESOURCE_DATA_ENTRY;
IMAGE_RESOURCE_DATA_ENTRY = packed record
OffsetToData: DWORD; // 資料 RVA (相對 ImageBase)
Size: DWORD; // 資料大小 (位元組)
CodePage: DWORD; // 碼頁 (通常 0 或 1252)
Reserved: DWORD; // 保留,必須 0
end;
// 緊接在 Directory Entry 之後 (RVA 指向此處)
PIMAGE_RESOURCE_DIR_STRING_U = ^IMAGE_RESOURCE_DIR_STRING_U;
IMAGE_RESOURCE_DIR_STRING_U = packed record
Length: WORD; // 字元數 (不含結束符)
NameString: array[0..0] of WideChar; // Unicode 字串
end;
procedure EnumResources(DirRVA: DWORD; Level: Integer);
var
Dir: PIMAGE_RESOURCE_DIRECTORY;
Entry: PIMAGE_RESOURCE_DIRECTORY_ENTRY;
I: Integer;
Name: string;
begin
Dir := PIMAGE_RESOURCE_DIRECTORY(ImageBase + DirRVA);
Entry := PIMAGE_RESOURCE_DIRECTORY_ENTRY(Dir + 1); // 緊接在結構後
for I := 0 to Dir.NumberOfNamedEntries + Dir.NumberOfIdEntries - 1 do
begin
if IsStringName(Entry^) then
Name := WideCharToString(PWideChar(ImageBase + GetNameRva(Entry^)) + 2) // 跳過 Length WORD
else
Name := '#' + IntToStr(GetId(Entry^));
if IsSubdirectory(Entry^) then
EnumResources(GetDataRvaOrSubdirRva(Entry^), Level + 1)
else
begin
// 到達 Data Entry
DataEntry := PIMAGE_RESOURCE_DATA_ENTRY(ImageBase + GetDataRvaOrSubdirRva(Entry^));
// DataEntry.OffsetToData + ImageBase = 實際資料位址
// DataEntry.Size = 大小
ProcessResource(Name, Level, DataEntry);
end;
Inc(Entry);
end;
end;
*.res) 編譯後即嵌入此結構,可用 FindResource/LoadResource/LockResource API 存取,無需手工解析。