//// RSRC - Win32 command line resource manager // // Copyright (c) 1996-9, Microsoft Corporation. All rights reserved. // // David C Brown [dbrown] 29th October 1998. ///// RSRC Command line // //c RSRC Executable [-l LocLang] [-u UnlocLang] [-i Types] [-q] //c [ [-t|-d] TextOutput [-c UnlocExecutable] //c | [-a|-r] text-input [-s symbols] [-v] ] // //p Executable: Win32 binary to analyse (default), to generate tokens (-t) // or dump (-d) from, or containing resources to be replaced (-r) // or appended to (-a). // //p -l LocLang: Restrict processing to the specified localized language. LangId // should be specified as a full hex NLS Language id, for example use // '-l 409' for US English. Required for replace (-r) operation. // //p -u UnlocLang: Specifies unlocalized language, defaults to 409 (US English). // //p -i Types: Restrict processing to listed types. Each type is indicated by a letter // as below: // //t Letter | Type | Letter | Type | Letter | Type //t ------ | ---- | ------ | ---- | ------ | ---- //t c | Cursors | g | Message tables | n | INFs //t b | Bitmaps | v | Version info | h | HTML //t i | Icons | a | Accelerators | x | Binary data //t m | Menus | f | Font directories | | //t d | Dialogs | t | Fonts | o | All others //t s | Strings | r | RCDATA | a | All (default) // // //p -q: Quiet. By default Rsrc displays summary statistics of types and languages // of resources processed. -q suppresses all output except warning and error messages. // //p -t TextOutput: Generate tokens in checkin format. // //p -d TextOutput: Dump resources in Hex & ASCII format. // //p -c UnlocExecutable: Compare with unlocalized (English) resources - localised // resources in executable are compared with English resources in // UnlocExecutable. When the localised resource is bit for bit identical // with the English resource RSRC writes a one line unloc // token instead of the full resource. Valid only with tokenise (-t) // option. // //p -a TextInput: Append resources from text input file. Every resource in the // text file is added to the executable. Resources already in the executable // are not removed. When a resource from the token file has the same type, id // and language as one in the executable, the executable resource is replaced // by the token resource. // //p -r TextInput: Replace English resources in executable by localised resources // from text file. Requires -l parameter to specify localisation language. // When a resource from the token file has the same type and id as one in the // executable, and the executable resource is US English (409) and the localised // resource is in the language specified on the -l parameter, the US English // resource is removed. // //p -s Symbols: Symbol file (.dbg format). When RSRC updates the header checksum // in executable, it will also do so in the named symbol file. Valid only // with the replace (-r) and append (-a) options. // // // Miscellaneous options // //p -v: Update file and product version. By default any file and product version // in the token file is ignored during update/append, the file and product // versions from the original unlocalised resources are retained. // ///// Definitions // //p Resource key: The combination of resource type, resource id and // resource language. The resource key uniquely identifies the // resource. A Win32 executable can contain any combination of // languages, ids and types so long as no two resources have the // same type, key and language. // //p Resource type: A numeric or string value. Some numeric values are // predefined, for example menus and dialogs, but apps can and // do use any value they choose. // //p Resource id: A numeric or string value. Used by an application to // identify the resource when calling FindResource, LoadString etc. // //p Resource language: An NLS LANGID, i.e. a combination of primary and // sub-language, such as 0409 (US English). // //p Unloc token: A line in the token file specifying a localised resource // key followed by '=lang,cksum' where lang is the unlocalised // language (usually 0409) and cksum is the checksum of the unlocalised // resource. Used when the only difference between the localised and // unlocalised resource is the language in the resource key. ///// Use during localisation checkin process // //c RSRC LocalisedExecutable -c UnlocExecutable -t Tokens -l LocLang [-u UnlocLang] // // Extracts localized tokens for the specified langauge. // // Where a resource in the localised executable is bit for bit identical // to a resource in the unlocalized executable, the resource content is not // written to the token file. In its place RSRC writes an unloc token // giving the checksum of the resource and specifying the target language. // // Warnings are generated if the localised executable contains resources // in languages other than that specified by the -l parameter. // // Unlocalised resources for comparison are looked up in the unlocalised // executable in the language specified on the -u parameter, default 409 // (US ENglish). ///// Use during the build to update a single language executable // //c RSRC Executable [-u UnlocLang] -r Tokens -l LocLang -s SymbolFile // // Each localised resource in the token file is added to the executable. // // Each corresponding unlocalized resource is removed from the executable. // // For each unloc token the unlocalized resource is found in the executable // and its language is updated to the target localized language recorded // in the unloc token. // // Tokens of other than the specified localised language are not // processed, but generate warnings. // // Warnings are generated for any resources not appearing in both the // executable and token files. // // Warnings are also generated for resources of other than the unlocalised // language found in the original executable, and resources of other than // the localised language in the token file. // // The unlocalised language defaults to 409 (US English). ///// Use during the build to add resources to a multi-language executable // //c RSRC Executable [-u UnlocLang] -a Tokens [-l Language] -s SymbolFile // // Localised resources from the token file are added to the executable. // // For each unloc token the unlocalised resource is found in the executable // and copied for the localised language recorded in the unloc token. // // If '-l Languge' is specified, only tokens of that language are added. // When used with the append (-a) option, '-l Language' applies only to // the token file: pre-existing resources in the executable are not affected. // // If a resource from the token file matches a resource already in the // executable in type, name and language, the executable resource // is replaced. // // Warnings are generated if any token in the executable is replaced, or // if the unlocalised resource corresponding to an unloc token is missing // or has a checksum which differs from the unlocalised resource that was // passed on the '-u' parameter when the toke file was created. // // If the '-l Language' option is used, warnings are generated for any // resources of other languages found in the token file. ///// Token format - resource key and header // // A resource may be represented by one or more lines. When // a resource is spread over more than one line, all lines except the // first are indented by three spaces. // // The first line of every resource starts with the resource key as follows: // // type,id,language; // // This is followed by the codepage recorded in the resource directory. // Note that the codepage is not part of the resource key, and is not // maintained consistently by all software. In particular: // // o RC writes the codepage as zero // o The NT UpdateResource API writes the codepage as 1252 // o Locstudio writes a codepage that corresponds to the resource language // // Winnt.h documents the codepage as follows: // // "Each resource data entry ... contains ... a CodePage that should be // used when decoding code point values within the resource data. // Typically for new applications the code page would be the unicode // code page.' // // In practise I have never seen the codepage value set to Unicode (1200). // // If the '-c' (unlocalised comparison) parameter was provided on the // rsrc command, and there was an unlocalised resource with the same type // and id, the language and checksum of that unlocalised resource are // appended. // // Finally, the resource data is represented in one of the forms below, // or as 'unloc' if the resource data exactly matches the unlocalised resource // found in the file passed by 'c'. // // // There are thus three possible token key/header formats as follows: // //c type,id,language;codepage;resource-data // // Resource recorded in full, either no '-c' parameter specified, or // resource does not exist in unlocalised file. // // //c type,id,language;codepage,unlocalised-checksum,language;resource-data // // Resource recorded in full, '-c' parameter was specified, and localised // resource image differed from unlocalised resource image. // // //c type,id,language;codepage,unlocalised-checksum,language;'Unloc' // // Resource recorded in full, '-c' parameter was specified, and localised // resource image was identical to unlocalised resource image. ///// Token samples - default hex format // // // For most resource types, RSRC generates resources // as a string of hex digits. // // For example, the following represents an accelerator resource. // //c 0009,0002,0409;00000000;Hex;00000020:030074008e00000003002e00840000000b0044008400000087002e0084000000 // // o Type 0x0009 (Accelerator) // o Id 0x0002 // o Language 0x0409 (LANG_ENGLISH, SUBLANG_US) // o Codepage 0 (implies resource was probably generated by RC) // o Length in bytes 0x0020 // // The resource is short, so its hex representation follows the length. // // // A larger binary resource is represented on multiple lines as follows: // //c 000a,4000,0409;00000000;Hex;0000016a //c 00000000:0000640100004c0000000114020000000000c000000000000046830000003508000050130852c8e0bd0170493f38ace1bd016044d085c9e0bd01003000000000000001000000000000000000000000000000590014001f0fe04fd020ea3a6910a2d808002b30309d190023563a5c000000000000000000000000000000000065 //c 00000080:7c15003100000000003025a49e308857696e6e74000015003100000000002f25d3863508466f6e747300000000490000001c000000010000001c0000003900000000000000480000001d0000000300000063de7d98100000005f535445504853544550485f00563a5c57494e4e545c466f6e7473000010000000050000a02400 //c 00000100:00004200000060000000030000a05800000000000000737465706800000000000000000000004255867d3048d211b5d8d085029b1cfa4119c94a9f4dd211871f0000000000004255867d3048d211b5d8d085029b1cfa4119c94a9f4dd211871f00000000000000000000 // // o Type 0x000a (RCDATA) // o Id 0x4000 // o Language 0x0409 (LANG_ENGLISH, SUBLANG_US) // o Codepage 0 // o Length in bytes 0x016a // // The hex representation is split onto multiple lines each of 128 bytes. ///// Warnings and errors // // // // // // o warning RSRC100: Localised resource has no corresponding unlocalised resource in %s // o warning RSRC110: Unlocalised resource from token file appended to executable // o warning RSRC111: Unlocalised resource from token file replaced unlocalised resource in executable // o warning RSRC112: Localised resource from token file replaced localised resource already present in executable // o warning RSRC113: Localised resource from token file appended to executable - there was no matching unlocalised resource // // o warning RSRC120: Token file resource does not match specified language - ignored // o warning RSRC121: Token file resource is not a requested resource type - ignored // o warning RSRC122: executable unlocalised resource checksum does not match checksum recorded in token file for resource %s // o warning RSRC124: missing executable unlocalised resource for %s // o warning RSRC125: executable contains no unlocalised resource corresponding to resource %s // // o warning RSRC160: Symbol file does not match exectable // o warning RSRC161: Symbol file not processed // o warning RSRC162: Could not reopen executable %s to update checksum // o warning RSRC163: Failed to write updated symbol checksum // // o warning RSRC170: No localizable resources in %s // o warning RSRC171: could not close executable // // // o error RSRC230: 'Unloc' token is missing unlocalised resource information for %s // o error RSRC231: Failed to apply unloc token // o error RSRC232: Failed to apply token // // o error RSRC300: Hex digit expected // o error RSRC301: Hex value too large // o error RSRC302: Unexpected end of file // o error RSRC303: \'%s\' expected // o error RSRC304: newline expected // o error RSRC310: Unrecognised resource type for resource %s // // o error RSRC400: -t (tokenise) option excludes -d, -a, -r, and -s // o error RSRC401: -d (dump) option excludes -t, -u, -a, -r, and -s // o error RSRC402: -a (append) option excludes -t, -d, -u, and -r // o error RSRC403: -r (replace) option excludes -t, -d, -u, and -a // o error RSRC404: -r (replace) option requires -l (LangId) // o error RSRC405: Analysis excludes -s // // o error RSRC420: Update failed. // o error RSRC421: Token extraction failed. // o error RSRC422: Analysis failed. // // o error RSRC500: Corrupt executable - resource appears more than once // o error RSRC501: %s is not an executable file // o error RSRC502: %s is not a Win32 executable file // o error RSRC503: No resources in %s // // o error RSRC510: Cannot open executable file %s // o error RSRC511: cannot find resource directory in %s // o error RSRC512: Cannot create resource token file %s // o error RSRC513: Cannot open unlocalised executable file %s // o error RSRC514: cannot find resource directory in unlocalised executable %s // o error RSRC515: cannot write delta token file %s // o error RSRC516: cannot write stand alone token file %s // // o error RSRC520: Cannot open resource token file %s // o error RSRC521: UTF8 BOM missing from token file // // o error RSRC530: Cannot read executable resources from %s // o error RSRC531: Failed reading update tokens // // o error RSRC600: BeginUpdateResource failed on %s // o error RSRC601: UpdateResourceW failed on %s // o error RSRC602: EndUpdateResourceW failed on %s //// From Adina // // Here is my follow up on 2. // // Abstract: // The build team needs the new tool eventually run with build.exe, i.e. // we need build.exe recognize the errors, warnings, and simple output // messages from rsrc.exe and write them to build.err, build.wrn and // build.log files respectively. // // Solution: // All we need is RSRC complain to the general rule for the MS tools. // That is (\\orville\razzle\src\sdktools\build\buildexe.c): // {toolname} : {number}: {text} // // where: // // toolname If possible, the container and specific module that has // the error. For instance, the compiler uses // filename(linenum), the linker uses library(objname), etc. // If unable to provide a container, use the tool name. // number A number, prefixed with some tool identifier (C for // compiler, LNK for linker, LIB for librarian, N for nmake, // etc). // test The descriptive text of the message/error. // // Accepted String formats are: // container(module): error/warning NUM ... // container(module) : error/warning NUM ... // container (module): error/warning NUM ... // container (module) : error/warning NUM ... // // Ex. of RSRC error: // // RSRC : error RSRC2001: unable to open file d:\nt\binaries\jpn\ntdll.dll // // Ex. of RSRC warning: // // RSRC : warning RSRC5000: unable to find symbol file // d:\nt\binaries\jpn\retail\dll\ntdll.dbg // // Be aware that the error number after "error/warning" is NOT optional. // As the format above says, you can also display any information you // consider useful (for example the name of the binary being processed, // or the line number in the token file that caused the error/warning) // immediately after the name of the tool: RSRC(). // // I confirm that RSRC_OK=0, RSRC_WRN=1, RSRC_ERR=2 are fine with us as // return values. Also, it does not make any difference if you write the // output to stderr ot stdout, but I would suggest to write the tool's // usage and all the warning and error message lines to stderr, and any // other text to stdout (based on other ms tools we're using, like // rebase.exe, binplace.exe, etc). // // I can make the changes to build.exe so that it recognizes RSRC. // // Please let me know if you have any questions. // // Thank you // Adina /// Following meeting Joerg. here are my action items: // // Meet with Joerg, Uwe, Majd, Hideki, Adina to plan usage in bidi NT5 // build process and consider use for odd jobs in other languages. // // P1. Implement option to skip updating file and product version, and // to omit these from token file. // P1. Implement separate error code for detecting unhandled binary // format (such as win16). // // P2. Add CRC to each resource to detect SLM or editor corruption. // (Delete CRC in token file always accepted to allow hand modification). // P2. Option to disable header comment in token file // // P3. Interpret MSGTBL, ACCELERATOR and RCDATA - RCDATA as string // depending on option. // // Thanks -- Dave. //// From Joerg // // Howdy, // I'm playing with rsrc and ran into problems with ParseToken(): if rsrc // is located in a directory with spaces (e.g. Program Files), // the function fails to skip the command name, since it's quoted and // ParseToken stops at the first blank within the quotes. // I also had trouble compiling it (so I can step thru and see what it's // doing) under VC5 because there is no default constructor // for the class "LangId", so I just added a dummy constructor. // // Jörg //// Following meeting planning bidi build, Wednesday 2nd Dec. // // Checksum protection against user changes to tok file // Include length in warning comparison // Will need alpha build // Default file name - add .rsrc // Don't extract file or product version // => If version resource updated use file and product version from // US at write time // Diagnose version only resources // Diagnose not win32 // Warning for no translations on tokenisation // Warning no no translations on update, and don't touch executable // Ability to -r any unlocalised language //// Resultant priorities (8th Dec): // // û 1. Use unlocalised file/product version if updating version resource // û 2. Analyse mode diagnoses no localisable resources and unhandled binary formats // 3. Warn when no translations, don't touch executable if updating // û 4. Support -r from any language to any language // 5. Allocate error numbers, clarify error messages // // 6. Include length in unloc token // û 7. Handle quoted installation directory and default filenames // 8. Add checksum protection against corruption of token file // 9. Option to interpret RCDATA as Unicode string (for kernel) // 10. Interpret MSGTBL and ACCELERATOR // 11. Support Win16 binaries // 12. ? Option to disable token file header #pragma warning( disable : 4786 ) // map creates some ridiculously long debug identifiers #include "stdio.h" #include "windows.h" #include "imagehlp.h" #include "time.h" #include using namespace std ; using namespace std::rel_ops ; #define DBG 1 //// OK and ASSERT macros // // All functions return an HRESULT. // All function calls are wrapped in 'OK()'. // OK checks for a failed HRESULT and if so returns that HRESULT directly. // Thus all errors propagate back up the call chain. // // MUST issues a message if an HRESULT is not S_OK and returns E_FAIL // back up the call chain. void __cdecl DebugMsg(char *fmt, ...) { va_list vargs; va_start(vargs, fmt); vfprintf(stderr, fmt, vargs); } #define MUST(a,b) {HRESULT hr; hr = (a); if (hr!= S_OK) {if (!g_fError) DebugMsg b; g_fError = TRUE; return E_FAIL;};} #define SHOULD(a,b) {HRESULT hr; hr = (a); if (hr!= S_OK) {DebugMsg b; g_fWarn = TRUE; return S_FALSE;};} #if DBG #pragma message("Checked build") #define OK(a) {HRESULT hr; hr = (a); if (hr!= S_OK) {DebugMsg("%s(%d): error RSRC999 : HRESULT not S_OK: "#a"\n", __FILE__, __LINE__); return hr;};} #define ASSERT(a) {if (!(a)) {DebugMsg("%s(%d): error RSRC999 : Assertion failed: "#a"\n", __FILE__, __LINE__); return E_UNEXPECTED;};} #else #pragma message ("Free build") #define OK(a) {HRESULT hr; hr = (a); if (hr != S_OK) return hr;} #define ASSERT(a) {if (!(a)) {return E_UNEXPECTED;};} #endif const int MAXPATH = 128; const char HexDigit[] = "0123456789abcdef"; const BYTE bZeroPad[] = { 0, 0, 0, 0}; const int MAXHEXLINELEN=128; const int OPTHELP = 0x00000001; const int OPTQUIET = 0x00000002; const int OPTEXTRACT = 0x00000004; const int OPTUNLOC = 0x00000008; const int OPTHEXDUMP = 0x00000010; const int OPTAPPEND = 0x00000020; const int OPTREPLACE = 0x00000040; const int OPTSYMBOLS = 0x00000080; const int OPTVERSION = 0x00000100; const int PROCESSCUR = 0x00000001; const int PROCESSBMP = 0x00000002; const int PROCESSICO = 0x00000004; const int PROCESSMNU = 0x00000008; const int PROCESSDLG = 0x00000010; const int PROCESSSTR = 0x00000020; const int PROCESSFDR = 0x00000040; const int PROCESSFNT = 0x00000080; const int PROCESSACC = 0x00000100; const int PROCESSRCD = 0x00000200; const int PROCESSMSG = 0x00000400; const int PROCESSVER = 0x00000800; const int PROCESSBIN = 0x00001000; const int PROCESSINF = 0x00002000; const int PROCESSOTH = 0x00004000; const int PROCESSALL = 0xFFFFFFFF; DWORD g_dwOptions = 0; DWORD g_dwProcess = 0; LANGID g_LangId = 0xffff; BOOL g_fWarn = FALSE; BOOL g_fError = FALSE; LANGID g_liUnlocalized = 0x0409; // Standard unlocalized language int g_cResourcesIgnored = 0; int g_cResourcesUpdated = 0; // Simple replacement int g_cResourcesTranslated = 0; // Changed from unloc language to loc language int g_cResourcesAppended = 0; // Added without affecting existing resources int g_cResourcesExtracted = 0; // Extracted to token file char g_szTypes [MAXPATH]; char g_szExecutable [MAXPATH]; // Name of executable being analysed, tokenised or updated char g_szResources [MAXPATH]; // Name of resource token file char g_szUnloc [MAXPATH]; // Name of unlocalized executable for comparison int HexCharVal(char c) { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return c - '0'; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': return c - 'a' + 10; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': return c - 'A' + 10; } return -1; // Not a hex value } //// Scanner // // A structure for scanning through a block of memory class Scanner { protected: const BYTE *m_pStart; const BYTE *m_pRead; const BYTE *m_pLimit; public: Scanner() {m_pStart = NULL; m_pRead = NULL; m_pLimit = NULL;} Scanner(const BYTE *pb, DWORD dwLen) {m_pStart = pb; m_pRead = pb; m_pLimit = pb+dwLen;} ~Scanner() {m_pStart = NULL; m_pRead = NULL; m_pLimit=NULL;} const BYTE* GetRead() {return m_pRead;} const BYTE* GetLimit() {return m_pLimit;} HRESULT Advance(UINT cBytes) { ASSERT(m_pStart != NULL); ASSERT(m_pRead+cBytes <= m_pLimit); m_pRead += cBytes; return S_OK; } HRESULT Align(const BYTE *pb, int iAlignment) { // Advance until read position is a multiple of iAlignment // past pb. iAlignment MUST be a power of 2. // Does not advance past limit. // Ensure iAlignment is a power of 2 // This seems like a good test, though I'm not sure I could prove it! ASSERT((iAlignment | iAlignment-1) == iAlignment*2 - 1); if (m_pRead - pb & iAlignment - 1) { m_pRead += (iAlignment - (m_pRead - pb & iAlignment - 1)); if (m_pRead > m_pLimit) { m_pRead = m_pLimit; } } return S_OK; } HRESULT SetRead(const BYTE *pb) { ASSERT(m_pRead != NULL); ASSERT(pb >= m_pStart); ASSERT(pb < m_pLimit); m_pRead = pb; return S_OK; } }; class TextScanner : public Scanner { protected: const BYTE *m_pLine; // Start of current line int m_iLine; // Current line char m_szTextPos[40]; public: TextScanner() {m_pLine = NULL; m_iLine = 0; Scanner();} virtual char *GetTextPos() { sprintf(m_szTextPos, "%d:%d", m_iLine, m_pRead-m_pLine+1); return m_szTextPos; } //// ReadString // // Translates UTF8 to Unicode // Removes '\' escapes as necessary // Always returns a new zero terminated string HRESULT ReadString(WCHAR **ppwcString, int *piLen) { char *pc; WCHAR *pwc; int iLen; ASSERT(*((char*)m_pRead) == '\"'); OK(Advance(1)); pc = (char*)m_pRead; iLen = 0; // Count the number of unicode codepoints represented by the string while ( *pc != '\"' && pc < (char*)m_pLimit) { while ( pc < (char*)m_pLimit && *pc != '\\' && *pc != '\"' ) { if (*pc < 128) { pc++; } else { ASSERT(*pc >= 0xC0); // 80-BF reserved for trailing bytes if (*pc < 0xE0) { pc+=2; } else if (*pc < 0xF0) { pc+=3; } else { iLen++; // Additional Unicode codepoint required for surrogate pc+=4; } ASSERT(pc <= (char*)m_pLimit); } iLen++; } if (*pc == '\\') { pc++; if (pc < (char*)m_pLimit) { pc++; iLen++; } } } // Create a Unicode copy of the string *ppwcString = new WCHAR[iLen+1]; ASSERT(*ppwcString != NULL); pwc = *ppwcString; while (*((char*)m_pRead) != '\"') { while ( *((char*)m_pRead) != '\\' && *((char*)m_pRead) != '\"') { if (*m_pRead < 0x80) { *pwc++ = *(char*)m_pRead; m_pRead++; } else { if (*m_pRead < 0xE0) { *pwc++ = (WCHAR)(*m_pRead & 0x1F) << 6 | (WCHAR)(*(m_pRead+1) & 0x3F); m_pRead+=2; } else if (*m_pRead < 0xF0) { *pwc++ = (WCHAR)(*m_pRead & 0x0F) << 12 | (WCHAR)(*(m_pRead+1) & 0x3F) << 6 | (WCHAR)(*(m_pRead+2) & 0x3F); m_pRead+=3; } else { *pwc++ = 0xd800 | (( (WCHAR)(*m_pRead & 0x07 << 2) | (WCHAR)(*(m_pRead+1) & 0x30 >> 4)) - 1) << 6 | (WCHAR)(*(m_pRead+1) & 0x0F) << 2 | (WCHAR)(*(m_pRead+2) & 0x30) >> 4; *pwc++ = 0xdc00 | (WCHAR)(*(m_pRead+2) & 0x0f) << 6 | (WCHAR)(*(m_pRead+3) & 0x3f); m_pRead+=4; } } } if (*(char*)m_pRead == '\\') { m_pRead++; if (m_pRead < m_pLimit) { switch (*(char*)m_pRead) { case 'r': *pwc++ = '\r'; break; case 'n': *pwc++ = '\n'; break; case 't': *pwc++ = '\t'; break; case 'z': *pwc++ = 0; break; case 'L': *pwc++ = 0x2028; break; // Line separator case 'P': *pwc++ = 0x2029; break; // Paragraph separator default: *pwc++ = *(char*)m_pRead; } m_pRead++; } } } *pwc = 0; // Add zero terminator m_pRead ++; *piLen = pwc - *ppwcString; ASSERT(m_pRead <= m_pLimit); return S_OK; } //// ReadHex // // Reads all characters up to he first non-hex digit and returns // the value represented by the sequence as a DWORD HRESULT ReadHex(DWORD *pdwVal) { *pdwVal = 0; MUST(HexCharVal(*(char*)m_pRead) >= 0 ? S_OK : E_FAIL, ("%s: error RSRC300: Hex digit expected\n", GetTextPos())); while ( HexCharVal(*(char*)m_pRead) >= 0 && m_pRead < m_pLimit) { MUST(*pdwVal < 0x10000000 ? S_OK : E_FAIL, ("%s: error RSRC301: Hex value too large\n", GetTextPos())); *pdwVal = *pdwVal << 4 | HexCharVal(*(char*)m_pRead); OK(Advance(1)); } return S_OK; } //// ReadHexByte - Read exactly 2 hex digits HRESULT ReadHexByte(BYTE *pb) { int n1,n2; // The two nibbles. n1 = HexCharVal(*(char*)m_pRead); n2 = HexCharVal(*(char*)(m_pRead+1)); MUST( n1 >= 0 && n2 >= 0 ? S_OK : E_FAIL, ("%s: error RSRC300: Hex digit expected\n", GetTextPos())); *pb = (n1 << 4) + n2; MUST(Advance(2), ("%s: error RSRC302: Unexpected end of file\n", GetTextPos())); return S_OK; } HRESULT Expect(const char *pc) { while ( *pc && m_pRead+1 <= m_pLimit) { MUST(*(char*)m_pRead == *pc ? S_OK : E_FAIL, ("%s: error RSRC303: \'%s\' expected\n", GetTextPos(), pc)); m_pRead++; pc++; } MUST(*pc == 0 ? S_OK : E_FAIL, ("%s: error RSRC303: \'%s\' expected\n", GetTextPos(), pc)); return S_OK; } //// SkipLn // // Skip to beginning of next non empty, non comment line. HRESULT SkipLn() { ASSERT(m_pRead != NULL); while (m_pRead < m_pLimit) { if (*(char*)m_pRead == '\r') { m_pRead++; if (m_pRead < m_pLimit && *(char*)m_pRead == '\n') { m_pRead++; m_pLine = m_pRead; m_iLine++; if ( m_pRead < m_pLimit && *(char*)m_pRead != '#' && *(char*)m_pRead != '\r') { break; } } } else { m_pRead++; } } return S_OK; } //// ExpectLn // // Expect end of line, preceeded by any whitespace // // Also skips trailing comments and whole line comments // // Any parameter is passed to Expect to vb found at the beginning of the new line HRESULT ExpectLn(const char *pc) { ASSERT(m_pRead != NULL); while ( m_pRead < m_pLimit && ( *(char*)m_pRead == ' ' || *(char*)m_pRead == '\t')) { m_pRead++; } if ( m_pRead < m_pLimit && ( *(char*)m_pRead == '\r' || *(char*)m_pRead == '#')) { // Condition satisfied, skip to first non comment line SkipLn(); } else { MUST(E_FAIL,("%s: error RSRC304: newline expected\n", GetTextPos())); } if (pc) { return Expect(pc); } return S_OK; } }; //// Mapped files // // File mapping is used to read executable and token files. // // File mapping is also used to update in place checksum information // in executable and symbol files. class MappedFile : public TextScanner { HANDLE m_hFileMapping; BOOL fRW; // True when writeable char m_szFileName[MAXPATH]; char m_szTextPos[MAXPATH+40]; public: MappedFile() {m_hFileMapping = NULL; TextScanner();} const BYTE* GetFile() {return m_pStart;} virtual char *GetTextPos() { sprintf(m_szTextPos, "%s(%s)", m_szFileName, TextScanner::GetTextPos()); return m_szTextPos; } HRESULT Open(const char *pcFileName, BOOL fWrite) { HANDLE hFile; m_pStart = NULL; m_pRead = NULL; m_pLimit = NULL; strcpy(m_szFileName, pcFileName); hFile = CreateFileA( pcFileName, GENERIC_READ | (fWrite ? GENERIC_WRITE : 0), FILE_SHARE_READ | (fWrite ? FILE_SHARE_WRITE | FILE_SHARE_DELETE : 0 ), NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); ASSERT(hFile != INVALID_HANDLE_VALUE); m_hFileMapping = CreateFileMapping( hFile, NULL, fWrite ? PAGE_READWRITE : PAGE_WRITECOPY, 0,0, NULL); ASSERT(m_hFileMapping != NULL); m_pStart = (BYTE*) MapViewOfFile( m_hFileMapping, fWrite ? FILE_MAP_WRITE : FILE_MAP_READ, 0,0, 0); ASSERT(m_pStart != NULL); m_pRead = m_pStart; m_pLine = m_pStart; m_pLimit = m_pStart + GetFileSize(hFile, NULL); m_iLine = 1; CloseHandle(hFile); fRW = fWrite; return S_OK; } DWORD CalcChecksum() { DWORD dwHeaderSum; DWORD dwCheckSum; if (CheckSumMappedFile((void*)m_pStart, m_pLimit-m_pStart, &dwHeaderSum, &dwCheckSum) == NULL) { return 0; } else { return dwCheckSum; } } HRESULT Close() { if (m_pStart) { UnmapViewOfFile(m_pStart); CloseHandle(m_hFileMapping); m_hFileMapping = NULL; m_pStart = NULL; } return S_OK; } }; //// NewFile - services for writing a new text otr binary file // // class NewFile { HANDLE hFile; DWORD cbWrite; // Bytes written BYTE buf[4096]; // Performance buffer int iBufUsed; public: NewFile() {iBufUsed = 0;} int GetWrite() {return cbWrite;} HRESULT OpenWrite(char *pcFileName) { cbWrite = 0; // Bytes written hFile = CreateFileA( pcFileName, GENERIC_READ | GENERIC_WRITE, 0, // Not shared NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); ASSERT(hFile != INVALID_HANDLE_VALUE); return S_OK; } HRESULT WriteBytes(const BYTE *pb, DWORD dwLen) { DWORD dwWritten; if (iBufUsed + dwLen <= sizeof(buf)) { memcpy(buf+iBufUsed, pb, dwLen); iBufUsed += dwLen; } else { ASSERT(hFile != NULL); if (iBufUsed > 0) { ASSERT(WriteFile(hFile, buf, iBufUsed, &dwWritten, NULL)); ASSERT(dwWritten == iBufUsed); iBufUsed = 0; } if (dwLen <= sizeof(buf)) { memcpy(buf, pb, dwLen); iBufUsed = dwLen; } else { ASSERT(WriteFile(hFile, pb, dwLen, &dwWritten, NULL)); ASSERT(dwWritten == dwLen); } } cbWrite += dwLen; return S_OK; } HRESULT WriteS(const char *pc) { return WriteBytes((BYTE*)pc, strlen(pc)); } //// WriteString // // Translates Unicode to UTF8 // Adds '\' escapes as necessary HRESULT WriteString(const WCHAR *pwc, int iLen) { BYTE buf[3]; const WCHAR *pwcLimit; pwcLimit = pwc + iLen; OK(WriteBytes((BYTE*)"\"", 1)); while (pwc < pwcLimit) { switch (*pwc) { case 0: OK(WriteS("\\z")); break; case '\r': OK(WriteS("\\r")); break; case '\n': OK(WriteS("\\n")); break; case '\t': OK(WriteS("\\t")); break; case 0x2028: OK(WriteS("\\L")); break; // Line separator case 0x2029: OK(WriteS("\\P")); break; // Paragraph separator case '\"': OK(WriteS("\\\"")); break; case '\\': OK(WriteS("\\\\")); break; default: if (*pwc < 128) { OK(WriteBytes((BYTE*)pwc, 1)); } else if (*pwc < 0x7FF) { buf[0] = 0xC0 | *pwc >> 6; buf[1] = 0x80 | *pwc & 0x3F; OK(WriteBytes(buf, 2)); } else { // Note - should code surrogates properly, this doesn't buf[0] = 0xE0 | *pwc>>12 & 0x0F; buf[1] = 0x80 | *pwc>>6 & 0x3F; buf[2] = 0x80 | *pwc & 0x3F; OK(WriteBytes(buf, 3)); } } pwc++; } OK(WriteBytes((BYTE*)"\"", 1)); return S_OK; } //// WriteHex // // Writes the given value in the given number of digits. // // If cDigits is zero, uses as many as necessary. HRESULT WriteHex(DWORD dw, int cDigits) { int i; char cBuf[8]; i = 7; while (dw && i >= 0) { cBuf[i] = HexDigit[dw & 0xf]; dw >>= 4; i--; } if (cDigits != 0) { while (i >= (8-cDigits)) { cBuf[i] = '0'; i--; } } OK(WriteBytes((BYTE*)(cBuf+i+1), 7-i)); return S_OK; } //// WriteHexBuffer // // Writes a buffer of up to 256 bytes as a continuous stream of hex digits HRESULT WriteHexBuffer(const BYTE *pb, DWORD dwLength) { DWORD i; char cBuf[512]; ASSERT(hFile); ASSERT(dwLength <= 256); for (i=0; i> 4 & 0xf]; cBuf[2*i+1] = HexDigit[*pb & 0xf]; pb++; } OK(WriteBytes((BYTE*)cBuf, 2*dwLength)); cbWrite += 2*dwLength; return S_OK; } //// WriteLn // // Write end of line mark (CR,LF) HRESULT WriteLn() { return WriteS("\r\n"); } HRESULT Close() { DWORD dwWritten; if (hFile) { if (iBufUsed > 0) { ASSERT(WriteFile(hFile, buf, iBufUsed, &dwWritten, NULL)); ASSERT(dwWritten == iBufUsed); } CloseHandle(hFile); hFile = NULL; } return S_OK; } }; //// Resource structures // // Each resource structure has an internal representation for the // resource that may be read and written to/from both text and // executable files. // // The ReadTok and WriteTok functions handle formatting and parsing // of the token file. // // The ReadBin function unpacks a resource from a memory mapped // executable into the internal representation. // // The cbBin function returns the unpadded length required for the // item in executable (packed) format. // // The CopyBin function packs the internal representation into a // target buffer. class Resource { public: virtual HRESULT ReadTok (TextScanner &mfText) = 0; virtual HRESULT ReadBin (Scanner &mfBin, DWORD dwLen) = 0; virtual HRESULT WriteTok (NewFile &nfText) const = 0; virtual size_t cbBin () const = 0; virtual HRESULT CopyBin (BYTE **ppb) const = 0; // For statistics virtual int GetItems () const = 0; virtual int GetWords () const = 0; }; //// ResourceBYTE // // BYTE value represented in hex digits. class ResourceBYTE { public: BYTE b; HRESULT ReadBin(Scanner *pmf) { b = *((BYTE*)(pmf->GetRead())); OK(pmf->Advance(sizeof(BYTE))); return S_OK; } size_t cbBin() const { return 1; } HRESULT CopyBin(BYTE **ppb) const { **ppb = b; (*ppb)++; return S_OK; } HRESULT ReadTok(TextScanner *pmf) { DWORD dw; OK(pmf->ReadHex(&dw)); ASSERT(dw < 0x100); b = (BYTE)dw; return S_OK; } HRESULT WriteTok(NewFile *pmf) const { OK(pmf->WriteHex(b, 2)); return S_OK; } }; //// ResoureWORD // // WORD value represented in hex digits. class ResourceWORD { public: WORD w; HRESULT ReadBin(Scanner *pmf) { w = *((WORD*)(pmf->GetRead())); OK(pmf->Advance(sizeof(WORD))); return S_OK; } size_t cbBin() const { return sizeof(WORD); } HRESULT CopyBin(BYTE **ppb) const { *(WORD*)*ppb = w; *ppb += sizeof(WORD); return S_OK; } HRESULT ReadTok(TextScanner *pmf) { DWORD dw; OK(pmf->ReadHex(&dw)); ASSERT(dw < 0x10000); w = (WORD)dw; return S_OK; } HRESULT WriteTok(NewFile *pmf) const { OK(pmf->WriteHex(w, 4)); return S_OK; } }; //// ResourceDWORD // // DWORD value represented in hex digits. class ResourceDWORD { public: DWORD dw; HRESULT ReadBin(Scanner *pmf) { dw = *((DWORD*)(pmf->GetRead())); OK(pmf->Advance(sizeof(DWORD))); return S_OK; } size_t cbBin() const { return sizeof(DWORD); } HRESULT CopyBin(BYTE **ppb) const { *(DWORD*)*ppb = dw; *ppb += sizeof(DWORD); return S_OK; } HRESULT ReadTok(TextScanner *pmf) { OK(pmf->ReadHex(&dw)); return S_OK; } HRESULT WriteTok(NewFile *pmf) const { OK(pmf->WriteHex(dw, 8)); return S_OK; } }; //// ResourceString // // String displayed with quotes. May be zero terminated or length // word. const WCHAR wcZero = 0; class ResourceString { WCHAR *pwcString; WORD iLen; void rsFree() { if (pwcString) delete [] pwcString; pwcString = NULL; iLen = 0; } public: ResourceString() {pwcString = NULL; iLen = 0;} ~ResourceString() {rsFree();} ResourceString& operator= (const ResourceString &rs) { iLen = rs.iLen; pwcString = new WCHAR[iLen+1]; memcpy(pwcString, rs.pwcString, 2*(iLen+1)); return *this; } ResourceString(const ResourceString &rs) { *this = rs; } const WCHAR *GetString() const {return pwcString;} const int GetLength() const {return iLen;}; void SetEmpty () {iLen = 0; pwcString = NULL;} HRESULT ReadBinL(Scanner *pmf) { rsFree(); iLen = *((WORD*)(pmf->GetRead())); OK(pmf->Advance(sizeof(WORD))); pwcString = new WCHAR[iLen+1]; ASSERT(pwcString != NULL); memcpy(pwcString, (WCHAR*)pmf->GetRead(), 2*iLen); pwcString[iLen] = 0; OK(pmf->Advance(iLen * sizeof(WCHAR))); return S_OK; } size_t cbBinL() const { return iLen * sizeof(WCHAR) + sizeof(WORD); } HRESULT CopyBinL(BYTE **ppb) const { *(WORD*)*ppb = iLen; *ppb += sizeof(WORD); memcpy(*ppb, pwcString, sizeof(WCHAR)*iLen); *ppb += sizeof(WCHAR)*iLen; return S_OK; } // Zero terminated HRESULT ReadBinZ(Scanner *pmf) { const WCHAR* pwc; rsFree(); pwc = (WCHAR*)pmf->GetRead(); while (*(WCHAR*)pmf->GetRead() != 0) { OK(pmf->Advance(2)); } iLen = (WCHAR*)pmf->GetRead() - pwc; OK(pmf->Advance(2)); pwcString = new WCHAR[iLen+1]; ASSERT(pwcString != NULL); memcpy(pwcString, pwc, 2*(iLen+1)); return S_OK; } size_t cbBinZ() const { return (iLen+1) * sizeof(WCHAR); } // Known length (dwLen excludes zero terminator) HRESULT ReadBin(Scanner *pmf, DWORD dwLen) { rsFree(); iLen = dwLen; pwcString = new WCHAR[dwLen+1]; ASSERT(pwcString != NULL); memcpy(pwcString, pmf->GetRead(), 2*dwLen); pwcString[dwLen] = 0; OK(pmf->Advance(2*dwLen)); return S_OK; } size_t cbBin() const { return iLen * sizeof(WCHAR); } HRESULT CopyBinZ(BYTE **ppb) const { memcpy(*ppb, pwcString, sizeof(WCHAR)*iLen); *ppb += sizeof(WCHAR)*iLen; *(WCHAR*)*ppb = 0; *ppb += sizeof(WCHAR); return S_OK; } HRESULT ReadTok(TextScanner *pmf) { int l; rsFree(); OK(pmf->ReadString(&pwcString, &l)); ASSERT(l < 0x10000 && l >= 0); iLen = (WORD) l; return S_OK; } HRESULT WriteTok(NewFile *pmf) const { OK(pmf->WriteString(pwcString, iLen)); return S_OK; } int GetWords() const { int i; int wc; i = 0; wc = 0; while (i ' ') { i++; } } return wc; } }; //// ResourceVariant // // A widely used alternative of either a Unicode string or a WORD value. class ResourceVariant { ResourceString *prs; ResourceWORD rw; BOOL fString; void rvFree() { if (fString && prs) delete prs; prs = NULL; fString=FALSE; } public: ResourceVariant() {fString=FALSE; prs=NULL;} ~ResourceVariant() {rvFree();} // Copy and assignment constructors required as this is used as the key in an STL map ResourceVariant& operator= (const ResourceVariant &rv) { fString = rv.fString; if (fString) { prs = new ResourceString(*rv.prs); } else { prs = NULL; rw = rv.rw; } return *this; } ResourceVariant(const ResourceVariant &rv) { *this = rv; } void fprint(FILE *fh) const { if (fString) { fprintf(fh, "%S", prs->GetString()); } else { fprintf(fh, "%x", rw.w); } } const BOOL GetfString() const {return fString;} const WORD GetW() const {return rw.w;} void SetW(WORD w) {if (fString) {delete prs; fString = FALSE;}rw.w = w;} const WCHAR *GetString() const {return prs->GetString();} const int GetLength() const {return prs->GetLength();} const int GetWords() const {return fString ? prs->GetWords() : 0;} HRESULT ReadBinFFFFZ(Scanner *pmf) { rvFree(); fString = *(WORD*)pmf->GetRead() != 0xffff; if (fString) { prs = new ResourceString; OK(prs->ReadBinZ(pmf)); } else { OK(pmf->Advance(sizeof(WORD))); OK(rw.ReadBin(pmf)); } return S_OK; } size_t cbBinFFFFZ() const { return fString ? prs->cbBinZ() : rw.cbBin() + sizeof(WORD); } HRESULT CopyBinFFFFZ(BYTE **ppb) const { if (fString) { return prs->CopyBinZ(ppb); } else { *(WORD*)*ppb = 0xFFFF; // Mark as value (*ppb) += sizeof(WORD); return rw.CopyBin(ppb); } } HRESULT ReadBinFFFFL(Scanner *pmf) { rvFree(); fString = *(WORD*)pmf->GetRead() != 0xffff; if (fString) { prs = new ResourceString; OK(prs->ReadBinL(pmf)); } else { OK(pmf->Advance(sizeof(WORD))); OK(rw.ReadBin(pmf)); } return S_OK; } size_t cbBinFFFFL() const { return fString ? prs->cbBinL() : rw.cbBin() + sizeof(WORD); } HRESULT CopyBinFFFFL(BYTE **ppb) const { if (fString) { return prs->CopyBinL(ppb); } else { *(WORD*)*ppb = 0xFFFF; // Mark as value (*ppb) += sizeof(WORD); return rw.CopyBin(ppb); } } HRESULT ReadTok(TextScanner *pmf) { rvFree(); fString = *(char*)pmf->GetRead() == '\"'; if (fString) { prs = new ResourceString; OK(prs->ReadTok(pmf)); } else { OK(rw.ReadTok(pmf)); } return S_OK; } HRESULT WriteTok(NewFile *pmf) const { if (fString) { OK(prs->WriteTok(pmf)); } else { OK(rw.WriteTok(pmf)); } return S_OK; } HRESULT ReadWin32ResDirEntry( Scanner *pmf, const BYTE *pRsrc, IMAGE_RESOURCE_DIRECTORY_ENTRY *pirde) { rvFree(); fString = pirde->NameIsString; if (fString) { prs = new ResourceString; OK(pmf->SetRead(pRsrc + pirde->NameOffset)); OK(prs->ReadBinL(pmf)); } else { OK(pmf->SetRead((BYTE*)&pirde->Id)); OK(rw.ReadBin(pmf)); } return S_OK; } bool operator< (const ResourceVariant &rv) const { int l,c; if (fString != rv.GetfString()) { return !fString; // Numerics before strings } else if (!fString) { return rw.w < rv.GetW(); } else { l = prs->GetLength(); if (l > rv.GetLength()) { l = rv.GetLength(); } c = wcsncmp(prs->GetString(), rv.GetString(), l); if (c==0) { return prs->GetLength() < rv.GetLength(); } else { return c < 0; } } return FALSE; // Equal at all depths } }; //// ResourceKey // // The resource key is the unique identifier of a resource, containing // a resource type, a programmer defined unique id for the resource, and // a language identifier. class ResourceKey { public: int iDepth; ResourceVariant *prvId[3]; ResourceKey() {iDepth=0;} ResourceKey& operator= (const ResourceKey &rk) { int i; iDepth = rk.iDepth; for (i=0; ifprint(fh); fprintf(fh, ","); prvId[1]->fprint(fh); fprintf(fh, ","); prvId[2]->fprint(fh); } LPCWSTR GetResName(int i) const { if (i >= iDepth) { return (LPCWSTR) 0; } if (prvId[i]->GetfString()) { return prvId[i]->GetString(); } else { return (LPCWSTR)prvId[i]->GetW(); } } HRESULT SetLanguage(WORD lid) { ASSERT(iDepth == 3); ASSERT(prvId[2]->GetfString() == FALSE); prvId[2]->SetW(lid); return S_OK; } HRESULT ReadTok(TextScanner *pmf) { prvId[0] = new ResourceVariant; ASSERT(prvId[0] != NULL); OK(prvId[0]->ReadTok(pmf)); iDepth = 1; while (*(char*)pmf->GetRead() == ',') { OK(pmf->Advance(1)); prvId[iDepth] = new ResourceVariant; ASSERT(prvId[iDepth] != NULL); OK(prvId[iDepth]->ReadTok(pmf)); iDepth++; } return S_OK; } HRESULT WriteTok(NewFile *pmf) const { int i; OK(prvId[0]->WriteTok(pmf)); for (i=1; iWriteS(",")); OK(prvId[i]->WriteTok(pmf)); } return S_OK; } bool operator< (const ResourceKey &rk) const { int i,l,c; if (iDepth != rk.iDepth) { return iDepth < rk.iDepth; // Lower depths come first } else { for (i=0; iGetfString() != rk.prvId[i]->GetfString()) { return prvId[i]->GetfString() ? true : false; // Strings come before values } else { if (prvId[i]->GetfString()) { // Compare strings l = prvId[i]->GetLength(); if (l > rk.prvId[i]->GetLength()) { l = rk.prvId[i]->GetLength(); } c = wcsncmp(prvId[i]->GetString(), rk.prvId[i]->GetString(), l); if (c == 0) { if (prvId[i]->GetLength() != rk.prvId[i]->GetLength()) { return prvId[i]->GetLength() < rk.prvId[i]->GetLength(); } } else { return c < 0; } } else { // Compare numeric values if (prvId[i]->GetW() != rk.prvId[i]->GetW()) { return prvId[i]->GetW() < rk.prvId[i]->GetW(); } } } } return FALSE; // Equal at all depths } } }; //// ResourceBinary // // Arbitrary binary resource // // Formatted as lines of hex digits class ResourceBinary : public Resource { protected: // Accessed by ResourceHexDump BYTE *pb; DWORD dwLength; void rbFree() {if (pb) {delete[] pb; pb=NULL;}dwLength = 0;} public: ResourceBinary() {pb = NULL; dwLength = 0;} ~ResourceBinary() {rbFree();} DWORD GetLength() const {return dwLength;} HRESULT ReadTok(TextScanner &mfText) { DWORD i; DWORD dwOffset; DWORD dwCheckOffset; rbFree(); OK(mfText.Expect("Hex;")); OK(mfText.ReadHex(&dwLength)); pb = new BYTE[dwLength]; ASSERT(pb != NULL); if (dwLength <= MAXHEXLINELEN) { // Hex follows on same line OK(mfText.Expect(":")); for (i=0; i MAXHEXLINELEN) { OK(mfText.ExpectLn(" ")); OK(mfText.ReadHex(&dwCheckOffset)); ASSERT(dwOffset == dwCheckOffset); OK(mfText.Expect(":")); for (i=0; i MAXHEXLINELEN) { OK(nfText.WriteS("\r\n ")); OK(nfText.WriteHex(dwOffset, 8)); OK(nfText.WriteS(":")); OK(nfText.WriteHexBuffer(pb+dwOffset, MAXHEXLINELEN)); dwOffset += MAXHEXLINELEN; } // Write remaining bytes, if any OK(nfText.WriteS("\r\n ")); OK(nfText.WriteHex(dwOffset, 8)); OK(nfText.WriteS(":")); OK(nfText.WriteHexBuffer(pb+dwOffset, dwLength - dwOffset)); } return S_OK; } size_t cbBin() const { return dwLength; } HRESULT CopyBin(BYTE **ppb) const { if (dwLength > 0) { memcpy(*ppb, pb, dwLength); *ppb += dwLength; } return S_OK; } int GetItems() const { return 0; } int GetWords() const { return 0; } BOOL CompareBin(const BYTE *pbComp, DWORD dwLen) const { if (dwLength != dwLen) return FALSE; if (dwLength == 0) return TRUE; if (pb ==pbComp) return true; return !memcmp(pb, pbComp, dwLength); } }; //// ResourceHexDump // // Special version of ResourceBinary for generating a hex dump analysis class ResourceHexDump : public ResourceBinary { public: HRESULT WriteTok(NewFile &nfText) const { DWORD i,j; ResourceDWORD rdw; OK(nfText.WriteS("Hexdump,")); OK(nfText.WriteHex(dwLength, 8)); OK(nfText.WriteS(":")); for (i=0; i0) { // Append ASCII interpretation for (j=i-16; j 31) { OK(nfText.WriteBytes(pb+j, 1)); } else { OK(nfText.WriteS(".")); } } } OK(nfText.WriteS("\r\n ")); rdw.dw = i; OK(rdw.WriteTok(&nfText)); OK(nfText.WriteS(": ")); } OK(nfText.WriteHex(pb[i], 2)); OK(nfText.WriteS(" ")); } // Append ANSI interpretation to last line if (dwLength % 16 > 0) { for (i = dwLength % 16 ; i < 16; i++) { if (i % 4 == 0) { OK(nfText.WriteS(" ")); } if (i % 8 == 0) { OK(nfText.WriteS(" ")); } OK(nfText.WriteS(" ")); } } OK(nfText.WriteS(" ")); for (j=dwLength-1 & 0xfffffff0; j 31) { OK(nfText.WriteBytes(pb+j, 1)); } else { OK(nfText.WriteS(".")); } } OK(nfText.WriteLn()); return S_OK; } }; //// Menu32 // // class MenuItem32 { ResourceDWORD rdwType; ResourceDWORD rdwState; ResourceDWORD rdwId; // Extended ID ResourceWORD rwId; // Non-extended ID ResourceWORD rwFlags; ResourceDWORD rdwHelpId; ResourceString rsCaption; BOOL fExtended; public: void SetExtended(BOOL f) {fExtended = f;} int GetWords() const {return rsCaption.GetWords();} virtual HRESULT ReadTok(TextScanner &mfText) { if (!fExtended) { OK(rwFlags.ReadTok(&mfText)); OK(mfText.Expect(",")); if (!(rwFlags.w & MF_POPUP)) { OK(rwId .ReadTok(&mfText)); OK(mfText.Expect(",")); } } else { OK(rdwType .ReadTok(&mfText)); OK(mfText.Expect(",")); OK(rdwState .ReadTok(&mfText)); OK(mfText.Expect(",")); OK(rdwId .ReadTok(&mfText)); OK(mfText.Expect(",")); OK(rwFlags .ReadTok(&mfText)); OK(mfText.Expect(",")); if (rwFlags.w & 1) { OK(rdwHelpId.ReadTok(&mfText)); OK(mfText.Expect(",")); } } OK(rsCaption.ReadTok(&mfText)); return S_OK; } virtual HRESULT ReadBin(Scanner &mfBin) { const BYTE *pb; // For tracking pb = mfBin.GetRead(); if (!fExtended) { OK(rwFlags.ReadBin(&mfBin)); if (!(rwFlags.w & MF_POPUP)) { OK(rwId .ReadBin(&mfBin)); } } else { OK(rdwType .ReadBin(&mfBin)); OK(rdwState.ReadBin(&mfBin)); OK(rdwId .ReadBin(&mfBin)); OK(rwFlags .ReadBin(&mfBin)); } OK(rsCaption.ReadBinZ(&mfBin)); if (fExtended && rwFlags.w & 1) { OK(mfBin.Align(pb, 4)); OK(rdwHelpId.ReadBin(&mfBin)); } return S_OK; } virtual HRESULT WriteTok(NewFile &nfText) const { if (!fExtended) { OK(rwFlags.WriteTok(&nfText)); OK(nfText.WriteS(",")); if (!(rwFlags.w & MF_POPUP)) { OK(rwId .WriteTok(&nfText)); OK(nfText.WriteS(",")); } } else { OK(rdwType .WriteTok(&nfText)); OK(nfText.WriteS(",")); OK(rdwState .WriteTok(&nfText)); OK(nfText.WriteS(",")); OK(rdwId .WriteTok(&nfText)); OK(nfText.WriteS(",")); OK(rwFlags .WriteTok(&nfText)); OK(nfText.WriteS(",")); if (rwFlags.w & 1) { OK(rdwHelpId.WriteTok(&nfText)); OK(nfText.WriteS(",")); } } OK(rsCaption.WriteTok(&nfText)); return S_OK; } virtual size_t cbBin() const { size_t cb; if (!fExtended) { cb = rwFlags.cbBin() + rsCaption.cbBinZ(); if (!(rwFlags.w & MF_POPUP)) { cb += rwId.cbBin(); } } else { cb = rdwType.cbBin() + rdwState.cbBin() + rdwId.cbBin() + rwFlags.cbBin() + rsCaption.cbBinZ(); if (rwFlags.w & 1) { cb = cb + 3 & ~3; cb += rdwHelpId.cbBin(); } } return cb; } virtual HRESULT CopyBin (BYTE **ppb) const { const BYTE * pb; pb = *ppb; if (!fExtended) { OK(rwFlags.CopyBin(ppb)); if (!(rwFlags.w & MF_POPUP)) { OK(rwId .CopyBin(ppb)); } } else { OK(rdwType .CopyBin(ppb)); OK(rdwState.CopyBin(ppb)); OK(rdwId .CopyBin(ppb)); OK(rwFlags .CopyBin(ppb)); } OK(rsCaption.CopyBinZ(ppb)); if (fExtended && rwFlags.w & 1) { while (*ppb - pb & 3) { **ppb = 0; (*ppb)++; } OK(rdwHelpId.CopyBin(ppb)); } return S_OK; } }; class Menu32 : public Resource { ResourceWORD rwVer; ResourceWORD rwHdrSize; ResourceBinary rbHeader; MenuItem32 *pMnuItm; DWORD cItems; BOOL fExtended; public: virtual HRESULT ReadTok(TextScanner &mfText) { DWORD i, iItem; OK(mfText.Expect("Mnu32")); fExtended = *(char*)mfText.GetRead() == 'X'; if (fExtended) { OK(mfText.Expect("X;")); } else { OK(mfText.Expect("N;")); } OK(rwVer .ReadTok(&mfText)); OK(mfText.Expect(",")); OK(rwHdrSize.ReadTok(&mfText)); OK(mfText.Expect(",")); if (fExtended && rwHdrSize.w > 0) { OK(rbHeader.ReadTok(mfText)); OK(mfText.Expect(",")); } OK(mfText.ReadHex(&cItems)); OK(mfText.Expect(":")); pMnuItm = new MenuItem32 [cItems]; ASSERT(pMnuItm != NULL); for (i=0; i 0) { rbHeader.ReadBin(mfBin, rwHdrSize.w); } ASSERT(mfBin.GetRead() - pb < dwLen); // Count menu items if (fExtended) { OK(mfBin.Align(pb, 4)); } pbFirstItem = mfBin.GetRead(); mi.SetExtended(fExtended); while (mfBin.GetRead() - pb < dwLen) { OK(mi.ReadBin(mfBin)); cItems++; if (fExtended) { OK(mfBin.Align(pb, 4)); } } pMnuItm = new MenuItem32 [cItems]; ASSERT(pMnuItm != NULL); // Record the menus OK(mfBin.SetRead(pbFirstItem)); for (i=0; i 0) { OK(rbHeader.WriteTok(nfText)); OK(nfText.WriteS(",")); } OK(nfText.WriteHex(cItems,4)); OK(nfText.WriteS(":")); for (i=0; i 0) { cb += rbHeader.cbBin(); } for (i=0; i 0) { rbHeader.CopyBin(ppb); } for (i=0; i= i); ASSERT(iString < cStrings); while (i 0) { cNonEmpty++; } cStrings++; } ASSERT(mfBin.GetRead() - pb <= dwLen); ASSERT(cStrings == 16); return S_OK; } virtual HRESULT WriteTok(NewFile &nfText) const { int i; ASSERT(cStrings <= 16); OK(nfText.WriteS("Str;")); OK(nfText.WriteHex(cStrings, 2)); OK(nfText.WriteS(",")); OK(nfText.WriteHex(cNonEmpty, 2)); OK(nfText.WriteS(":")); for (i=0; i 0) { OK(nfText.WriteS("\r\n ")); OK(nfText.WriteHex(i, 1)); OK(nfText.WriteS(":")); OK(rs[i].WriteTok(&nfText)); } } return S_OK; } virtual size_t cbBin() const { int i; size_t cb; cb = 0; for (i=0; iExpect(",")); OK(rdwStyle .ReadTok(pmf)); OK(pmf->Expect(",")); OK(rdwExStyle .ReadTok(pmf)); OK(pmf->Expect(",")); OK(rdwSignature.ReadTok(pmf)); OK(pmf->Expect(",")); OK(rdwHelpId .ReadTok(pmf)); OK(pmf->Expect(",")); OK(rwX .ReadTok(pmf)); OK(pmf->Expect(",")); OK(rwY .ReadTok(pmf)); OK(pmf->Expect(",")); OK(rwCx .ReadTok(pmf)); OK(pmf->Expect(",")); OK(rwCy .ReadTok(pmf)); OK(pmf->Expect(",")); OK(rvMenu .ReadTok(pmf)); OK(pmf->Expect(",")); OK(rvClass .ReadTok(pmf)); OK(pmf->Expect(",")); OK(rvTitle .ReadTok(pmf)); if (rdwStyle.dw & DS_SETFONT) { OK(pmf->Expect(",")); OK(rwPointSize.ReadTok(pmf)); OK(pmf->Expect(",")); OK(rwWeight .ReadTok(pmf)); OK(pmf->Expect(",")); OK(rbItalic .ReadTok(pmf)); OK(pmf->Expect(",")); OK(rbCharSet .ReadTok(pmf)); OK(pmf->Expect(",")); OK(rsFaceName .ReadTok(pmf)); } fExtended = rdwSignature.dw != 0; return S_OK; } HRESULT ReadBin(Scanner *pmf) { OK(rdwSignature.ReadBin(pmf)); fExtended = HIWORD(rdwSignature.dw) == 0xFFFF; if (!fExtended) { rdwStyle.dw = rdwSignature.dw; OK(rdwExStyle.ReadBin(pmf)); rdwSignature.dw = 0; rdwHelpId.dw = 0; } else { // Extended dialog adds signature and HelpID OK(rdwHelpId.ReadBin(pmf)); OK(rdwExStyle.ReadBin(pmf)); OK(rdwStyle.ReadBin(pmf)); } OK(rwcDit .ReadBin(pmf)); OK(rwX .ReadBin(pmf)); OK(rwY .ReadBin(pmf)); OK(rwCx .ReadBin(pmf)); OK(rwCy .ReadBin(pmf)); OK(rvMenu .ReadBinFFFFZ(pmf)); OK(rvClass .ReadBinFFFFZ(pmf)); OK(rvTitle .ReadBinFFFFZ(pmf)); if (rdwStyle.dw & DS_SETFONT) { OK(rwPointSize.ReadBin(pmf)); if (!fExtended) { rwWeight.w = 0; rbItalic.b = 0; rbCharSet.b = 0; } else { OK(rwWeight .ReadBin(pmf)); OK(rbItalic .ReadBin(pmf)); OK(rbCharSet .ReadBin(pmf)); } OK(rsFaceName .ReadBinZ(pmf)); } return S_OK; } size_t cbBin() const { size_t cb; cb = rdwStyle .cbBin() // Basics for all dialogs + rdwExStyle .cbBin() + rwcDit .cbBin() + rwX .cbBin() + rwY .cbBin() + rwCx .cbBin() + rwCy .cbBin() + rvMenu .cbBinFFFFZ() + rvClass .cbBinFFFFZ() + rvTitle .cbBinFFFFZ(); if (rdwStyle.dw & DS_SETFONT) { // Facname additions cb += rwPointSize .cbBin() + rsFaceName .cbBinZ(); } if (fExtended) { // Extended dialog addtions cb += rdwSignature .cbBin() + rdwHelpId .cbBin(); if (rdwStyle.dw & DS_SETFONT) { cb += rwWeight .cbBin() + rbItalic .cbBin() + rbCharSet .cbBin(); } } return cb; } HRESULT CopyBin(BYTE **ppb) const { BYTE *pbOriginal; pbOriginal = *ppb; if (!fExtended) { OK(rdwStyle .CopyBin(ppb)); OK(rdwExStyle.CopyBin(ppb)); } else { OK(rdwSignature.CopyBin(ppb)); OK(rdwHelpId .CopyBin(ppb)); OK(rdwExStyle .CopyBin(ppb)); OK(rdwStyle .CopyBin(ppb)); } OK(rwcDit .CopyBin(ppb)); OK(rwX .CopyBin(ppb)); OK(rwY .CopyBin(ppb)); OK(rwCx .CopyBin(ppb)); OK(rwCy .CopyBin(ppb)); OK(rvMenu .CopyBinFFFFZ(ppb)); OK(rvClass .CopyBinFFFFZ(ppb)); OK(rvTitle .CopyBinFFFFZ(ppb)); if (rdwStyle.dw & DS_SETFONT) { OK(rwPointSize.CopyBin(ppb)); if (fExtended) { OK(rwWeight .CopyBin(ppb)); OK(rbItalic .CopyBin(ppb)); OK(rbCharSet.CopyBin(ppb)); } OK(rsFaceName .CopyBinZ(ppb)); } return S_OK; } HRESULT WriteTok(NewFile *pmf) const { OK(rwcDit .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rdwStyle .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rdwExStyle .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rdwSignature.WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rdwHelpId .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rwX .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rwY .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rwCx .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rwCy .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rvMenu .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rvClass .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rvTitle .WriteTok(pmf)); if (rdwStyle.dw & DS_SETFONT) { OK(pmf->WriteS(",")); OK(rwPointSize.WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rwWeight .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rbItalic .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rbCharSet .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rsFaceName .WriteTok(pmf)); } return S_OK; } }; class DialogItem32 { BOOL fExtended; ResourceDWORD rdwStyle; ResourceDWORD rdwHelpId; ResourceDWORD rdwExStyle; ResourceWORD rwX; ResourceWORD rwY; ResourceWORD rwCx; ResourceWORD rwCy; ResourceWORD rwId; // Normal ResourceDWORD rdwId; // Extended ResourceVariant rvClass; ResourceVariant rvTitle; ResourceWORD rwcbRawData; // Raw data size (extended only) ResourceBinary rbRawData; ResourceWORD rwDummy; // Replaces raw data on normal dialogs public: void SetExtended(BOOL f) {fExtended = f;} int GetWords() const {return rvTitle.GetWords();} HRESULT ReadTok(TextScanner *pmf) { if (fExtended) { OK(rdwId.ReadTok(pmf)); OK(pmf->Expect(",")); } else { OK(rwId.ReadTok(pmf)); OK(pmf->Expect(",")); } OK(rdwStyle .ReadTok(pmf)); OK(pmf->Expect(",")); OK(rdwExStyle .ReadTok(pmf)); OK(pmf->Expect(",")); OK(rdwHelpId .ReadTok(pmf)); OK(pmf->Expect(",")); OK(rwX .ReadTok(pmf)); OK(pmf->Expect(",")); OK(rwY .ReadTok(pmf)); OK(pmf->Expect(",")); OK(rwCx .ReadTok(pmf)); OK(pmf->Expect(",")); OK(rwCy .ReadTok(pmf)); OK(pmf->Expect(",")); OK(rvClass .ReadTok(pmf)); OK(pmf->Expect(",")); OK(rvTitle .ReadTok(pmf)); OK(pmf->Expect(",")); if (fExtended) { OK(rbRawData.ReadTok(*pmf)); ASSERT(rbRawData.GetLength() < 0x10000); rwcbRawData.w = (WORD)rbRawData.GetLength(); } else { OK(rwDummy.ReadTok(pmf)); } return S_OK; } HRESULT ReadBin(Scanner *pmf) { if (!fExtended) { OK(rdwStyle.ReadBin(pmf)); OK(rdwExStyle.ReadBin(pmf)); rdwHelpId.dw = 0; } else { OK(rdwHelpId.ReadBin(pmf)); OK(rdwExStyle.ReadBin(pmf)); OK(rdwStyle.ReadBin(pmf)); } OK(rwX .ReadBin(pmf)); OK(rwY .ReadBin(pmf)); OK(rwCx.ReadBin(pmf)); OK(rwCy.ReadBin(pmf)); if (fExtended) { OK(rdwId.ReadBin(pmf)); } else { OK(rwId.ReadBin(pmf)); } OK(rvClass.ReadBinFFFFZ(pmf)); OK(rvTitle.ReadBinFFFFZ(pmf)); if (fExtended) { OK(rwcbRawData.ReadBin(pmf)); OK(rbRawData.ReadBin(*pmf, rwcbRawData.w)); } else { OK(rwDummy.ReadBin(pmf)); } return S_OK; } size_t cbBin() const { size_t cb; cb = rdwStyle .cbBin() + rdwExStyle .cbBin() + rwX .cbBin() + rwY .cbBin() + rwCx .cbBin() + rwCy .cbBin() + rvClass .cbBinFFFFZ() + rvTitle .cbBinFFFFZ(); if (!fExtended) { cb += rwId .cbBin() + rwDummy .cbBin(); } else { cb += rdwId .cbBin() + rdwHelpId .cbBin() + rbRawData .cbBin() + rwcbRawData .cbBin(); } return cb; } HRESULT CopyBin(BYTE **ppb) const { BYTE *pbOriginal; pbOriginal = *ppb; if (!fExtended) { OK(rdwStyle.CopyBin(ppb)); OK(rdwExStyle.CopyBin(ppb)); } else { OK(rdwHelpId.CopyBin(ppb)); OK(rdwExStyle.CopyBin(ppb)); OK(rdwStyle.CopyBin(ppb)); } OK(rwX .CopyBin(ppb)); OK(rwY .CopyBin(ppb)); OK(rwCx.CopyBin(ppb)); OK(rwCy.CopyBin(ppb)); if (fExtended) { OK(rdwId.CopyBin(ppb)); } else { OK(rwId.CopyBin(ppb)); } OK(rvClass.CopyBinFFFFZ(ppb)); OK(rvTitle.CopyBinFFFFZ(ppb)); if (fExtended) { OK(rwcbRawData.CopyBin(ppb)); OK(rbRawData.CopyBin(ppb)); } else { OK(rwDummy.CopyBin(ppb)); } return S_OK; } HRESULT WriteTok(NewFile *pmf) const { if (fExtended) { OK(rdwId.WriteTok(pmf)); OK(pmf->WriteS(",")); } else { OK(rwId.WriteTok(pmf)); OK(pmf->WriteS(",")); } OK(rdwStyle .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rdwExStyle .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rdwHelpId .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rwX .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rwY .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rwCx .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rwCy .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rvClass .WriteTok(pmf)); OK(pmf->WriteS(",")); OK(rvTitle .WriteTok(pmf)); OK(pmf->WriteS(",")); if (fExtended) { OK(rbRawData.WriteTok(*pmf)); } else { OK(rwDummy.WriteTok(pmf)); } return S_OK; } }; //// Dialog32 // // class Dialog32 : public Resource { DialogHeader32 DlgHdr; // Header DialogItem32 *pDlgItm; // Array of items BOOL fExtended; int cItems; public: Dialog32() {pDlgItm = NULL;}; virtual HRESULT ReadTok(TextScanner &mfText) { DWORD i, dwSeq; OK(mfText.Expect("Dlg32")); fExtended = *(char*)mfText.GetRead() == 'X'; if (fExtended) { OK(mfText.Expect("X;")); } else { OK(mfText.Expect("N;")); } OK(DlgHdr.ReadTok(&mfText)); ASSERT(fExtended == DlgHdr.GetExtended()); cItems = DlgHdr.GetItemCount(); pDlgItm = new DialogItem32 [cItems]; ASSERT(pDlgItm != NULL); for (i=0; irwValueLength.ReadBin(&mfBinary)); wValueLength = *(WORD*)mfBinary.GetRead(); OK(mfBinary.Advance(2)); if (rwLength.w > 0) { // Block is not empty *ppvb = new VersionBlock; ASSERT(*ppvb != NULL); (*ppvb)->pNext = NULL; (*ppvb)->pSub = NULL; (*ppvb)->iDepth = iDepth; OK((*ppvb)->rwbText.ReadBin(&mfBinary)); OK((*ppvb)->rsKey.ReadBinZ(&mfBinary)); OK(mfBinary.Align(pbResource, 4)); (*ppvb)->bValue = wValueLength > 0; if ((*ppvb)->bValue) { if ((*ppvb)->rwbText.w == 0) { // Binary value OK((*ppvb)->rbValue.ReadBin(mfBinary, wValueLength)); } else { // WCHAR string. // Some writers include a zero terminator, some don't. // Some incode zero codepoints inside the string // Some writers get the length right, some dont. // msvcrt20.dll text lengths are too long. // Choose a length that is min(ValueLength, length remaining), // and then drop any trailing zeros. // Clip ValueLength to length remaining ASSERT(mfBinary.GetRead() < pbBlock + rwLength.w); if (wValueLength > (pbBlock + rwLength.w - mfBinary.GetRead()) / 2) { wValueLength = (pbBlock + rwLength.w - mfBinary.GetRead()) / 2; } // Clip trailing zeros while ( wValueLength > 0 && ((WCHAR*)mfBinary.GetRead())[wValueLength-1] == 0) { wValueLength--; } // Extract whatever remains OK((*ppvb)->rsValue.ReadBin(&mfBinary, wValueLength)); // Check that there's nothing being lost between the end of // the string and the end of the block. // Note that we assume here that blocks containing text values // cannot have variety of messes that value text is stored in // in exisiting executables. while (mfBinary.GetRead() < pbBlock + rwLength.w) { ASSERT(*(WCHAR*)mfBinary.GetRead() == 0); OK(mfBinary.Advance(2)); } } OK(mfBinary.Align(pbResource, 4)); } if (mfBinary.GetRead() - pbBlock < rwLength.w) { ASSERT(mfBinary.GetLimit() > mfBinary.GetRead()); // Read subblocks OK(ReadBinVersionBlocks( mfBinary, rwLength.w - (mfBinary.GetRead() - pbBlock), &((*ppvb)->pSub), iDepth + 1, &(*ppvb)->cSub)); } if (mfBinary.GetRead() < pbResource + dwLength) { // Prepare to read more blocks at this level ppvb = &((*ppvb)->pNext); } } (*cSub)++; } return S_OK; } HRESULT WriteTokVersionBlocks( NewFile &nfText, VersionBlock *pvb) const { while (pvb) { OK(nfText.WriteS("\r\n ")); OK(nfText.WriteHex(pvb->iDepth, 2)); OK(nfText.WriteS(",")); OK(pvb->rsKey.WriteTok(&nfText)); if (pvb->bValue) { OK(nfText.WriteS("=")); if (pvb->rwbText.w == 0) { OK(pvb->rbValue.WriteTok(nfText)); // Binary value } else { OK(pvb->rsValue.WriteTok(&nfText)); // String value } } if (pvb->pSub) { OK(nfText.WriteS(";")); OK(nfText.WriteHex(pvb->cSub,4)); OK(WriteTokVersionBlocks(nfText, pvb->pSub)); } pvb = pvb->pNext; } return S_OK; } HRESULT ReadTokVersionBlocks( TextScanner &mfText, VersionBlock **ppvb, int iDepth, DWORD *pcBlocks) { int i; DWORD dwRecordedDepth; OK(mfText.ReadHex(pcBlocks)); for (i=0; i<*pcBlocks; i++) { *ppvb = new VersionBlock; ASSERT(*ppvb != NULL); (*ppvb)->pNext = NULL; (*ppvb)->pSub = NULL; (*ppvb)->iDepth = iDepth; (*ppvb)->cSub = 0; OK(mfText.ExpectLn(" ")); OK(mfText.ReadHex(&dwRecordedDepth)); ASSERT(dwRecordedDepth == iDepth); OK(mfText.Expect(",")); OK((*ppvb)->rsKey.ReadTok(&mfText)); if (*(char*)mfText.GetRead() != '=') { // No value (*ppvb)->rwbText.w = 1; (*ppvb)->bValue = FALSE; } else { OK(mfText.Expect("=")); (*ppvb)->bValue = TRUE; if (*(char*)mfText.GetRead() == '\"') { // String value (*ppvb)->rwbText.w = 1; OK((*ppvb)->rsValue.ReadTok(&mfText)); } else { // Binary value (*ppvb)->rwbText.w = 0; OK((*ppvb)->rbValue.ReadTok(mfText)); } } if (*(char*)mfText.GetRead() == ';') { // Process subkeys OK(mfText.Expect(";")); OK(ReadTokVersionBlocks( mfText, &(*ppvb)->pSub, iDepth+1, &(*ppvb)->cSub)); } // Prepare to add another block ppvb = &(*ppvb)->pNext; } return S_OK; } size_t cbBinVersionBlocks(const VersionBlock *pvb) const { size_t cb; cb = 6; // Header cb += pvb->rsKey.cbBinZ(); cb = cb+3 & ~3; // DWORD align if (pvb->bValue) { if (pvb->rwbText.w) { cb += pvb->rsValue.cbBinZ(); } else { cb += pvb->rbValue.cbBin(); } cb = cb + 3 & ~3; // DWORD align } if (pvb->pSub != NULL) { pvb = pvb->pSub; while (pvb) { cb += cbBinVersionBlocks(pvb); pvb = pvb->pNext; } } return cb; } HRESULT CopyBinVersionBlocks( BYTE **ppb, const VersionBlock *pvb) const { const BYTE *pbResource; size_t cb; pbResource = *ppb; while (pvb != NULL) { cb = cbBinVersionBlocks(pvb); ASSERT(cb < 0x1000); *((WORD*)(*ppb)) = (WORD)cb; (*ppb) += 2; // Generate value length if (pvb->bValue) { if (pvb->rwbText.w) { *((WORD*)(*ppb)) = pvb->rsValue.GetLength()+1; } else { *((WORD*)(*ppb)) = pvb->rbValue.GetLength(); } } else { *((WORD*)(*ppb)) = 0; } (*ppb) += 2; OK(pvb->rwbText.CopyBin(ppb)); OK(pvb->rsKey.CopyBinZ(ppb)); while (*ppb - pbResource & 3) { **ppb = 0; (*ppb)++; } if (pvb->bValue) { if (pvb->rwbText.w) { OK(pvb->rsValue.CopyBinZ(ppb)); } else { OK(pvb->rbValue.CopyBin(ppb)); } while (*ppb - pbResource & 3) { **ppb = 0; (*ppb)++; } } if (pvb->pSub) { OK(CopyBinVersionBlocks(ppb, pvb->pSub)); } pvb = pvb->pNext; } return S_OK; } int GetItemsVersionBlocks(const VersionBlock *pvb) const { int iItems = 0; while (pvb != NULL) { if ( pvb->bValue && pvb->rwbText.w != 0) { iItems++; } iItems += GetItemsVersionBlocks(pvb->pSub); pvb = pvb->pNext; } return iItems; } int GetWordsVersionBlocks(const VersionBlock *pvb) const { int iWords = 0; while (pvb != NULL) { if ( pvb->bValue && pvb->rwbText.w != 0) { iWords += pvb->rsValue.GetWords(); } iWords += GetWordsVersionBlocks(pvb->pSub); pvb = pvb->pNext; } return iWords; } public: virtual HRESULT ReadTok(TextScanner &mfText) { OK(mfText.Expect("Ver;")); return ReadTokVersionBlocks(mfText, &pvb, 0, &cBlocks); } virtual HRESULT WriteTok(NewFile &nfText) const { OK(nfText.WriteS("Ver;")); OK(nfText.WriteHex(cBlocks,4)); return WriteTokVersionBlocks(nfText, pvb); } virtual HRESULT ReadBin(Scanner &mfBinary, DWORD dwLen) { return ReadBinVersionBlocks(mfBinary, dwLen, &pvb, 0, &cBlocks); } virtual size_t cbBin() const { const VersionBlock *pvbTop; size_t cb; cb = 0; pvbTop = pvb; while (pvbTop) { cb += cbBinVersionBlocks(pvbTop); pvbTop = pvbTop->pNext; } return cb; } virtual HRESULT CopyBin (BYTE **ppb) const { return CopyBinVersionBlocks(ppb, pvb); } int GetItems() const { return GetItemsVersionBlocks(pvb); } int GetWords() const { return GetWordsVersionBlocks(pvb); } VersionBlock *FindStringFileInfo(WCHAR* pwcStr) const { VersionBlock *pvbRider; if ( pvb && pvb->pSub && pvb->pSub->pSub && pvb->pSub->pSub->pSub) { pvbRider = pvb->pSub->pSub->pSub; while ( pvbRider && wcscmp(pvbRider->rsKey.GetString(), pwcStr) != 0) { pvbRider = pvbRider->pNext; } return pvbRider; } else { return NULL; } } ResourceString* GetStringFileInfo(WCHAR *pwcStr) { VersionBlock *pvbStringFileInfo; pvbStringFileInfo = FindStringFileInfo(pwcStr); if (pvbStringFileInfo) { return &pvbStringFileInfo->rsValue; } else { return NULL; } } void SetStringFileInfo(WCHAR *pwcStr, ResourceString *prs) { VersionBlock *pvbStringFileInfo; pvbStringFileInfo = FindStringFileInfo(pwcStr); if (pvbStringFileInfo) { pvbStringFileInfo->rsValue = *prs; } } ResourceBinary* GetBinaryInfo() const { if (pvb) { return &pvb->rbValue; } return NULL; } void SetBinaryInfo(const ResourceBinary *prb) { if (pvb) { pvb->rbValue = *prb; } } }; //// Statistic collection // // struct ResourceStats { int cResources; // Number of resources with this resource type int cItems; // Number of items with this resource type int cWords; // Number of words in strings in this resource type int cBytes; // Number of bytes used by resources of this type }; typedef map < ResourceVariant, ResourceStats, less > MappedResourceStats; MappedResourceStats ResourceStatsMap; //// Define our own LangId class so that primary languages sort together. // // class LangId { public: DWORD dwLang; LangId(DWORD dwL) {dwLang = dwL;}; bool operator< (LangId li) const { if (PRIMARYLANGID(dwLang) != PRIMARYLANGID(li.dwLang)) { return PRIMARYLANGID(dwLang) < PRIMARYLANGID(li.dwLang) ? true : false; } else { return SUBLANGID(dwLang) < SUBLANGID(li.dwLang) ? true : false; } } }; typedef map < LangId, ResourceStats, less > MappedLanguageStats; MappedLanguageStats LanguageStatsMap; //// UpdateStats // // const ResourceStats ZeroStats = {0}; HRESULT UpdateStats( const ResourceKey &rk, int cItems, int cWords, int cBytes) { if (ResourceStatsMap.count(*rk.prvId[0]) == 0) { ResourceStatsMap[*rk.prvId[0]] = ZeroStats; } if (LanguageStatsMap.count(rk.prvId[2]->GetW()) == 0) { LanguageStatsMap[rk.prvId[2]->GetW()] = ZeroStats; } ResourceStatsMap[*rk.prvId[0]].cResources += 1; ResourceStatsMap[*rk.prvId[0]].cItems += cItems; ResourceStatsMap[*rk.prvId[0]].cWords += cWords; ResourceStatsMap[*rk.prvId[0]].cBytes += cBytes; LanguageStatsMap[rk.prvId[2]->GetW()].cResources += 1; LanguageStatsMap[rk.prvId[2]->GetW()].cItems += cItems; LanguageStatsMap[rk.prvId[2]->GetW()].cWords += cWords; LanguageStatsMap[rk.prvId[2]->GetW()].cBytes += cBytes; return S_OK; } //// IsResourceWanted // // Returns whether a given resource key was requested on the command line BOOL IsResourceWanted(const ResourceKey &rk) { if (rk.prvId[0]->GetfString()) { return g_dwProcess & PROCESSOTH; } else { switch (rk.prvId[0]->GetW()) { case 1: return g_dwProcess & PROCESSCUR; case 2: return g_dwProcess & PROCESSBMP; case 3: return g_dwProcess & PROCESSICO; case 4: return g_dwProcess & PROCESSMNU; case 5: return g_dwProcess & PROCESSDLG; case 6: return g_dwProcess & PROCESSSTR; case 7: return g_dwProcess & PROCESSFDR; case 8: return g_dwProcess & PROCESSFNT; case 9: return g_dwProcess & PROCESSACC; case 10: return g_dwProcess & PROCESSRCD; case 11: return g_dwProcess & PROCESSMSG; case 16: return g_dwProcess & PROCESSVER; case 240: case 1024: case 23: case 2110: return g_dwProcess & PROCESSBIN; case 2200: return g_dwProcess & PROCESSINF; default: return g_dwProcess & PROCESSOTH; } } return FALSE; } //// NewResource // // Returns a pointer to a newly allocated subclass of Resource // suitable for the given resource type. Resource *NewResource(const ResourceVariant &rv) { if (rv.GetfString()) { return new ResourceBinary; } else { switch (rv.GetW()) { case 1: return new ResourceBinary; case 2: return new ResourceBinary; case 3: return new ResourceBinary; case 4: return new Menu32; case 5: return new Dialog32; case 6: return new String32; case 7: return new ResourceBinary; case 8: return new ResourceBinary; case 9: return new ResourceBinary; case 10: return new ResourceBinary; case 11: return new ResourceBinary; case 16: return new VersionInfo; case 240: case 1024: case 23: case 2110: return new ResourceBinary; case 2200: return new ResourceBinary; default: return new ResourceBinary; } } } //// Rsrc internal resource directory // // Rsrc stores resources in an STL 'map' structure. class ResourceValue { public: const BYTE *pb; // Pointer into mapped file DWORD cb; // Count of bytes in the value Resource *pResource; DWORD dwCodePage; // Codepage from Win32 resource index - not very useful! ResourceValue() {pb = NULL; pResource = NULL; cb=0; dwCodePage=0;} /* ~ResourceValue() {}; // Don't destroy content on destruction ResourceValue& operator= (const ResourceValue &rv) { pb = rv.pb; cb = rv.cb; pResource = rv.pResource; dwCodePage = rv.dwCodePage; return *this; } ResourceValue(const ResourceValue &rv) { *this = rv; } */ //// CreateImage // // Convert interpreted resource to binary image. // Used to prepare resources read from tokens for // comparison and update. HRESULT CreateImage() { BYTE *pbBuf; ASSERT(pb == NULL); ASSERT(pResource != NULL); cb = pResource->cbBin(); pbBuf = new BYTE [cb]; ASSERT(pbBuf != NULL); pb = pbBuf; OK(pResource->CopyBin(&pbBuf)); ASSERT(pbBuf - pb == cb); // This may be too strong? It has not failed yet! ASSERT(pbBuf - pb <= cb); // This must be true - otherwise we wrote past the end of the buffer return S_OK; } //// InterpretImage // // Convert binary image to interpreted resource. // Used to prepare resources read from executable for // writing as tokens. HRESULT InterpretImage(const ResourceKey &rk) { ASSERT(pb != NULL); ASSERT(pResource == NULL); ASSERT(rk.iDepth == 3); ASSERT(!rk.prvId[2]->GetfString()); if (g_dwOptions & OPTHEXDUMP) { pResource = new ResourceHexDump; } else { // This is a resource extraction to tokens so interpret content pResource = NewResource(*rk.prvId[0]); } ASSERT(pResource != NULL); OK(pResource->ReadBin(Scanner(pb, cb), cb)); pb = NULL; cb = 0; return S_OK; } //// Checksum // // Returns DWORD checksum of binary content of resource DWORD Checksum() { DWORD dw; DWORD *pdw; int i,l; ASSERT(pb != NULL); l = cb >> 2; // Length in whole DWORDS pdw = (DWORD*)pb; dw = 0; for (i=0; i2) dw ^= pb[cb-3] << 16; if (l>1) dw ^= pb[cb-2] << 8; if (l>0) dw ^= pb[cb-1]; return dw; } }; class ResourceMap : public map < ResourceKey, ResourceValue*, less > { public: //// AddResource // // HRESULT AddResource(ResourceKey &rk, const BYTE *pb, DWORD cb, DWORD dwCodePage) { ResourceValue *prv; // Build a resource structure prv = new ResourceValue; prv->pb = pb; prv->cb = cb; prv->dwCodePage = dwCodePage; prv->pResource = NULL; // Process add options if (IsResourceWanted(rk)) { // Insert resource details into STL map if (this->count(rk) != 0) { fprintf(stderr, "%s(", g_szExecutable); rk.fprint(stderr); fprintf(stderr, "): error RSRC500: Corrupt executable - resource appears more than once\n"); g_fError = TRUE; return E_FAIL; } (*this)[rk] = prv; } else { g_cResourcesIgnored++; } return S_OK; } //// CopyResources // // Takes a copy so the original mapped file can be closed HRESULT CopyResources() { iterator rmi; BYTE *pb; for (rmi = begin(); rmi != end(); rmi++) { pb = new BYTE[rmi->second->cb]; ASSERT(pb != NULL); memcpy(pb, rmi->second->pb, rmi->second->cb); rmi->second->pb = pb; } return S_OK; } //// WriteTokens // // Writes the content of the map as a token file. // // If an unlocalised map is provided, bit for bit identical // resources are written as a reference to the unlocalised // version language, rather than in full. HRESULT WriteTokens(NewFile &nfText, ResourceMap *prmUnlocalised) { iterator rmi; iterator rmiUnlocalised; ResourceKey rkUnlocalised; for (rmi = begin(); rmi != end(); rmi++) { g_cResourcesExtracted++; // Write resource key and codepage OK(rmi->first.WriteTok(&nfText)); OK(nfText.WriteS(";")); OK(nfText.WriteHex(rmi->second->dwCodePage, 8)); if (prmUnlocalised) { // Add unlocalised checksum and language rkUnlocalised = rmi->first; rkUnlocalised.SetLanguage(g_liUnlocalized); rmiUnlocalised = prmUnlocalised->find(rkUnlocalised); if (rmiUnlocalised == prmUnlocalised->end()) { fprintf(stderr, "%s(", g_szResources); rmi->first.fprint(stderr); fprintf(stderr, "): warning RSRC100: Localised resource has no corresponding unlocalised resource in %s\n", g_szUnloc); g_fWarn = TRUE; } else { // Put out details of the unlocalised resource OK(nfText.WriteS(",")); OK(nfText.WriteHex(rmiUnlocalised->second->Checksum(), 8)); OK(nfText.WriteS(",")); OK(nfText.WriteHex(g_liUnlocalized, 4)); } } OK(nfText.WriteS(";")); // Check whether resource needs to be written in full if ( prmUnlocalised && rmiUnlocalised != prmUnlocalised->end() && rmiUnlocalised->second->cb == rmi->second->cb && memcmp(rmi->second->pb, rmiUnlocalised->second->pb, rmi->second->cb) == 0) { // Bit for bit match with unlocalised executable OK(nfText.WriteS("Unloc")); } else { // Doesn't match - write it in full OK(rmi->second->InterpretImage(rmi->first)); OK(rmi->second->pResource->WriteTok(nfText)); } OK(nfText.WriteLn()); } return S_OK; } //// UpdateWin32Executable // // HRESULT UpdateWin32Executable(char *pExecutable) { iterator rmi; HANDLE hUpdate; hUpdate = BeginUpdateResourceA(pExecutable, TRUE); // Will replace all resources MUST(hUpdate != NULL ? S_OK : E_FAIL, ("RSRC : error RSRC600: BeginUpdateResource failed on %s\n", pExecutable)); for (rmi = begin(); rmi != end(); rmi++) { ASSERT(rmi->first.iDepth == 3); ASSERT(!rmi->first.prvId[2]->GetfString()); // Create binary image of resource if necessary if (rmi->second->pb == NULL) { OK(rmi->second->CreateImage()); } // Use NT resource API to update resource binary image in executable if (!UpdateResourceW( hUpdate, rmi->first.GetResName(0), rmi->first.GetResName(1), rmi->first.prvId[2]->GetW(), (void*)rmi->second->pb, rmi->second->cb)) { EndUpdateResourceW(hUpdate, TRUE); // Discard all requested updates g_fError = TRUE; fprintf(stderr, "RSRC : error RSRC601: UpdateResourceW failed on %s\n", pExecutable); return HRESULT_FROM_WIN32(GetLastError()); } } if (!EndUpdateResourceW(hUpdate, FALSE)) { // Apply all requested updates fprintf(stderr, "RSRC : error RSRC602: EndUpdateResourceW failed on %s\n", pExecutable); g_fError = TRUE; return HRESULT_FROM_WIN32(GetLastError()); } return S_OK; } }; class SymbolFile { MappedFile *m_pmfSymbolFile; IMAGE_SEPARATE_DEBUG_HEADER *m_pDebugHeader; public: DWORD GetChecksum() const {return m_pDebugHeader->CheckSum;} DWORD GetTimeDateStamp() const {return m_pDebugHeader->TimeDateStamp;} DWORD GetImageBase() const {return m_pDebugHeader->ImageBase;} DWORD GetSizeOfImage() const {return m_pDebugHeader->SizeOfImage;} void SetChecksum (DWORD dwChecksum) {m_pDebugHeader->CheckSum = dwChecksum;} void SetTimeDateStamp (DWORD dwTimeDateStamp) {m_pDebugHeader->TimeDateStamp = dwTimeDateStamp;} void SetImageBase (DWORD dwImageBase) {m_pDebugHeader->ImageBase = dwImageBase;} void SetSizeOfImage (DWORD dwSizeOfImage) {m_pDebugHeader->SizeOfImage = dwSizeOfImage;} HRESULT Open(MappedFile *pmfSymbolFile) { m_pmfSymbolFile = pmfSymbolFile; m_pDebugHeader = (IMAGE_SEPARATE_DEBUG_HEADER*) pmfSymbolFile->GetFile(); ASSERT(m_pDebugHeader->Signature == IMAGE_SEPARATE_DEBUG_SIGNATURE); return S_OK; } }; class Win32Executable : public MappedFile { IMAGE_NT_HEADERS *m_pNtHeader; IMAGE_SECTION_HEADER *m_pSections; int m_iSectionRsrc; int m_iSectionRsrc1; // For scanning ResourceKey m_rk; // Current resource key HRESULT MapDirectory( ResourceMap &rm, const BYTE *pbRsrc, // Resource block int dwOffset, // Directory offset relative to m_pbRsrc int iLevel) { // Directory level being scanned IMAGE_RESOURCE_DIRECTORY *pird; IMAGE_RESOURCE_DIRECTORY_ENTRY *pEntries; IMAGE_RESOURCE_DATA_ENTRY *pirde; const BYTE *pb; int i; pird = (IMAGE_RESOURCE_DIRECTORY*) (pbRsrc+dwOffset); pEntries = (IMAGE_RESOURCE_DIRECTORY_ENTRY*) (pird+1); for (i=0; iNumberOfNamedEntries + pird->NumberOfIdEntries; i++) { // Read the ID from the directory ASSERT(iLevel<3); m_rk.iDepth = iLevel+1; m_rk.prvId[iLevel] = new ResourceVariant; ASSERT(m_rk.prvId[iLevel] != NULL); OK(m_rk.prvId[iLevel]->ReadWin32ResDirEntry(this, pbRsrc, pEntries+i)); if (pEntries[i].DataIsDirectory) { // This is a directory node. Recurse to scan that directory. OK(MapDirectory(rm, pbRsrc, pEntries[i].OffsetToDirectory, iLevel+1)); } else { // We've reached a leaf node, establish the data address and // add the resource to the map. pirde = (IMAGE_RESOURCE_DATA_ENTRY*) (pbRsrc + pEntries[i].OffsetToData); // Note that even when the resource data is in .rsrc1, the // directory entry is usually in .rsrc. if (pirde->OffsetToData < m_pSections[m_iSectionRsrc].VirtualAddress + m_pSections[m_iSectionRsrc].SizeOfRawData) { // Data is in section .rsrc ASSERT(pirde->OffsetToData >= m_pSections[m_iSectionRsrc].VirtualAddress); pb = GetFile() + m_pSections[m_iSectionRsrc].PointerToRawData + pirde->OffsetToData - m_pSections[m_iSectionRsrc].VirtualAddress; } else { // Data is in section .rsrc1 ASSERT(pirde->OffsetToData >= m_pSections[m_iSectionRsrc1].VirtualAddress); ASSERT(pirde->OffsetToData < m_pSections[m_iSectionRsrc1].VirtualAddress + m_pSections[m_iSectionRsrc1].SizeOfRawData); pb = GetFile() + m_pSections[m_iSectionRsrc1].PointerToRawData + pirde->OffsetToData - m_pSections[m_iSectionRsrc1].VirtualAddress; } OK(rm.AddResource(m_rk, pb, pirde->Size, pirde->CodePage)); } } return S_OK; } public: DWORD GetChecksum() const {return m_pNtHeader->OptionalHeader.CheckSum;} DWORD GetTimeDateStamp() const {return m_pNtHeader->FileHeader.TimeDateStamp;} DWORD GetImageBase() const {return m_pNtHeader->OptionalHeader.ImageBase;} DWORD GetSizeOfImage() const {return m_pNtHeader->OptionalHeader.SizeOfImage;} BOOL Is64BitImage() const {return m_pNtHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC;} void SetChecksum(DWORD dwChecksum) {m_pNtHeader->OptionalHeader.CheckSum=dwChecksum;} HRESULT Open(const char *pcFileName, BOOL fWrite) { int i; OK(MappedFile::Open(pcFileName, fWrite)); MUST(( *(WORD*)m_pStart == IMAGE_DOS_SIGNATURE && *(WORD*)(m_pStart+0x18) >= 0x40) // WinVer >= 4 ? S_OK : E_FAIL, ("RSRC : error RSRC501: %s is not an executable file\n", pcFileName)); m_pNtHeader = (IMAGE_NT_HEADERS*)(m_pStart + *(WORD*)(m_pStart+0x3c)); MUST((m_pNtHeader->Signature == IMAGE_NT_SIGNATURE) ? S_OK : E_FAIL, ("RSRC : error RSRC502: %s is not a Win32 executable file\n", pcFileName)); if (Is64BitImage()) { m_pSections = (IMAGE_SECTION_HEADER*)( (BYTE *) (m_pNtHeader+1) + (IMAGE_SIZEOF_NT_OPTIONAL64_HEADER - IMAGE_SIZEOF_NT_OPTIONAL32_HEADER)); } else { m_pSections = (IMAGE_SECTION_HEADER*)(m_pNtHeader+1); } m_iSectionRsrc = -1; m_iSectionRsrc1 = -1; // Locate the one or two resource sections for (i=0; iFileHeader.NumberOfSections; i++) { if (strcmp((char*)m_pSections[i].Name, ".rsrc") == 0) { m_iSectionRsrc = i; } else if (strcmp((char*)m_pSections[i].Name, ".rsrc") == 0) { m_iSectionRsrc1 = i; } } MUST(m_iSectionRsrc >= 0 ? S_OK : E_FAIL, ("RSRC : error RSRC503: No resources in %s\n", pcFileName)); ASSERT(m_iSectionRsrc > -1); // Check for presence of resources return S_OK; } //// MapResourceDirectory // // Extract the resource directory into an STL map. HRESULT MapResourceDirectory(ResourceMap &rm) { OK(MapDirectory( rm, m_pStart + m_pSections[m_iSectionRsrc].PointerToRawData, 0, 0)); if (m_iSectionRsrc1 >= 0) { OK(MapDirectory( rm, m_pStart + m_pSections[m_iSectionRsrc1].PointerToRawData, 0, 0)); } return S_OK; } }; //// High level operation // // Controlling routines for the various modes of operation ResourceMap rmExecutable; // Read and/or update ResourceMap rmUnlocalised; // '-u' option - unlocalised resources for comparison //// ApplyResource // // Applies a given key and value to the executable resource map. // // Tokens are merged with those already loaded from the executable // according to the update mode (append or replace). HRESULT ApplyResource(ResourceKey &rk, ResourceValue *prv) { ResourceKey rkUnloc; VersionInfo *pviLoc; VersionInfo *pviUnloc; ResourceMap::iterator rmiUnloc; // Establish equivalent unlocalised key rkUnloc = rk; rkUnloc.SetLanguage(g_liUnlocalized); // First ensure that we keep the unlocalised version info, if we can if ( !(g_dwOptions & OPTVERSION) && !rk.prvId[0]->GetfString() && rk.prvId[0]->GetW() == 16 // VersionInfo && (rmiUnloc=rmExecutable.find(rkUnloc)) != NULL && rmiUnloc != rmExecutable.end()) { // Special case - keep unlocalised file and product versions if (rmiUnloc->second->pResource == NULL) { rmiUnloc->second->InterpretImage(rmiUnloc->first); } pviLoc = static_cast(prv->pResource); pviUnloc = static_cast(rmiUnloc->second->pResource); if (pviLoc && pviUnloc) { pviLoc->SetStringFileInfo(L"FileVersion", pviUnloc->GetStringFileInfo(L"FileVersion")); pviLoc->SetStringFileInfo(L"ProductVersion", pviUnloc->GetStringFileInfo(L"ProductVersion")); pviLoc->SetBinaryInfo(pviUnloc->GetBinaryInfo()); } } if (rk.prvId[2]->GetW() == g_liUnlocalized) { // New token is not localized fprintf(stderr, "%s(", g_szResources); rk.fprint(stderr); if (rmExecutable.count(rk) == 0) { fprintf(stderr, "): warning RSRC110: Unlocalised resource from token file appended to executable\n"); g_fWarn = TRUE; g_cResourcesAppended++; } else { fprintf(stderr, "): warning RSRC111: Unlocalised resource from token file replaced unlocalised resource in executable\n"); g_fWarn = TRUE; g_cResourcesUpdated++; } } else if (rmExecutable.count(rk) > 0) { // New token already exists in executable fprintf(stderr, "%s(", g_szResources); rk.fprint(stderr); fprintf(stderr, "): warning RSRC112: Localised resource from token file replaced localised resource already present in executable\n"); g_fWarn = TRUE; g_cResourcesUpdated++; } else if (g_dwOptions & OPTREPLACE) { // Replace operation // // Replace unlocalised resource with localised translation if (rmExecutable.count(rkUnloc) == 0) { fprintf(stderr, "%s(", g_szResources); rk.fprint(stderr); fprintf(stderr, "): warning RSRC113: Localised resource from token file appended to executable - there was no matching unlocalised resource\n"); g_fWarn = TRUE; g_cResourcesAppended++; } else { // Normal operation: remove unlocalised resource from executable rmExecutable.erase(rkUnloc); g_cResourcesTranslated++; } } else { // Append operation g_cResourcesAppended++; } rmExecutable[rk] = prv; return S_OK; } //// ReadTokens // // Scans the token file. // // Selected resources are passed to ApplyResource HRESULT ReadTokens(TextScanner &mfText) { ResourceKey rk; ResourceValue *prv; ResourceKey rkUnlocalised; DWORD dwCodePage; DWORD dwUnlocChecksum; ResourceMap::iterator rmiUnlocalised; DWORD liUnlocalised; // Unlocalised language referenced by token while (mfText.GetRead() < mfText.GetLimit()) { OK(rk.ReadTok(&mfText)); // Read resource key OK(mfText.Expect(";")); if ( ( g_LangId != 0xffff && rk.prvId[2]->GetW() != g_LangId) || !IsResourceWanted(rk)) { // Ignore this token g_cResourcesIgnored++; fprintf(stderr, "%s(", g_szResources); rk.fprint(stderr); if (g_LangId != 0xffff && rk.prvId[2]->GetW() != g_LangId) { fprintf(stderr, "): warning RSRC120: Token file resource does not match specified language - ignored\n"); g_fWarn = TRUE; } else { fprintf(stderr, "): warning RSRC121: Token file resource is not a requested resource type - ignored\n"); g_fWarn = TRUE; } // Skip unwanted resource OK(mfText.SkipLn()); while (*(char*)mfText.GetRead() == ' ') { OK(mfText.SkipLn()); } } else { rmiUnlocalised = NULL; OK(mfText.ReadHex(&dwCodePage)); if (*(char*)mfText.GetRead() == ',') { // There is unlocalised resource information available OK(mfText.Expect(",")); OK(mfText.ReadHex(&dwUnlocChecksum)); OK(mfText.Expect(",")); OK(mfText.ReadHex(&liUnlocalised)); // Check whether the unlocalised resource still exists in the // current executable, and has the same checksum, rkUnlocalised = rk; rkUnlocalised.SetLanguage(liUnlocalised); rmiUnlocalised = rmExecutable.find(rkUnlocalised); if ( rmiUnlocalised != rmExecutable.end() && dwUnlocChecksum != rmiUnlocalised->second->Checksum()) { fprintf(stderr, "%s: warning RSRC122: executable unlocalised resource checksum does not match checksum recorded in token file for resource ", mfText.GetTextPos()); rk.fprint(stderr); fprintf(stderr, "\n"); g_fWarn = TRUE; } } OK(mfText.Expect(";")); if (*(char*)mfText.GetRead() == 'U') { // No resource content provided in token file // Use unlocalised resource from executable if (rmiUnlocalised == NULL) { fprintf(stderr, "%s: error RSRC230: 'Unloc' token is missing unlocalised resource information for ", mfText.GetTextPos()); rk.fprint(stderr); fprintf(stderr, "\n"); g_fError = TRUE; return E_FAIL; } OK(mfText.Expect("Unloc")); OK(mfText.ExpectLn("")); if (rmiUnlocalised == rmExecutable.end()) { fprintf(stderr, "%s: warning RSRC124: missing executable unlocalised resource for ", mfText.GetTextPos()); rk.fprint(stderr); fprintf(stderr, " - localisation skipped\n"); g_fWarn = TRUE; } else { MUST(ApplyResource(rk, rmiUnlocalised->second), ("%s: error RSRC231: Failed to apply unloc token\n", mfText.GetTextPos())); } } else { // Resource content is provided in token file if (rmiUnlocalised == rmExecutable.end()) { fprintf(stderr, "%s: warning RSRC125: executable contains no unlocalised resource corresponding to resource ", mfText.GetTextPos()); rk.fprint(stderr); fprintf(stderr, "\n"); g_fWarn = TRUE; } prv = new ResourceValue; ASSERT(prv != NULL); prv->dwCodePage = dwCodePage; prv->pb = NULL; prv->cb = 0; switch (*(char*)mfText.GetRead()) { case 'H': prv->pResource = new ResourceBinary; break; case 'D': prv->pResource = new Dialog32; break; case 'M': prv->pResource = new Menu32; break; case 'S': prv->pResource = new String32; break; case 'V': prv->pResource = new VersionInfo; break; default: fprintf(stderr, "%s: error RSRC310: Unrecognised resource type for resource ", mfText.GetTextPos()); rk.fprint(stderr); fprintf(stderr, "\n"); g_fError = TRUE; return E_FAIL; } ASSERT(prv->pResource != NULL); // Parse selected resource OK(prv->pResource->ReadTok(mfText)); OK(mfText.ExpectLn(NULL)); // Save parsed resource in STL map MUST(ApplyResource(rk, prv), ("%s: error RSRC232: Failed to apply token\n", mfText.GetTextPos())); } } } return S_OK; } //// Stats // // HRESULT Analyse(char *pExecutable) { Win32Executable w32x; NewFile nfText; ResourceMap::iterator rmi; MappedResourceStats::iterator mrsi; MappedLanguageStats::iterator mlsi; char key[100]; int i; const WCHAR *pwc; BOOL fLocalizable; MUST(w32x.Open(pExecutable, FALSE), ("RSRC : error RSRC510: Cannot open executable file %s\n", pExecutable)); MUST(w32x.MapResourceDirectory(rmExecutable), ("RSRC : error RSRC511: cannot find resource directory in %s\n, pExecutable")); // Scan through the resources updating the stats fLocalizable = FALSE; for (rmi = rmExecutable.begin(); rmi != rmExecutable.end(); rmi++) { if ( rmi->first.prvId[0]->GetfString() || rmi->first.prvId[0]->GetW() != 16) { fLocalizable = TRUE; } OK(rmi->second->InterpretImage(rmi->first)); UpdateStats(rmi->first, rmi->second->pResource->GetItems(), rmi->second->pResource->GetWords(), rmi->second->pResource->cbBin()); } if (!(g_dwOptions & OPTQUIET)) { fprintf(stdout, "\n Resource type Count Items Words Bytes\n"); fprintf(stdout, " ------------ ------ ------ ------ --------\n"); for (mrsi = ResourceStatsMap.begin(); mrsi != ResourceStatsMap.end(); mrsi++) { if (mrsi->first.GetfString()) { key[0] = '\"'; i=0; pwc = mrsi->first.GetString(); while (i < min(10, mrsi->first.GetLength())) { key[i+1] = (char) pwc[i]; i++; } key[i+1] = '\"'; key[i+2] = 0; fprintf(stdout, " %-12.12s ", key); } else { switch (mrsi->first.GetW()) { case 1: fprintf(stdout, " 1 (Cursor) "); break; case 2: fprintf(stdout, " 2 (Bitmap) "); break; case 3: fprintf(stdout, " 3 (Icon) "); break; case 4: fprintf(stdout, " 4 (Menu) "); break; case 5: fprintf(stdout, " 5 (Dialog) "); break; case 6: fprintf(stdout, " 6 (String) "); break; case 7: fprintf(stdout, " 7 (Fnt dir) "); break; case 8: fprintf(stdout, " 8 (Font) "); break; case 9: fprintf(stdout, " 9 (Accel) "); break; case 10: fprintf(stdout, " a (RCDATA) "); break; case 11: fprintf(stdout, " b (Msgtbl) "); break; case 16: fprintf(stdout, " 10 (Version) "); break; default: fprintf(stdout, " %-12x ", mrsi->first.GetW()); } } fprintf(stdout, "%6d ", mrsi->second.cResources); if (mrsi->second.cItems > 0) { fprintf(stdout, "%6d ", mrsi->second.cItems); } else { fprintf(stdout, " "); } if (mrsi->second.cWords > 0) { fprintf(stdout, "%6d ", mrsi->second.cWords); } else { fprintf(stdout, " "); } fprintf(stdout, "%8d\n", mrsi->second.cBytes); } fprintf(stdout, "\n Language Resources Items Words Bytes\n"); fprintf(stdout, " -------- --------- ------ ------ --------\n"); for (mlsi = LanguageStatsMap.begin(); mlsi != LanguageStatsMap.end(); mlsi++) { fprintf(stdout, " %8x %9d ", mlsi->first, mlsi->second.cResources); if (mlsi->second.cItems > 0) { fprintf(stdout, "%6d ", mlsi->second.cItems); } else { fprintf(stdout, " "); } if (mlsi->second.cWords > 0) { fprintf(stdout, "%6d ", mlsi->second.cWords); } else { fprintf(stdout, " "); } fprintf(stdout, "%8d\n", mlsi->second.cBytes); } fprintf(stdout, "\n"); } if (!fLocalizable) { fprintf(stderr, "RSRC : warning RSRC170: No localizable resources in %s\n", pExecutable); g_fWarn = TRUE; } SHOULD(w32x.Close(), ("RSRC : warning RSRC171: could not close executable\n")); return S_OK; } HRESULT ExtractResources(char *pExecutable, char *pResources) { Win32Executable w32x; Win32Executable w32xUnloc; NewFile nfText; char str[100]; DWORD dw; MUST(w32x.Open(g_szExecutable, FALSE), ("RSRC : error RSRC510: Cannot open executable file %s\n", g_szExecutable)); MUST(nfText.OpenWrite(g_szResources), ("RSRC : error RSRC512: Cannot create resource token file %s\n", g_szResources)); // Write header if (!(g_dwOptions & OPTHEXDUMP)) { OK(nfText.WriteS("\xef\xbb\xbf\r\n")); // UTF-8 mark for notepad, richedit etc. } OK(nfText.WriteS("### ")); OK(nfText.WriteS(g_szResources)); OK(nfText.WriteS("\r\n#\r\n# Extracted: ")); GetDateFormatA( MAKELCID(LANG_ENGLISH, SORT_DEFAULT), 0, NULL, "yyyy/MM/dd ", str, sizeof(str)); OK(nfText.WriteS(str)); GetTimeFormatA( MAKELCID(LANG_ENGLISH, SORT_DEFAULT), 0, NULL, "HH:mm:ss\'\r\n# By: \'", str, sizeof(str)); OK(nfText.WriteS(str)); dw = sizeof(str); GetComputerNameA(str, &dw); OK(nfText.WriteS(str)); OK(nfText.WriteS("\r\n# Executable: ")); OK(nfText.WriteS(g_szExecutable)); if (g_LangId != 0xffff) { OK(nfText.WriteS("\r\n# Language: ")); OK(nfText.WriteHex(g_LangId, 3)); } if (g_dwProcess != PROCESSALL) { OK(nfText.WriteS("\r\n# Res types: ")); OK(nfText.WriteS(g_szTypes)); } OK(nfText.WriteS("\r\n\r\n")); MUST(w32x.MapResourceDirectory(rmExecutable), ("RSRC : error RSRC511: cannot find resource directory in %s\n, g_szExecutable")); if (g_dwOptions & OPTUNLOC) { // Write tokens that differ from specified unlocalised executable MUST(w32xUnloc.Open(g_szUnloc, FALSE), ("RSRC : error RSRC513: Cannot open unlocalised executable file %s\n", g_szUnloc)); MUST(w32xUnloc.MapResourceDirectory(rmUnlocalised), ("RSRC : error RSRC514: cannot find resource directory in unlocalised executable %s\n, g_szUnloc")); MUST(rmExecutable.WriteTokens(nfText, &rmUnlocalised), ("RSRC : error RSRC515: cannot write delta token file %s\n, g_szResources")); w32xUnloc.Close(); } else { MUST(rmExecutable.WriteTokens(nfText, NULL), ("RSRC : error RSRC516: cannot write stand alone token file %s\n, g_szResources")); } if (!(g_dwOptions & OPTQUIET)) { fprintf(stdout, "\n%d resource(s) %s.\n", g_cResourcesExtracted, g_dwOptions & OPTHEXDUMP ? "dumped" : "tokenized"); if (g_cResourcesIgnored) { fprintf(stdout, "%d resource(s) ignored.\n", g_cResourcesIgnored); } } OK(w32x.Close()); OK(nfText.Close()); return S_OK; } //// UpdateResources // // Update resources in executable with tokens from given text // // Processing // // 1. Existing resources are loaded into the map as ResourceBinaries. // 2. Resources are merged in from the token file according to // command line selected processing options // 3. The NT UpdateResource API set is used to replace all the resources // in the executable with the merged resources in the map. HRESULT UpdateResources(char *pExecutable, char *pResources, char* pSymbols) { Win32Executable w32x; SymbolFile symf; MappedFile mfText; MappedFile mfSymbols; DWORD dwCheckSum; MUST(w32x.Open(pExecutable, FALSE), ("RSRC : error RSRC510: Cannot open executable file %s\n", pExecutable)); MUST(mfText.Open(pResources, FALSE), ("RSRC : error RSRC520: Cannot open resource token file %s\n", pResources)); MUST(mfText.Expect("\xef\xbb\xbf"), ("RSRC : error RSRC521: UTF8 BOM missing from token file\n")); // UTF-8 mark for notepad, richedit etc. OK(mfText.ExpectLn("")); // Skip over header comments if (g_dwOptions & OPTSYMBOLS) { if ( SUCCEEDED(mfSymbols.Open(pSymbols, TRUE)) && SUCCEEDED(symf.Open(&mfSymbols))) { if ( symf.GetChecksum() != w32x.GetChecksum() || symf.GetImageBase() != w32x.GetImageBase()) { time_t tsTime = symf.GetTimeDateStamp(); time_t teTime = w32x.GetTimeDateStamp(); char ssTime[30]; strcpy(ssTime, ctime(&tsTime)); ssTime[19] = 0; char seTime[30]; strcpy(seTime, ctime(&teTime)); seTime[19] = 0; fprintf(stderr, "\n Symbol mismatch: Executable Symbol file\n"); fprintf(stderr, " ImageBase: %8x %8x\n", w32x.GetImageBase(), symf.GetImageBase()); fprintf(stderr, " Checksum: %8x %8x\n", w32x.GetChecksum(), symf.GetChecksum()); fprintf(stderr, " Timestamp: %-15.15s %-15.15s\n\n", ssTime+4, seTime+4); fprintf(stderr, "RSRC : warning RSRC160: Symbol file does not match exectable\n"); g_fWarn = TRUE; } } else { fprintf(stderr, "RSRC : warning RSRC161: Symbol file not processed\n"); g_fWarn = TRUE; g_dwOptions &= ~OPTSYMBOLS; } } // Load existing resources MUST(w32x.MapResourceDirectory(rmExecutable), ("RSRC : error RSRC530: Cannot read executable resources from %s\n", pExecutable)); OK(rmExecutable.CopyResources()); // Take local copy before closing the mapped file OK(w32x.Close()); // Merge in resources from token file MUST(ReadTokens(mfText), ("RSRC : error RSRC531: Failed reading update tokens\n")); OK(rmExecutable.UpdateWin32Executable(pExecutable)); // Update was succesful, Recalculate checksum SHOULD(w32x.Open(pExecutable, TRUE), ("RSRC : warning RSRC162: Could not reopen executable %s to update checksum\n", pExecutable)); dwCheckSum = w32x.CalcChecksum(); w32x.SetChecksum(dwCheckSum); if (g_dwOptions & OPTSYMBOLS) { symf.SetChecksum(dwCheckSum); symf.SetTimeDateStamp(w32x.GetTimeDateStamp()); symf.SetSizeOfImage(w32x.GetSizeOfImage()); SHOULD(mfSymbols.Close(), ("RSRC : warning RSRC163: Failed to write updated symbol checksum\n")); } w32x.Close(); if (!(g_dwOptions & OPTQUIET)) { fprintf(stdout, "\n"); if (g_cResourcesTranslated) { fprintf(stdout, "%d resource(s) translated.\n", g_cResourcesTranslated); } if (g_cResourcesAppended) { fprintf(stdout, "%d resource(s) appended.\n", g_cResourcesAppended); } if (g_cResourcesUpdated) { fprintf(stdout, "%d resource(s) updated.\n", g_cResourcesUpdated); } if (g_cResourcesIgnored) { fprintf(stdout, "%d resource(s) ignored.\n", g_cResourcesIgnored); } } mfText.Close(); return S_OK; } //// Parameter parsing // // char g_cSwitch = '-'; // Switch character is recorded the first time one is seen void SkipWhitespace(char** p, char* pE) { while ((*p0 && *p0) && (*p' ')) { *s=**p; s++; (*p)++; l--; } // Skip any part of token that didn't fit into s while ((*p' ')) (*p)++; } if (l>0) *s++ = 0; else *(s-1) = 0; SkipWhitespace(p, pE); } void ParseName(char** p, char* pE, char* s, int l) { // Uses ParseToken to parse a name such as a filename. // If the name starts with '/' or '-' it is assumed to be // an option rather than a filename and ParseName returns // a zero length string. if (*p