-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Add info about string-table resources #2110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: docs
Are you sure you want to change the base?
Conversation
Added info on string tables and how to enumerate them
Polished it a bit
@ptorr-msft : Thanks for your contribution! The author(s) and reviewer(s) have been notified to review your proposed change. |
@oldnewthing FYI, this is based on info from your blog. This had a (now-archived) KB article written about it and you wrote a nice blog post about it, but the actual Win32 API docs are quite useless. This is my attempt to fix it. (Assuming it is approved, I will submit another PR for the actual Enum* APIs to refer to this article for info about enumerating strings.) |
### String Table Resources | ||
|
||
When enumerating String Table resources (type **RT_STRING**) with functions such as **EnumResourceNamesW**, the system doesn't | ||
enumerate each individual string resource ID; instead it enumerates blocks of resources. You must inpsect these blocks to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enumerate each individual string resource ID; instead it enumerates blocks of resources. You must inpsect these blocks to | |
enumerate each individual string resource ID; instead it enumerates blocks of resources. You must inspect these blocks to |
|
||
When enumerating String Table resources (type **RT_STRING**) with functions such as **EnumResourceNamesW**, the system doesn't | ||
enumerate each individual string resource ID; instead it enumerates blocks of resources. You must inpsect these blocks to | ||
determine which string resource ID(s) they actually contain. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This phrasing makes it sound like EnumResourceNamesW contains a special case for strings. But really, there is no special case. EnumResourceNamesW enumerates the resources. It's just that strings do not map to resources 1:1.
I think we can delete this paragraph and instead augment the second paragraph.
'
Strings provided in the resource string table are packaged into blocks of 16 strings per block. Each string in the block takes the form of a 16-bit integer representing the length of the string, followed by that number of 16-bit Unicode (UTF-16LE) code units. Resource ID 1 is a block that holds strings 0 to 15, resource ID 2 is a block that holds strings 16 to 31, and in general, resource ID N is a block that holds strings (N-1) × 16 to (N-1)×16 + 15. Missing string table IDs are treated as empty strings, and any block that consists entirely of empty strings is omitted. The
EnumResourceNamesW
function enumerates the resource blocks, not the individual strings.
For example, the following string table
STRINGTABLE BEGIN 1 "One" 5 "Five" 100 "Hundred" STRINGTABLE END
is encoded as two string blocks.
Block 1:
0000 | Length of string 0 (empty string) 0003 | Length of string 1 (three characters) 004F | "O" 006E | "n" 0065 | "e" 0003 | Length of string 2 (three characters) 0054 | "T" 0077 | "w" 006F | "o" 0000 | Length of string 3 (empty string) 0000 | Length of string 4 (empty string) 0004 | Length of string 5 (four characters) 0046 | "F" 0069 | "i" 0076 | "v" 0065 | "e" 0000 | Length of string 6 (empty string) 0000 | Length of string 7 (empty string) 0000 | Length of string 8 (empty string) 0000 | Length of string 9 (empty string) 0000 | Length of string 10 (empty string) 0000 | Length of string 11 (empty string) 0000 | Length of string 12 (empty string) 0000 | Length of string 13 (empty string) 0000 | Length of string 14 (empty string) 0000 | Length of string 15 (empty string)
Block 7:
0000 | Length of string 96 (empty string) 0000 | Length of string 97 (empty string) 0000 | Length of string 98 (empty string) 0000 | Length of string 99 (empty string) 0007 | Length of string 100 (7 characters) 0048 | "H" 0075 | "u" 006E | "n" 0064 | "d" 0072 | "r" 0065 | "e" 0064 | "d" 0000 | Length of string 101 (empty string) 0000 | Length of string 102 (empty string) 0000 | Length of string 103 (empty string) 0000 | Length of string 104 (empty string) 0000 | Length of string 105 (empty string) 0000 | Length of string 106 (empty string) 0000 | Length of string 107 (empty string) 0000 | Length of string 108 (empty string) 0000 | Length of string 109 (empty string) 0000 | Length of string 110 (empty string) 0000 | Length of string 111 (empty string)
Note that the strings are not null-terminated.
std::wcout << L"Explorer.exe contains " << nStrings << L" strings (in " << nStringBlocks | ||
<< L" blocks)." << std::endl; | ||
} | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would accept a path as a parameter, rather than hard-coding Explorer.
Maybe
BOOL PrintStringsInBlock(HMODULE module, PCWSTR type, PCWSTR name, LONG_PTR data)
{
// Load the block and obtain a pointer to it.
auto ptr = (wchar_t*)LockResource(LoadResource(hModule, FindResource(module, name, type)));
if (ptr)
{
// Print all the non-empty strings in the block.
unsigned int baseId = (PtrToInt(name) - 1) * 16;
for (unsigned int stringIndex = 0; stringIndex < 16; ++stringIndex)
{
wchar_t size = *ptr;
if (size > 0)
{
printf("ID %u: ", baseId + stringIndex);
for (unsigned int charIndex = 0; charIndex < size; ++charIndex)
{
fputwc(ptr[charIndex + 1], stdout); // requires stdout to be set to Unicode
}
printf("\n");
}
// Skip to next potential string in the block
ptr += size + 1;
}
}
return TRUE;
}
void PrintStringsInModule(PCWSTR modulePath)
{
HMODULE module = LoadLibraryExW(modulePath, nullptr, LOAD_LIBRARY_AS_DATAFILE);
if (!module) return;
EnumResourceNamesW(module, RT_STRING, PrintStringsInBlock, 0);
FreeLibrary(module);
]
|
||
## Remarks | ||
|
||
### String Table Resources |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this content belongs in a separate page entirely, since it applies to multiple APIs (FindResourceW, EnumResourceNamesW, UpdateResourceW, plus the A versions).
Added description of string-table resources and a snippet of code to help enumerate actual strings. Thanks to @oldnewthing's blog for the details.