/*++ Copyright (c) 1997 Microsoft Corporation Module Name: hlpfiles.c Abstract: Implements a function that is called for every file or directory, selects HLP files trying to detect help files that will not work after upgrade is complete. This code might run if _UNICODE is defined, although it processes only ANSI strings. The only exception is in it's entry point (ProcessHelpFile) and in function pCheckSubsystemMatch. Author: Calin Negreanu (calinn) 25-Oct-1997 Revision History: calinn 23-Sep-1998 File mapping --*/ #include "pch.h" //since we are reading from a file we need that sizeof to give us the accurate result #pragma pack(push,1) #define DBG_HELPFILES "HlpCheck" // Help 3.0 version and format numbers #define Version3_0 15 #define Format3_0 1 // Help 3.1 version and format numbers #define Version3_1 21 #define Format3_5 1 // Help 4.0 version and format numbers #define Version40 33 #define Version41 34 //magic numbers and various constants #define HF_MAGIC 0x5F3F #define HF_VERSION 0x03 #define WhatWeKnow 'z' #define BT_MAGIC 0x293B #define BT_VERSION 0x02 #define SYSTEM_FILE "|SYSTEM" #define MACRO_NEEDED_0 "RegisterRoutine(\"" #define MACRO_NEEDED_1 "RR(\"" //help file header typedef struct _HF_HEADER { WORD Magic; //magic number, for hlp files is 0x5F3F (?_ - the help icon (with shadow)) BYTE Version; //version identification number, must be 0x03 BYTE Flags; //file flags LONG Directory; //offset of directory block LONG FirstFree; //offset of head of free list LONG Eof; //virtual end of file } HF_HEADER, *PHF_HEADER; //internal file header typedef struct _IF_HEADER { LONG BlockSize; //block size (including header) LONG FileSize; //file size (not including header) BYTE Permission; //low byte of file permissions } IF_HEADER, *PIF_HEADER; //internal |SYSTEM file header typedef struct _SF_HEADER { LONG BlockSize; //block size (including header) LONG FileSize; //file size (not including header) BYTE Permission; //low byte of file permissions WORD Magic; //Magic word = 0x036C WORD VersionNo; //version : 15 - version 3.0 // 21 - version 3.5 // 33 - version 4.0 // 34 - version 4.1 WORD VersionFmt; //version format 1 - format 3.0 // 1 - format 3.5 LONG DateCreated; //creation date WORD Flags; //flags : fDEBUG 0x01, fBLOCK_COMPRESSION 0x4 } SF_HEADER, *PSF_HEADER; #define MAX_FORMAT 15 //btree header typedef struct _BT_HEADER { WORD Magic; //magic number = 0x293B BYTE Version; //version = 2 BYTE Flags; //r/o, open r/o, dirty, isdir WORD BlockSize; //size of a block (bytes) CHAR Format[MAX_FORMAT+1];//key and record format string - MAXFORMAT=15 //**WARNING! the first character should be z for the btree we are going to read** WORD First; //first leaf block in tree WORD Last; //last leaf block in tree WORD Root; //root block WORD Free; //head of free block list WORD Eof; //next bk to use if free list empty WORD Levels; //number of levels currently in tree LONG Entries; //number of keys in btree } BT_HEADER, *PBT_HEADER; //index page header typedef struct _IDX_PAGE { SHORT Slack; //unused space at the end of page (bytes) SHORT Keys; //# of keys in page WORD PreviousPage; //pointer to parent page (FFFF if it's root page) } IDX_PAGE, *PIDX_PAGE; //leaf page header typedef struct _LEAF_PAGE { SHORT Slack; //unused space at the end of page (bytes) SHORT Keys; //# of keys in page WORD PreviousPage; //pointer to previous page (FFFF if it's first) WORD NextPage; //pointer to next page (FFFF if it's last) } LEAF_PAGE, *PLEAF_PAGE; //format of info in |SYSTEM file typedef struct _DATA_HEADER { WORD InfoType; //info type WORD InfoLength; //# of bytes containing the info } DATA_HEADER, *PDATA_HEADER; //types of info in |SYSTEM file enum { tagFirst, // First tag in the list tagTitle, // Title for Help window (caption) tagCopyright, // Custom text for About box tagContents, // Address for contents topic tagConfig, // Macros to be run at load time tagIcon, // override of default help icon tagWindow, // secondary window info tagCS, // character set tagCitation, // Citation String // The following are new to 4.0 tagLCID, // Locale ID and flags for CompareStringA tagCNT, // .CNT help file is associated with tagCHARSET, // charset of help file tagDefFont, // default font for keywords, topic titles, etc. tagPopupColor,// color of popups from a window tagIndexSep, // index separating characters tagLast // Last tag in the list }; // // for tagLCID // typedef struct { DWORD fsCompareI; DWORD fsCompare; LANGID langid; } KEYWORD_LOCALE, *PKEYWORD_LOCALE; #define DOS_SIGNATURE 0x5A4D // MZ #define NE_SIGNATURE 0x454E // New Executable file format signature - NE #define PE_SIGNATURE 0x00004550 // Portable Executable file format signature - PE00 #pragma pack(pop) ULONG pQueryBtreeForFile ( IN PCSTR BtreeImage, IN PCSTR StringNeeded ) /*++ Routine Description: This routine will traverse a b tree trying to find the string passed as parameter. It will return the pointer associated with the passed string or NULLto the beginning of the internal |SYSTEM file of a HLP file. Arguments: BtreeImage - Pointer to the beginning of the b tree StringNeeded - String to find in b tree Return Value: It will return the pointer associated with the passed string or NULL if string could not be found or some error occured. --*/ { PBT_HEADER pbt_Header; ULONG systemFileOffset = 0; WORD bt_Page; UINT bt_Deep; INT bt_KeysRead; PIDX_PAGE pbt_PageHeader; PCSTR pbt_LastString; PCSTR pbt_CurrentKey; LONG *pbt_LastLongOff = NULL; WORD *pbt_LastWordOff = NULL; BOOL found = FALSE; //let's read b tree header pbt_Header = (PBT_HEADER) BtreeImage; //check this b tree header to see if it's valid if ((pbt_Header->Magic != BT_MAGIC ) || (pbt_Header->Version != BT_VERSION) || (pbt_Header->Format [0] != WhatWeKnow) ) { //invalid b tree header. return 0; } //let's see if there is something in this b tree if ((pbt_Header->Levels == 0) || (pbt_Header->Entries <= 0) ) { //nothing else to do return 0; } //now we are going to loop until we find our string or until we are sure that the string //does not exist. We are reffering all the time to a certain page from the b tree (starting //with root page. //initializing current processing page bt_Page = pbt_Header->Root; //initializing deep counter //we count how deep we are to know if we are processing an index page (deep < btree deepmax) //or a leaf page (deep == btree deepmax) bt_Deep = 1; //we are breaking the loop if: // 1. we reached the maximum deep level and we didn't find the string // 2. first key in the current page was already greater than our string and // there is no previous page. while (!found) { //for each page we are using three pointers: // one to the page header // one to the key currently beeing processed // one to the last string <= our string (this can be NULL) pbt_PageHeader = (PIDX_PAGE) (BtreeImage + sizeof (BT_HEADER) + (bt_Page * pbt_Header->BlockSize)); pbt_CurrentKey = (PCSTR) pbt_PageHeader; pbt_CurrentKey += (bt_Deep == pbt_Header->Levels) ? sizeof (LEAF_PAGE) : sizeof (IDX_PAGE); pbt_LastString = NULL; //initializing number of keys read. bt_KeysRead = 0; //we are reading every key in this page until the we find one greater than our string //In the same time we try not to read too many keys while ((bt_KeysRead < pbt_PageHeader->Keys) && (StringCompareA (StringNeeded, pbt_CurrentKey) >= 0) ) { pbt_LastString = pbt_CurrentKey; bt_KeysRead++; //passing the string in this key pbt_CurrentKey = GetEndOfStringA (pbt_CurrentKey) + 1; //read this key associated value pbt_LastLongOff = (LONG *)pbt_CurrentKey; pbt_LastWordOff = (WORD *)pbt_CurrentKey; //now if this is an index page then there is a WORD here, otherwise a LONG pbt_CurrentKey += (bt_Deep == pbt_Header->Levels) ? sizeof (LONG) : sizeof (WORD); } //OK, now we have passed the string we are looking for. If the last found value is valid //(is <= for an index page) (is == for a leaf page) then keep with it. if (!pbt_LastString) { //we found nothing. The first key was already greater that our string //we try to get to the previous page if we have one. If not, there is //nothing else to do if (pbt_PageHeader->PreviousPage != 0xFFFF) { bt_Deep++; bt_Page = pbt_PageHeader->PreviousPage; continue; } else { return 0; } } //Now in the string pointed by pbt_LastString we have something <= our string. If this is an index //page then we move on else those two strings should be equal. if (bt_Deep != pbt_Header->Levels) { //We are on an index page. Mark going deeper and moving on. bt_Deep++; bt_Page = *pbt_LastWordOff; continue; } if (!StringMatchA (StringNeeded, pbt_LastString)) { //We are on a leaf page and the strings are not equal. Our string does not exist. //nothing else to do return 0; } found = TRUE; systemFileOffset = *pbt_LastLongOff; } return systemFileOffset; } PCSTR pGetSystemFilePtr ( IN PCSTR FileImage ) /*++ Routine Description: This routine will return a pointer to the beginning of the internal |SYSTEM file of a HLP file. Arguments: FileImage - Pointer to the beginning of the HLP file Return Value: NULL if an error occured, a valid pointer to the beginning of the |SYSTEM file otherwise --*/ { PCSTR systemFileImage = NULL; PHF_HEADER phf_Header; //we are going to read from various portions of this memory mapped file. There //is no guarantee that we are going to keep our readings inside the file so let's //prevent any access violation. __try { //first check if we are really dealing with a HLP file phf_Header = (PHF_HEADER) FileImage; if ((phf_Header->Magic != HF_MAGIC ) || (phf_Header->Version != HF_VERSION) ) { __leave; } //according to the hacked specs phf_header->Directory gives us the offset of //directory block relativ to the beginning of the HLP file. Here we find an //internal file header followed by a b tree. //now get the |SYSTEM internal file address passing the address of the b tree header. systemFileImage = FileImage + pQueryBtreeForFile (FileImage + phf_Header->Directory + sizeof (IF_HEADER), SYSTEM_FILE); } __except (EXCEPTION_EXECUTE_HANDLER) { return NULL; } return systemFileImage; } #define MODULE_OK 0 #define MODULE_NOT_FOUND 1 #define MODULE_BROKEN 2 #define MODULE_MISMATCHED 3 INT pCheckSubsystemByModule ( IN PCSTR FileName, IN WORD VersionNo, IN PCSTR ModuleName ) /*++ Routine Description: Checks a help file and an extension module to see if are going to be loaded in the same subsystem while running in NT. Arguments: FileName - The help file (full path) VersionNo - version of help file ModuleName - contains module name Return Value: MODULE_OK - if both help file and module will be loaded in same subsystems in NT. MODULE_NOT_FOUND - if the needed module could not be located MODULE_BROKEN - if broken or not a windows module MODULE_MISMATCHED - if help file and module will be loaded in different subsystems in NT. --*/ { PCSTR fileImage = NULL; HANDLE mapHandle = NULL; HANDLE fileHandle = INVALID_HANDLE_VALUE; PDOS_HEADER pdos_Header; LONG *pPE_Signature; WORD *pNE_Signature; CHAR fullPath [MAX_MBCHAR_PATH]; CHAR key [MEMDB_MAX]; PSTR endPtr; PSTR dontCare; INT result = MODULE_BROKEN; MemDbBuildKey (key, MEMDB_CATEGORY_GOOD_HLP_EXTENSIONS, ModuleName, NULL, NULL); if (MemDbGetValue (key, NULL)) { return MODULE_OK; } //if out of memory we will not come back here so not checking this __try { //preparing fullPath to contain only the HelpFile path StringCopyA (fullPath, FileName); endPtr = (PSTR) GetFileNameFromPathA (fullPath); if (!endPtr) { result = MODULE_OK; __leave; } *endPtr = 0; if ((!SearchPathA ( fullPath, ModuleName, ".EXE", MAX_MBCHAR_PATH, fullPath, &dontCare)) && (!SearchPathA ( fullPath, ModuleName, ".DLL", MAX_MBCHAR_PATH, fullPath, &dontCare)) && (!SearchPathA ( NULL, ModuleName, ".EXE", MAX_MBCHAR_PATH, fullPath, &dontCare)) && (!SearchPathA ( NULL, ModuleName, ".DLL", MAX_MBCHAR_PATH, fullPath, &dontCare)) ) { result = MODULE_NOT_FOUND; __leave; } //map the file into memory and get a it's address fileImage = MapFileIntoMemory (fullPath, &fileHandle, &mapHandle); if (fileImage == NULL) { result = MODULE_NOT_FOUND; __leave; } //map dos header into view of file pdos_Header = (PDOS_HEADER) fileImage; //now see what kind of signature we have there pNE_Signature = (WORD *) (fileImage + pdos_Header->e_lfanew); pPE_Signature = (LONG *) (fileImage + pdos_Header->e_lfanew); if (*pNE_Signature == NE_SIGNATURE) { //this is New Executable format result = (VersionNo > Version3_1) ? MODULE_MISMATCHED : MODULE_OK; } else if (*pPE_Signature == PE_SIGNATURE) { //this is Portable Executable format result = (VersionNo <= Version3_1) ? MODULE_MISMATCHED : MODULE_OK; } } __finally { //unmap and close module UnmapFile ((PVOID) fileImage, mapHandle, fileHandle); } return result; } BOOL pCheckSubsystemMatch ( IN PCSTR HlpName, IN PCSTR FriendlyName, OPTIONAL IN WORD VersionNo ) /*++ Routine Description: Checks all extension modules listed in MemDB category MEMDB_CATEGORY_HELP_FILES_DLLA to see if are going to be loaded in the same subsystem while running in NT. Arguments: HlpName - The help file (full path) VersionNo - version of help file KeyPath - contains category and module name (Ex:HelpFilesDll\foo.dll) Return Value: TRUE if successful, FALSE if at least one error occured --*/ { INT result; MEMDB_ENUMA e; PCSTR moduleName; PCTSTR ArgList[3]; PCTSTR Comp; //see if there is any aditional dll if (!MemDbEnumFirstValueA ( &e, MEMDB_CATEGORY_HELP_FILES_DLLA, MEMDB_ALL_SUBLEVELS, MEMDB_ENDPOINTS_ONLY )) { return TRUE; } do { moduleName = _mbschr (e.szName, '\\'); if (!moduleName) { continue; } moduleName = (PCSTR) _mbsinc (moduleName); result = pCheckSubsystemByModule ( HlpName, VersionNo, moduleName ); switch (result) { case MODULE_NOT_FOUND: #ifdef UNICODE ArgList[0] = ConvertAtoW (moduleName); ArgList[1] = ConvertAtoW (HlpName); #else ArgList[0] = moduleName; ArgList[1] = HlpName; #endif LOG ((LOG_WARNING, (PCSTR)MSG_HELPFILES_NOTFOUND_LOG, ArgList[0], ArgList[1])); #ifdef UNICODE FreeConvertedStr (ArgList[0]); FreeConvertedStr (ArgList[1]); #endif break; case MODULE_BROKEN: #ifdef UNICODE ArgList[0] = ConvertAtoW (moduleName); ArgList[1] = ConvertAtoW (HlpName); #else ArgList[0] = moduleName; ArgList[1] = HlpName; #endif LOG ((LOG_WARNING, (PCSTR)MSG_HELPFILES_BROKEN_LOG, ArgList[0], ArgList[1])); #ifdef UNICODE FreeConvertedStr (ArgList[0]); FreeConvertedStr (ArgList[1]); #endif break; case MODULE_MISMATCHED: if ((!FriendlyName) || (*FriendlyName == 0)) { FriendlyName = (PCSTR) GetFileNameFromPathA (HlpName); } #ifdef UNICODE ArgList[0] = ConvertAtoW (moduleName); ArgList[1] = ConvertAtoW (HlpName); ArgList[2] = ConvertAtoW (FriendlyName); #else ArgList[0] = moduleName; ArgList[1] = HlpName; ArgList[2] = FriendlyName; #endif Comp = BuildMessageGroup (MSG_MINOR_PROBLEM_ROOT, MSG_HELPFILES_SUBGROUP, ArgList[2]); MsgMgr_ObjectMsg_Add (HlpName, Comp, NULL); FreeText (Comp); LOG ((LOG_WARNING, (PCSTR)MSG_HELPFILES_MISMATCHED_LOG, ArgList[0], ArgList[1])); #ifdef UNICODE FreeConvertedStr (ArgList[0]); FreeConvertedStr (ArgList[1]); FreeConvertedStr (ArgList[2]); #endif break; } } while (MemDbEnumNextValueA (&e)); MemDbDeleteTreeA (MEMDB_CATEGORY_HELP_FILES_DLLA); return TRUE; } BOOL pSkipPattern ( IN PCSTR Source, IN OUT PCSTR *Result, IN PCSTR StrToSkip ) /*++ Routine Description: Skips a whole pattern. Usually when making simple parsers that are not supposed to raise an error message it's enough to know if the string that you parse is correct or not. For example if you want to see if a string complies with a pattern like "RR(\"" you don't have to scan for each symbol separately, just call this function with StrToSkip="RR(\"" The good thing is that this function skips also the spaces for you so a string like " RR ( \" " will match the pattern above. ANSI only!!! Arguments: Source - String to scan Result - If not NULL it will point right after the pattern if successful StrToSkip - Pattern to match and skip Return Value: TRUE if was able to match the pattern, FALSE otherwise --*/ { //first skip spaces Source = SkipSpaceA (Source ); StrToSkip = SkipSpaceA (StrToSkip); //now try to see if the strings match while ((*Source ) && (*StrToSkip) && (_totlower (*Source) == _totlower (*StrToSkip)) ) { Source = _mbsinc (Source ); StrToSkip = _mbsinc (StrToSkip); Source = SkipSpaceA (Source ); StrToSkip = SkipSpaceA (StrToSkip); } if (*StrToSkip) { return FALSE; } if (Result) { *Result = Source; } return TRUE; } BOOL pParseMacro ( IN PCSTR FileName, IN WORD VersionNo, IN PCSTR MacroStr ) /*++ Routine Description: Parses a macro from |SYSTEM file inside a HLP file to see if there is a RegisterRoutine macro If true, then it will eventually do something with that information. Arguments: MacroStr - String to parse VersionNo - Version number for this help file (we will use this to identify the subsystem where this file is more likely to be loaded). Return Value: TRUE if successful, FALSE if at least one error occured --*/ { BOOL result = TRUE; PCSTR endStr; char dllName[MAX_MBCHAR_PATH]; char exportName[MAX_MBCHAR_PATH]; PCSTR dllNameNoPath; //let's see if we have a pattern like RegisterRoutine(" or RR(" here if (!pSkipPattern (MacroStr, &MacroStr, MACRO_NEEDED_0)) { if (!pSkipPattern (MacroStr, &MacroStr, MACRO_NEEDED_1)) { return TRUE; } } //OK, we are ready to extract the dll name from the macro string endStr = _mbschr (MacroStr, '\"'); if (!endStr) { return FALSE; } endStr = (PCSTR) _mbsinc (SkipSpaceRA (MacroStr, _mbsdec(MacroStr, endStr))); if (!endStr) { return FALSE; } //now we have the dll name between MacroStr and EndStr //a little safety check if ((endStr - MacroStr) >= MAX_MBCHAR_PATH-1) { return FALSE; } StringCopyABA (dllName, MacroStr, endStr); if (!dllName[0]) { return FALSE; } //now see if this is a full path file name or not dllNameNoPath = GetFileNameFromPathA (dllName); //ok, now the following pattern should be >>","<< if (!pSkipPattern (endStr, &MacroStr, "\",\"")) { return TRUE; } //OK, we are ready to extract the export function name from the macro string endStr = _mbschr (MacroStr, '\"'); if (!endStr) { return FALSE; } endStr = (PCSTR) _mbsinc (SkipSpaceRA (MacroStr, _mbsdec(MacroStr, endStr))); if (!endStr) { return FALSE; } //now we have the dll name between MacroStr and EndStr //a little safety check if ((endStr - MacroStr) >= MAX_MBCHAR_PATH-1) { return FALSE; } StringCopyABA (exportName, MacroStr, endStr); if (!exportName[0]) { return FALSE; } //add to MemDb in HelpFilesDll category if (!MemDbSetValueExA ( MEMDB_CATEGORY_HELP_FILES_DLLA, dllNameNoPath, NULL, NULL, 0, NULL )) { return FALSE; } return result; } BOOL pCheckDlls ( IN PCSTR FileName, IN PCSTR SystemFileImage ) /*++ Routine Description: This routine checks the internal |SYSTEM file of a HLP file looking for additional Dll's. It does that trying to find either "RegisterRoutine" or "RR" macros. For every Dll found tries to match the Dll with the HLP file version. For every incompatibility found, adds an entry in the report presented to the user. Arguments: FileName - Full name of the help file SystemFileImage - Pointer to the beginning of the internal |SYSTEM file. Return Value: TRUE if successful, FALSE if at least one error occured --*/ { PSF_HEADER psf_Header; PDATA_HEADER pdata_Header; PCSTR currImage; PCSTR friendlyName = NULL; LONG sf_BytesToRead; BOOL result = TRUE; BOOL bNoFriendlyName = FALSE; //we are going to read from various portions of this memory mapped file. There //is no guarantee that we are going to keep our readings inside the file so let's //prevent any access violation. __try { //first thing. Extract help file version psf_Header = (PSF_HEADER) SystemFileImage; //if file version is 3.0 or less we have nothing else to do if (psf_Header->VersionNo <= Version3_0) { __leave; } //Now scanning |SYSTEM file looking for macros. We must be careful to stop when //this internal file is over. sf_BytesToRead = psf_Header->FileSize + sizeof (IF_HEADER) - sizeof (SF_HEADER); currImage = SystemFileImage + sizeof (SF_HEADER); while (sf_BytesToRead > 0) { //map a data header pdata_Header = (PDATA_HEADER) currImage; currImage += sizeof (DATA_HEADER); sf_BytesToRead -= sizeof (DATA_HEADER); //see what kind of info we have here (macros are in tagConfig) if (pdata_Header->InfoType == tagConfig) { //parsing the string to see if there is a RegisterRoutine macro //If so we are going to store the Dll into MemDB. if (!pParseMacro(FileName, psf_Header->VersionNo, currImage)) { result = FALSE; __leave; } } else if (pdata_Header->InfoType == tagTitle) { //Now we have the help file friendly name. Map ANSI string if (!bNoFriendlyName) { friendlyName = currImage; } } else if (pdata_Header->InfoType == tagLCID) { if (pdata_Header->InfoLength == sizeof (KEYWORD_LOCALE)) { DWORD lcid; PKEYWORD_LOCALE pkl = (PKEYWORD_LOCALE)currImage; lcid = MAKELCID (pkl->langid, SORT_DEFAULT); if (!IsValidLocale (lcid, LCID_INSTALLED)) { // // the title is not friendly // bNoFriendlyName = TRUE; friendlyName = NULL; } } } currImage += pdata_Header->InfoLength; sf_BytesToRead -= pdata_Header->InfoLength; } //we finally finished scanning the help file. Let's take advantage of the __try __except block //and try to see if this help file and all it's extension dlls will run in the same //subsystem on NT. if (!pCheckSubsystemMatch ( FileName, friendlyName, psf_Header->VersionNo )) { result = FALSE; } } __except (EXCEPTION_EXECUTE_HANDLER) { //if some exception occured maybe we managed to get something in MemDB //so let's make some cleanup MemDbDeleteTreeA (MEMDB_CATEGORY_HELP_FILES_DLLA); return FALSE; } return result; } BOOL pProcessHelpFile ( IN PCSTR FileName ) /*++ Routine Description: This routine checks a HLP file looking for additional DLLs used. If such a DLL is found we will try to see if this combination will run on NT. The fact is that depending on the version of the HLP file it will be opened by WinHelp.EXE (16 bit app) or WinHlp32.EXE (32 bit app). Now suppose that a HLP file is opened by WinHlp32.EXE and it has an additional 16 bit DLL, this combination will not work on NT (WinHlp32.EXE and the aditional DLL are running in differend subsystems). For every incompatibility found, we will add an entry in the report presented to the user. Arguments: FileName - Full information about the location of the file Return Value: TRUE if file was processed successful, FALSE if at least one error occured --*/ { PCSTR fileImage = NULL; HANDLE mapHandle = NULL; HANDLE fileHandle = INVALID_HANDLE_VALUE; PCSTR systemFileImage = NULL; BOOL result = TRUE; //map the file into memory and get a it's address fileImage = MapFileIntoMemory (FileName, &fileHandle, &mapHandle); __try { if (fileImage == NULL) { result = FALSE; __leave; } //find the internal file |SYSTEM systemFileImage = pGetSystemFilePtr (fileImage); if (systemFileImage == fileImage) { result = FALSE; __leave; } //check every additional dll used by help file result = result && pCheckDlls (FileName, systemFileImage); } __finally { //unmap and close help file UnmapFile ((PVOID) fileImage, mapHandle, fileHandle); } return result; } PSTR pGetTitle ( IN PCSTR FileName, IN PCSTR SystemFileImage ) /*++ Routine Description: This routine checks the internal |SYSTEM file of a HLP file looking for it's title Arguments: FileName - Full name of the help file SystemFileImage - Pointer to the beginning of the internal |SYSTEM file. Return Value: HLP file title (if available) --*/ { PSF_HEADER psf_Header; PDATA_HEADER pdata_Header; PCSTR currImage; LONG sf_BytesToRead; PSTR result = NULL; //we are going to read from various portions of this memory mapped file. There //is no guarantee that we are going to keep our readings inside the file so let's //prevent any access violation. __try { //first thing. Extract help file version psf_Header = (PSF_HEADER) SystemFileImage; //if file version is 3.0 or less we have nothing else to do if (psf_Header->VersionNo <= Version3_0) { __leave; } //Now scanning |SYSTEM file looking for macros. We must be careful to stop when //this internal file is over. sf_BytesToRead = psf_Header->FileSize + sizeof (IF_HEADER) - sizeof (SF_HEADER); currImage = SystemFileImage + sizeof (SF_HEADER); while (sf_BytesToRead > 0) { //map a data header pdata_Header = (PDATA_HEADER) currImage; currImage += sizeof (DATA_HEADER); sf_BytesToRead -= sizeof (DATA_HEADER); if (pdata_Header->InfoType == tagTitle) { //Now we have the help file friendly name. Map ANSI string result = DuplicatePathStringA (currImage, 0); break; } currImage += pdata_Header->InfoLength; sf_BytesToRead -= pdata_Header->InfoLength; } } __except (EXCEPTION_EXECUTE_HANDLER) { result = NULL; } return result; } PSTR GetHlpFileTitle ( IN PCSTR FileName ) /*++ Routine Description: This routine opens a HLP file looking for it's title. Arguments: FileName - Full information about the location of the file Return Value: The title of the HLP file if available --*/ { PCSTR fileImage = NULL; HANDLE mapHandle = NULL; HANDLE fileHandle = INVALID_HANDLE_VALUE; PCSTR systemFileImage = NULL; PSTR result = NULL; //map the file into memory and get a it's address fileImage = MapFileIntoMemory (FileName, &fileHandle, &mapHandle); __try { if (fileImage == NULL) { __leave; } //find the internal file |SYSTEM systemFileImage = pGetSystemFilePtr (fileImage); if (systemFileImage == fileImage) { __leave; } //check every additional dll used by help file result = pGetTitle (FileName, systemFileImage); } __finally { //unmap and close help file UnmapFile ((PVOID) fileImage, mapHandle, fileHandle); } return result; } BOOL ProcessHelpFile ( IN PFILE_HELPER_PARAMS Params ) /*++ Routine Description: This routine is mainly a dispatcher. Will pass HLP files to routine pProcessHelpFile and modules to pProcessModule. The goal is to create two MemDb trees containing the export functions needed and provided to be able to estimate about some modules or help files not working after migration. Arguments: Params - Full information about the location of the file Return Value: TRUE if successful, FALSE otherwise --*/ { PSTR fileName; TCHAR key[MEMDB_MAX]; DWORD dontCare; //we are going to process this file if : // 1. has HLP extension // 2. is not marked as incompatible (this routine also checks for handled) if (!StringIMatch (Params->Extension, TEXT(".HLP"))|| IsReportObjectIncompatible (Params->FullFileSpec) ) { return TRUE; } MemDbBuildKey (key, MEMDB_CATEGORY_COMPATIBLE_HLP, Params->FullFileSpec, NULL, NULL); if (MemDbGetValue (key, &dontCare)) { return TRUE; } #ifdef UNICODE fileName = ConvertWtoA (Params->FullFileSpec); #else fileName = (PSTR) Params->FullFileSpec; #endif if (!pProcessHelpFile (fileName)) { DEBUGMSG ((DBG_HELPFILES, "Error processing help file %s", fileName)); } #ifdef UNICODE FreeConvertedStr (fileName); #endif return TRUE; } DWORD InitHlpProcessing ( IN DWORD Request ) { switch (Request) { case REQUEST_QUERYTICKS: return TICKS_INIT_HLP_PROCESSING; case REQUEST_RUN: MemDbSetValueEx (MEMDB_CATEGORY_GOOD_HLP_EXTENSIONS, "KERNEL", NULL, NULL, 0, NULL); MemDbSetValueEx (MEMDB_CATEGORY_GOOD_HLP_EXTENSIONS, "KERNEL.EXE", NULL, NULL, 0, NULL); MemDbSetValueEx (MEMDB_CATEGORY_GOOD_HLP_EXTENSIONS, "KERNEL32", NULL, NULL, 0, NULL); MemDbSetValueEx (MEMDB_CATEGORY_GOOD_HLP_EXTENSIONS, "KERNEL32.DLL", NULL, NULL, 0, NULL); MemDbSetValueEx (MEMDB_CATEGORY_GOOD_HLP_EXTENSIONS, "KRNL386", NULL, NULL, 0, NULL); MemDbSetValueEx (MEMDB_CATEGORY_GOOD_HLP_EXTENSIONS, "KRNL386.EXE", NULL, NULL, 0, NULL); MemDbSetValueEx (MEMDB_CATEGORY_GOOD_HLP_EXTENSIONS, "USER", NULL, NULL, 0, NULL); MemDbSetValueEx (MEMDB_CATEGORY_GOOD_HLP_EXTENSIONS, "USER.EXE", NULL, NULL, 0, NULL); MemDbSetValueEx (MEMDB_CATEGORY_GOOD_HLP_EXTENSIONS, "USER32", NULL, NULL, 0, NULL); MemDbSetValueEx (MEMDB_CATEGORY_GOOD_HLP_EXTENSIONS, "USER32.DLL", NULL, NULL, 0, NULL); MemDbSetValueEx (MEMDB_CATEGORY_GOOD_HLP_EXTENSIONS, "GDI", NULL, NULL, 0, NULL); MemDbSetValueEx (MEMDB_CATEGORY_GOOD_HLP_EXTENSIONS, "GDI.EXE", NULL, NULL, 0, NULL); MemDbSetValueEx (MEMDB_CATEGORY_GOOD_HLP_EXTENSIONS, "GDI32", NULL, NULL, 0, NULL); MemDbSetValueEx (MEMDB_CATEGORY_GOOD_HLP_EXTENSIONS, "GDI32.DLL", NULL, NULL, 0, NULL); MemDbSetValueEx (MEMDB_CATEGORY_GOOD_HLP_EXTENSIONS, "SHELL", NULL, NULL, 0, NULL); MemDbSetValueEx (MEMDB_CATEGORY_GOOD_HLP_EXTENSIONS, "SHELL.DLL", NULL, NULL, 0, NULL); MemDbSetValueEx (MEMDB_CATEGORY_GOOD_HLP_EXTENSIONS, "SHELL32", NULL, NULL, 0, NULL); MemDbSetValueEx (MEMDB_CATEGORY_GOOD_HLP_EXTENSIONS, "SHELL32.DLL", NULL, NULL, 0, NULL); return ERROR_SUCCESS; default: DEBUGMSG ((DBG_ERROR, "Bad parameter in InitHlpProcessing")); } return 0; }