Introduction
Who is this tip for?
Before you go on reading the tip, I would like to make it clear that the content of this tip has very limited use only for those who are digging low level in the binary resource tree of PE modules. This tip assumes that you are already familiar with the FindResource()
WinAPI function, and that you are learning the PE resource format from another tutorial you found on the internet. I don't have the motivation to write the 1000th PE resource tutorial but I think this tip can still add something complementary to those resource tutorials. If you don't feel that this tip would serve you well, then please don't continue reading on and you spare some free time for yourself to have a coffee+donut combo.
So what's the thing this tip gives?
This tip gives you some source code that searches in the binary resource tree. In my opinion, the best example sources are very short and contain only the essence. This is why this source can come in handy for you. It's much easier to learn from this than taking a look at the sources of a resource editor program with a thousand source files. Another good reason to choose this source to analyze is that these sources emulate the work of the FindResource()
WinAPI function so you might know what to expect as a result when you run it!
About the Code
Limitations/Advantages of this code compared to its WinAPI sibling
This code served me well in practice in not too ordinary situations but it is now here for learning purposes and not for use in production code - but who knows - maybe it comes handy for someone. Note that the code works only on Win32, but maybe with minor modifications it could operate in x64 environment as well. Maybe the only thing that needs to be changed is the check for IMAGE_NT_OPTIONAL_HDR64_MAGIC
in the PE header, I don't know if there are any changes to the resource format since then because I'm not digging the binaries any more. This FindResource()
and LoadString()
has more detailed error detection (language) than the Win32 version, you can find more about this in the sources posted.
Usage of the code
I assume that you used the windows version of these functions so I will be brief: First call MyFindResource()
and then with the return value you can use MyLoadResource()
and MySizeOfResource()
like you use these methods of the WinAPI. MyLoadResource()
and MySizeOfResource()
return the pointer to the resource and the size of the resource that you can use for example with CreateDialogIndirect()
(you have to cast the pointer) in case of a dialog resource, or maybe you can put your binary right into the resource part of your module for your own purposes. I myself hate string resources and use text files to do localization work but many programs use string resources for that by copy pasting the tutorials from the internet. :P Since string resources have a special format I wrote a codepiece that handles those and posted it up if you are interested in it. The code was used with Visual C++ 6 but it should work with newer compilers at most with minor modifications. I added the C tag too to this article despite the fact that the sources are .cpp files because it could be ported to C easily without C++ knowledge. The sources will work with both Unicode and ANSI character set setting.
You can find some weird coding conventions and Hungarian notation in this antique piece of code that I don't anymore follow.
Mini Start Guide to Learn about PE Resources
If you read along this tip and have no clue about the resource format inside PE files, but you are hell curious then I give you a small startup guide. Again, this isn't going to be the 1000th resource format tutorial, I just point you the direction!
What is this binary resource stuff?
If you already worked on a windowed win32 application in Visual Studio, then you must be familiar with the .rc files and maybe with the .res files that are generated from .rc files. The .rc file in your project is a text file that defines different kind of resources: cursors, icons, dialogs, etc... Visual Studio has very nice visual editors for each resource types (icon, dialog, stringtable, etc...) so might never edit .rc files manually. There are several resource types and the number of types is growing as time passes. This textual .rc file can reference other files (cursors, icons, binary files), but it contains complete description in text about some resource formats (dialogs, versioninfo). A compiler called rc.exe compiles this .rc file into a binary file that is the .res file. This res file contains everything, even external files referenced by the .rc (cursors, icons) are already compiled into it. This binary .res file is interesting because it is basically attached (usually to the end) to your .exe or .dll file by placing each resource in a search tree (by the linker), and the header of your .exe or .dll has a pointer to the beginning of this data.
How is this binary resource accessed by windows functions?
When your .exe or .dll is loaded, this attached binary resource data is also loaded into memory and some windows functions (like CreateDialog
, LoadCursor
, LoadIcon
) expect you to specify a HMODULE
/HINSTANCE
parameter because the HMODULE
/HINSTANCE
handle values are really pointers to the loaded header of your .exe/.dll files in memory so from this pointer it can look up the address of the resource binary by accessing the headers of your .exe/.dll. In my example program, you can check out the ResDirVAFromHMODULE()
function that converts a HMODULE
handle into a pointer that points to the binary resource in the loaded module.
OK, I have a pointer to this binary resource so what to do?
At the beginning of that loaded binary resource, you have a tree that has 3 levels. (My source code contains a brief tutorial about the structure of this.) On these levels, you search for your resource by type (level 1), name/id (level 2), and language (level 3). (On each level, the sibling nodes are sorted so you can perform binary search.) You must be familiar with these parameters (type, name, language) if you ever looked into .rc files. After completing your search for your resource on all three levels, you get a pointer to the actual resource data and you get the size of the data. It depends on the type of the resource how to interpret that data, it's different for every resource type (cursor, icon, dialog, versioninfo, ...). In my sources, you have the code that searches the 3 levels of the resource tree (FindResource
), and you have an example to handle one specific type of resource (LoadString
: string resources).
Source Files
ResCommon.h:
#ifndef ResCommon_h
#define ResCommon_h
#if defined(UNICODE) && !defined(_UNICODE)
# define _UNICODE
#endif
#if !defined(UNICODE) && defined(_UNICODE)
# undef _UNICODE
#endif
#include <windows.h>
#include <winnt.h>
#include <tchar.h>
#define IS_RES_ID(lpName) (!((DWORD)(lpName) & 0xFFFF0000))
#define RAISE_EXCEPTION_RESULT(result)
(RaiseException (0xEDCB0000 | ((result) & 0xFFFF), 0, 0, NULL))
#define RAISE_EXCEPTION() (RaiseException (0xE0000000, 0, 0, NULL))
#define EXCEPT_EVAL ( error_code =
((GetExceptionCode () & 0xFFFF0000) == 0xEDCB0000) \
? (WORD)(GetExceptionCode () &
0xFFFF) : error_code, EXCEPTION_EXECUTE_HANDLER)
DWORD ResDirVAFromHMODULE (HMODULE ImageBase, WORD *lpwErrorCode = NULL);
#define RES_OK 0x0000
#define RES_MEMORY_ERR 0x0001
#define RES_INVALID_RESNAME 0x0002
#define RES_TYPE_NOT_FOUND 0x0003
#define RES_NOT_FOUND 0x0004
#define RES_LANG_NOT_FOUND 0x0005
#define RES_NO_RESOURCE 0x0006
#define RES_INVALID_IMAGEBASE 0xFFFE
#define RES_UNKNOWN_ERR 0xFFFF
#endif
ResCommon.cpp:
#include "ResCommon.h"
DWORD ResDirVAFromHMODULE (HMODULE ImageBase, WORD *lpwErrorCode)
{
DWORD res;
WORD error_code = RES_INVALID_IMAGEBASE;
__try
{
if ( ((IMAGE_DOS_HEADER*)ImageBase)->e_magic != IMAGE_DOS_SIGNATURE)
RAISE_EXCEPTION_RESULT (RES_INVALID_IMAGEBASE);
IMAGE_NT_HEADERS *hdr = (IMAGE_NT_HEADERS*)((DWORD)ImageBase + ((IMAGE_DOS_HEADER*)ImageBase)->e_lfanew);
if (hdr->Signature != IMAGE_NT_SIGNATURE ||
hdr->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC)
RAISE_EXCEPTION_RESULT (RES_INVALID_IMAGEBASE);
if (hdr->OptionalHeader.NumberOfRvaAndSizes < (IMAGE_DIRECTORY_ENTRY_RESOURCE + 1) ||
!hdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress ||
hdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].Size < sizeof (IMAGE_RESOURCE_DIRECTORY))
RAISE_EXCEPTION_RESULT (RES_NO_RESOURCE);
res = (DWORD)ImageBase + hdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress;
error_code = RES_OK;
}
__except (EXCEPT_EVAL)
{
res = 0;
}
__try
{
if (lpwErrorCode) *lpwErrorCode = error_code;
}
__except (EXCEPTION_EXECUTE_HANDLER) {}
return res;
}
LoadString.h:
#ifndef LoadString_h
#define LoadString_h
#include "ResCommon.h"
#include "FindResource.h"
DWORD MyLoadString (
HMODULE ImageBase, DWORD dwResDirVA, UINT uID, LPTSTR lpBuffer, DWORD nBufferMax, WORD wLanguage = MAKELANGID (LANG_NEUTRAL,
SUBLANG_NEUTRAL), WORD *lpwErrorCode = NULL, WORD *lpwLanguageFound = NULL );
DWORD MyLoadString (
HMODULE ImageBase, UINT uID, LPTSTR lpBuffer, DWORD nBufferMax, WORD wLanguage = MAKELANGID (LANG_NEUTRAL,
SUBLANG_NEUTRAL), WORD *lpwErrorCode = NULL, WORD *lpwLanguageFound = NULL );
IMAGE_RESOURCE_DIR_STRING_U *InternalLoadString (
HMODULE ImageBase, DWORD dwResDirVA, UINT uID, WORD wLanguage = MAKELANGID (LANG_NEUTRAL,
SUBLANG_NEUTRAL), WORD *lpwErrorCode = NULL, WORD *lpwLanguageFound = NULL );
IMAGE_RESOURCE_DIR_STRING_U *InternalLoadString (
HMODULE ImageBase, UINT uID, WORD wLanguage = MAKELANGID (LANG_NEUTRAL,
SUBLANG_NEUTRAL), WORD *lpwErrorCode = NULL, WORD *lpwLanguageFound = NULL );
#endif
LoadString.cpp
#include "FindResource.h"
#include "LoadString.h"
DWORD MyLoadString (
HMODULE ImageBase, DWORD dwResDirVA, UINT uID, LPTSTR lpBuffer, DWORD nBufferMax, WORD wLanguage, WORD *lpwErrorCode, WORD *lpwLanguageFound )
{
IMAGE_RESOURCE_DIR_STRING_U *str;
DWORD len;
WORD error_code;
__try
{
str = InternalLoadString (
ImageBase,
dwResDirVA,
uID,
wLanguage,
&error_code,
lpwLanguageFound
);
if (!str) RAISE_EXCEPTION_RESULT (RES_NOT_FOUND);
error_code = RES_UNKNOWN_ERR;
len = str->Length;
if (nBufferMax && lpBuffer)
{
len = min (nBufferMax - 1, len);
#ifdef UNICODE
memcpy (
(void*)lpBuffer,
(void*)&str->NameString,
len * sizeof (WCHAR)
);
#else
len = WideCharToMultiByte (
CP_ACP,
0,
(LPCWSTR)&str->NameString,
len,
lpBuffer,
len,
NULL,
NULL
);
#endif
lpBuffer[len] = 0;
}
else if (nBufferMax && !lpBuffer)
{
len = (DWORD)str;
}
error_code = RES_OK;
}
__except (EXCEPT_EVAL)
{
len = 0;
}
__try
{
if (lpwErrorCode) *lpwErrorCode = error_code;
}
__except (EXCEPTION_EXECUTE_HANDLER) {}
return len;
}
DWORD MyLoadString (
HMODULE ImageBase, UINT uID, LPTSTR lpBuffer, DWORD nBufferMax, WORD wLanguage, WORD *lpwErrorCode, WORD *lpwLanguageFound )
{
DWORD dwResDirVA = ResDirVAFromHMODULE (ImageBase, lpwErrorCode);
if (!dwResDirVA) return 0;
return MyLoadString (
ImageBase,
dwResDirVA,
uID,
lpBuffer,
nBufferMax,
wLanguage,
lpwErrorCode,
lpwLanguageFound
);
}
IMAGE_RESOURCE_DIR_STRING_U *InternalLoadString (
HMODULE ImageBase, DWORD dwResDirVA, UINT uID, WORD wLanguage, WORD *lpwErrorCode, WORD *lpwLanguageFound )
{
IMAGE_RESOURCE_DIR_STRING_U *result;
IMAGE_RESOURCE_DATA_ENTRY *data;
WORD error_code = RES_UNKNOWN_ERR;
__try
{
data = InternalFindResourceEx (
dwResDirVA,
(LPCTSTR)((uID >> 4) + 1),
RT_STRING,
wLanguage,
&error_code,
lpwLanguageFound
);
if (!data) RAISE_EXCEPTION ();
error_code = RES_UNKNOWN_ERR;
uID &= 0xf; result = (IMAGE_RESOURCE_DIR_STRING_U*)MyLoadResource (ImageBase, data);
for (; uID; uID--)
result = (IMAGE_RESOURCE_DIR_STRING_U*)((DWORD)result
+ (result->Length + 1) * sizeof (WCHAR));
if (!result->Length) RAISE_EXCEPTION_RESULT (RES_NOT_FOUND);
error_code = RES_OK;
}
__except (EXCEPT_EVAL)
{
result = NULL;
}
__try
{
if (lpwErrorCode) *lpwErrorCode = error_code;
}
__except (EXCEPTION_EXECUTE_HANDLER) {}
return result;
}
IMAGE_RESOURCE_DIR_STRING_U *InternalLoadString (
HMODULE ImageBase, UINT uID, WORD wLanguage, WORD *lpwErrorCode, WORD *lpwLanguageFound )
{
DWORD dwResDirVA = ResDirVAFromHMODULE (ImageBase, lpwErrorCode);
if (!dwResDirVA) return NULL;
return InternalLoadString (
ImageBase,
dwResDirVA,
uID,
wLanguage,
lpwErrorCode,
lpwLanguageFound
);
}
FindResource.h
#ifndef FindResource_h
#define FindResource_h
#include "ResCommon.h"
void *MyLoadResource (
HMODULE ImageBase, IMAGE_RESOURCE_DATA_ENTRY *lpDataEntry );
DWORD MySizeOfResource (
IMAGE_RESOURCE_DATA_ENTRY *lpDataEntry );
IMAGE_RESOURCE_DATA_ENTRY *MyFindResource (
DWORD dwResDirVA, LPCTSTR lpName, LPCTSTR lpType, WORD *lpwErrorCode = NULL, WORD *lpwLanguageFound = NULL );
IMAGE_RESOURCE_DATA_ENTRY *MyFindResource (
HMODULE ImageBase, LPCTSTR lpName, LPCTSTR lpType, WORD *lpwErrorCode = NULL, WORD *lpwLanguageFound = NULL );
IMAGE_RESOURCE_DATA_ENTRY *MyFindResourceEx (
DWORD dwResDirVA, LPCTSTR lpName, LPCTSTR lpType, WORD wLanguage = MAKELANGID (LANG_NEUTRAL,
SUBLANG_NEUTRAL), WORD *lpwErrorCode = NULL );
IMAGE_RESOURCE_DATA_ENTRY *MyFindResourceEx (
HMODULE ImageBase, LPCTSTR lpName, LPCTSTR lpType, WORD wLanguage = MAKELANGID (LANG_NEUTRAL,
SUBLANG_NEUTRAL), WORD *lpwErrorCode = NULL );
IMAGE_RESOURCE_DATA_ENTRY *InternalFindResourceEx (
DWORD dwResDirVA, LPCTSTR lpName, LPCTSTR lpType, WORD wLanguage = MAKELANGID (LANG_NEUTRAL,
SUBLANG_NEUTRAL), WORD *lpwErrorCode = NULL, WORD *lpwLanguageFound = NULL );
IMAGE_RESOURCE_DATA_ENTRY *InternalFindResourceEx (
HMODULE ImageBase, LPCTSTR lpName, LPCTSTR lpType, WORD wLanguage = MAKELANGID (LANG_NEUTRAL,
SUBLANG_NEUTRAL), WORD *lpwErrorCode = NULL, WORD *lpwLanguageFound = NULL );
#endif
FindResource.cpp
#include "FindResource.h"
void *MyLoadResource (
HMODULE ImageBase, IMAGE_RESOURCE_DATA_ENTRY *lpDataEntry )
{
__try
{
if (!ImageBase || !lpDataEntry) return NULL;
return (void*)((DWORD)ImageBase + (lpDataEntry->OffsetToData & 0x7FFFFFFF));
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return NULL;
}
}
DWORD MySizeOfResource (
IMAGE_RESOURCE_DATA_ENTRY *lpDataEntry )
{
__try
{
return lpDataEntry->Size;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return 0;
}
}
IMAGE_RESOURCE_DATA_ENTRY *MyFindResource (
DWORD dwResDirVA, LPCTSTR lpName, LPCTSTR lpType, WORD *lpwErrorCode, WORD *lpwLanguageFound )
{
return InternalFindResourceEx (
dwResDirVA,
lpName,
lpType,
MAKELANGID (LANG_NEUTRAL, SUBLANG_NEUTRAL),
lpwErrorCode,
lpwLanguageFound
);
}
IMAGE_RESOURCE_DATA_ENTRY *MyFindResource (
HMODULE ImageBase, LPCTSTR lpName, LPCTSTR lpType, WORD *lpwErrorCode, WORD *lpwLanguageFound )
{
return InternalFindResourceEx (
ImageBase,
lpName,
lpType,
MAKELANGID (LANG_NEUTRAL, SUBLANG_NEUTRAL),
lpwErrorCode,
lpwLanguageFound
);
}
IMAGE_RESOURCE_DATA_ENTRY *MyFindResourceEx (
DWORD dwResDirVA, LPCTSTR lpName, LPCTSTR lpType, WORD wLanguage, WORD *lpwErrorCode )
{
IMAGE_RESOURCE_DATA_ENTRY *result;
WORD wLanguageFound;
result = InternalFindResourceEx (
dwResDirVA,
lpName,
lpType,
wLanguage,
lpwErrorCode,
&wLanguageFound
);
if (result && wLanguage != wLanguageFound)
{
result = NULL;
__try
{
if (lpwErrorCode) *lpwErrorCode = RES_LANG_NOT_FOUND;
}
__except (EXCEPTION_EXECUTE_HANDLER) {}
}
return result;
}
IMAGE_RESOURCE_DATA_ENTRY *MyFindResourceEx (
HMODULE ImageBase, LPCTSTR lpName, LPCTSTR lpType, WORD wLanguage, WORD *lpwErrorCode )
{
IMAGE_RESOURCE_DATA_ENTRY *result;
WORD wLanguageFound;
result = InternalFindResourceEx (
ImageBase,
lpName,
lpType,
wLanguage,
lpwErrorCode,
&wLanguageFound
);
if (result && wLanguage != wLanguageFound)
{
result = NULL;
__try
{
if (lpwErrorCode) *lpwErrorCode = RES_LANG_NOT_FOUND;
}
__except (EXCEPTION_EXECUTE_HANDLER) {}
}
return result;
}
int CmpLPCTSTRStringU (LPCTSTR lpStr, IMAGE_RESOURCE_DIR_STRING_U *lpStrU)
{
int result;
DWORD StrLen = (DWORD)lstrlen (lpStr);
if (StrLen < lpStrU->Length) return -1;
else if (StrLen > lpStrU->Length) return 1;
if (!StrLen) return 0;
LPTSTR UniStr;
#ifdef UNICODE
UniStr = (LPWSTR)&lpStrU->NameString;
#else
UniStr = (LPSTR)malloc (lpStrU->Length);
if (!UniStr) RAISE_EXCEPTION_RESULT (RES_MEMORY_ERR);
__try
{
if (WideCharToMultiByte (CP_ACP, 0, (LPCWSTR)&lpStrU->NameString,
lpStrU->Length, UniStr, lpStrU->Length, NULL, NULL) != lpStrU->Length)
RAISE_EXCEPTION_RESULT (RES_UNKNOWN_ERR);
#endif
result = CompareString (
LOCALE_USER_DEFAULT,
0,
lpStr,
StrLen,
UniStr,
lpStrU->Length
) - CSTR_EQUAL;
#ifndef UNICODE
}
__finally
{
free (UniStr);
}
#endif
return result;
}
int CmpLPCTSTRName (DWORD dwResDirVA, LPCTSTR lpStr, DWORD Name)
{
if (IS_RES_ID (lpStr))
{
if ((WORD)lpStr < (WORD)Name) return -1;
else if ((WORD)lpStr > (WORD)Name) return 1;
else return 0;
}
else
{
return CmpLPCTSTRStringU (
lpStr,
(IMAGE_RESOURCE_DIR_STRING_U*)(dwResDirVA + (Name & 0x7FFFFFFF))
);
}
}
void ConvertResName (LPCTSTR *lplpStr)
{
if (!IS_RES_ID (*lplpStr))
if ((*lplpStr)[0] == '#')
{
errno = 0;
LPTSTR endptr;
long num = _tcstol (*lplpStr + 1, &endptr, 10);
if (errno != 0 || (*lplpStr + 1) == endptr || (DWORD)num > 0xFFFF) RAISE_EXCEPTION_RESULT (RES_INVALID_RESNAME);
*lplpStr = (LPCTSTR)num;
}
}
IMAGE_RESOURCE_DIRECTORY_ENTRY *FindResDirEntry (
DWORD dwResDirVA, IMAGE_RESOURCE_DIRECTORY *lpDir, LPCTSTR lpName )
{
DWORD low, high, mid;
IMAGE_RESOURCE_DIRECTORY_ENTRY *entries = (IMAGE_RESOURCE_DIRECTORY_ENTRY*)(lpDir + 1);
ConvertResName (&lpName);
if (IS_RES_ID (lpName))
{
low = lpDir->NumberOfNamedEntries;
high = low + lpDir->NumberOfIdEntries;
}
else
{
low = 0;
high = lpDir->NumberOfNamedEntries;
}
while (high > low)
{
mid = (high + low) >> 1;
int cmpres = CmpLPCTSTRName (dwResDirVA, lpName, entries[mid].Name);
if (!cmpres) return &entries[mid];
if (cmpres < 0)
high = mid;
else
low = mid + 1;
}
return NULL;
}
IMAGE_RESOURCE_DATA_ENTRY *InternalFindResourceEx (
DWORD dwResDirVA, LPCTSTR lpName, LPCTSTR lpType, WORD wLanguage, WORD *lpwErrorCode, WORD *lpwLanguageFound )
{
IMAGE_RESOURCE_DATA_ENTRY *result = NULL;
IMAGE_RESOURCE_DIRECTORY_ENTRY *entry;
IMAGE_RESOURCE_DIRECTORY *lang_dir;
WORD error_code = RES_UNKNOWN_ERR;
__try
{
entry = FindResDirEntry (
dwResDirVA,
(IMAGE_RESOURCE_DIRECTORY*)dwResDirVA,
lpType
);
if (!entry) RAISE_EXCEPTION_RESULT (RES_TYPE_NOT_FOUND);
entry = FindResDirEntry (
dwResDirVA,
(IMAGE_RESOURCE_DIRECTORY*)(dwResDirVA + (entry->OffsetToData & 0x7FFFFFFF)),
lpName
);
if (!entry) RAISE_EXCEPTION_RESULT (RES_NOT_FOUND);
lang_dir = (IMAGE_RESOURCE_DIRECTORY*)
(dwResDirVA + (entry->OffsetToData & 0x7FFFFFFF));
if (wLanguage == MAKELANGID (LANG_NEUTRAL, SUBLANG_NEUTRAL))
wLanguage = LANGIDFROMLCID (GetThreadLocale ());
entry = FindResDirEntry (dwResDirVA, lang_dir, (LPCTSTR)wLanguage);
if (!entry)
{
if (!lang_dir->NumberOfIdEntries)
RAISE_EXCEPTION_RESULT (RES_LANG_NOT_FOUND);
entry = (IMAGE_RESOURCE_DIRECTORY_ENTRY*)(lang_dir + 1);
}
result = (IMAGE_RESOURCE_DATA_ENTRY*)
(dwResDirVA + (entry->OffsetToData & 0x7FFFFFFF));
error_code = RES_OK;
}
__except (EXCEPT_EVAL) {}
__try
{
if (lpwErrorCode) *lpwErrorCode = error_code;
}
__except (EXCEPTION_EXECUTE_HANDLER) {}
__try
{
if (lpwLanguageFound) *lpwLanguageFound = (WORD)entry->Name;
}
__except (EXCEPTION_EXECUTE_HANDLER) {}
return result;
}
IMAGE_RESOURCE_DATA_ENTRY *InternalFindResourceEx (
HMODULE ImageBase, LPCTSTR lpName, LPCTSTR lpType, WORD wLanguage, WORD *lpwErrorCode, WORD *lpwLanguageFound )
{
DWORD dwResDirVA = ResDirVAFromHMODULE (ImageBase, lpwErrorCode);
if (!dwResDirVA) return NULL;
return InternalFindResourceEx (
dwResDirVA,
lpName,
lpType,
wLanguage,
lpwErrorCode,
lpwLanguageFound
);
}