#include "util.h" #include "patchdownload.h" #include "sdsutils.h" extern "C" { #include "crc32.h" } #define CR 13 HANDLE g_hLogFile = NULL; extern HINF g_hInf; extern HINSTANCE g_hInstance; struct LangID { DWORD dwLangID; char szLanguage[3]; }; const LangID g_LangID [] = { {0x404, "TW"}, {0x804, "CN"}, {0x405, "CS"}, {0x406, "DA"}, {0x413, "NL"}, {0x409, "EN"}, {0x40B, "FI"}, {0x40C, "FR"}, {0x407, "DE"}, {0x408, "EL"}, {0x40E, "HU"}, {0x410, "IT"}, {0x411, "JA"}, {0x412, "KO"}, {0x414, "NO"}, {0x415, "PL"}, {0x416, "BR"}, {0x816, "PT"}, {0x419, "RU"}, {0x424, "SL"}, {0xC0A, "ES"}, {0x41D, "SV"}, {0x41E, "TH"}, {0x41F, "TR"}, {0x42A, "VI"}, {0x41B, "SK"}, {0x401, "AR"}, {0x403, "CA"}, {0x42D, "EU"}, {0x40D, "HE"}, {0x40F, "IS"}, {-1, NULL} }; extern PFSetupFindFirstLine pfSetupFindFirstLine; extern PFSetupGetStringField pfSetupGetStringField; extern PFSetupDecompressOrCopyFile pfSetupDecompressOrCopyFile; PVOID __fastcall MyVirtualAlloc(ULONG Size) { return VirtualAlloc( NULL, Size, MEM_COMMIT, PAGE_READWRITE ); } VOID __fastcall MyVirtualFree(PVOID Allocation) { VirtualFree( Allocation, 0, MEM_RELEASE ); } extern "C" HANDLE CreateSubAllocator(IN ULONG InitialCommitSize, IN ULONG GrowthCommitSize) { PSUBALLOCATOR SubAllocator; ULONG InitialSize; ULONG GrowthSize; InitialSize = ROUNDUP2( InitialCommitSize, MINIMUM_VM_ALLOCATION ); GrowthSize = ROUNDUP2( GrowthCommitSize, MINIMUM_VM_ALLOCATION ); SubAllocator = (PSUBALLOCATOR)MyVirtualAlloc( InitialSize ); // // If can't allocate entire initial size, back off to minimum size. // Very large initial requests sometimes cannot be allocated simply // because there is not enough contiguous address space. // if ( SubAllocator == NULL ) { SubAllocator = (PSUBALLOCATOR)MyVirtualAlloc( GrowthSize ); } if ( SubAllocator == NULL ) { SubAllocator = (PSUBALLOCATOR)MyVirtualAlloc( MINIMUM_VM_ALLOCATION ); } if ( SubAllocator != NULL ) { SubAllocator->NextAvailable = (PCHAR)SubAllocator + ROUNDUP2( sizeof( SUBALLOCATOR ), SUBALLOCATOR_ALIGNMENT ); SubAllocator->LastAvailable = (PCHAR)SubAllocator + InitialSize; SubAllocator->VirtualList = (PVOID*)SubAllocator; SubAllocator->GrowSize = GrowthSize; } return (HANDLE) SubAllocator; } extern "C" PVOID __fastcall SubAllocate(IN HANDLE hAllocator, IN ULONG Size) { PSUBALLOCATOR SubAllocator = (PSUBALLOCATOR) hAllocator; PCHAR NewVirtual; PCHAR Allocation; ULONG AllocSize; ULONG Available; ULONG GrowSize; ASSERT( Size < (ULONG)( ~(( SUBALLOCATOR_ALIGNMENT * 2 ) - 1 ))); AllocSize = ROUNDUP2( Size, SUBALLOCATOR_ALIGNMENT ); Available = SubAllocator->LastAvailable - SubAllocator->NextAvailable; if ( AllocSize <= Available ) { Allocation = SubAllocator->NextAvailable; SubAllocator->NextAvailable = Allocation + AllocSize; return Allocation; } // // Insufficient VM, so grow it. Make sure we grow it enough to satisfy // the allocation request in case the request is larger than the grow // size specified in CreateSubAllocator. // GrowSize = SubAllocator->GrowSize; if ( GrowSize < ( AllocSize + SUBALLOCATOR_ALIGNMENT )) { GrowSize = ROUNDUP2(( AllocSize + SUBALLOCATOR_ALIGNMENT ), MINIMUM_VM_ALLOCATION ); } NewVirtual = (PCHAR)MyVirtualAlloc( GrowSize ); // If failed to alloc GrowSize VM, and the allocation could be satisfied // with a minimum VM allocation, try allocating minimum VM to satisfy // this request. // if (( NewVirtual == NULL ) && ( AllocSize <= ( MINIMUM_VM_ALLOCATION - SUBALLOCATOR_ALIGNMENT ))) { GrowSize = MINIMUM_VM_ALLOCATION; NewVirtual = (PCHAR)MyVirtualAlloc( GrowSize ); } if ( NewVirtual != NULL ) { // Set LastAvailable to end of new VM block. SubAllocator->LastAvailable = NewVirtual + GrowSize; // Link new VM into list of VM allocations. *(PVOID*)NewVirtual = SubAllocator->VirtualList; SubAllocator->VirtualList = (PVOID*)NewVirtual; // Requested allocation comes next. Allocation = NewVirtual + SUBALLOCATOR_ALIGNMENT; // Then set the NextAvailable for what's remaining. SubAllocator->NextAvailable = Allocation + AllocSize; // And return the allocation. return Allocation; } // Could not allocate enough VM to satisfy request. return NULL; } extern "C" VOID DestroySubAllocator(IN HANDLE hAllocator) { PSUBALLOCATOR SubAllocator = (PSUBALLOCATOR) hAllocator; PVOID VirtualBlock = SubAllocator->VirtualList; PVOID NextVirtualBlock; do { NextVirtualBlock = *(PVOID*)VirtualBlock; MyVirtualFree( VirtualBlock ); VirtualBlock = NextVirtualBlock; }while (VirtualBlock != NULL); } HLOCAL ResizeBuffer(IN HLOCAL BufferHandle, IN DWORD Size, IN BOOL Moveable) { if (BufferHandle == NULL) { if (Size != 0) { BufferHandle = LocalAlloc(Moveable ? LMEM_MOVEABLE : LMEM_FIXED, Size); } } else if (Size == 0) { BufferHandle = LocalFree(BufferHandle); BufferHandle=NULL; } else { HLOCAL TempBufferHandle = LocalReAlloc(BufferHandle, Size, LMEM_MOVEABLE); if ( TempBufferHandle ) { BufferHandle = TempBufferHandle; } else { LocalFree(BufferHandle); BufferHandle = NULL; } } return BufferHandle; } VOID MyLowercase(IN OUT LPSTR String) { LPSTR p; for ( p = String; *p; p++ ) { if (( *p >= 'A' ) && ( *p <= 'Z' )) { *p |= 0x20; } } } void InitLogFile() { char szLogFileName[MAX_PATH], szTmp[MAX_PATH]; HKEY hKey; BYTE cbData[MAX_PATH]; DWORD dwSize = sizeof(cbData); if(ERROR_SUCCESS != RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Advanced INF Setup", 0, KEY_ALL_ACCESS, &hKey)) { return; } if(ERROR_SUCCESS != RegQueryValueEx(hKey, "AdvpextLog", 0, 0, cbData, &dwSize) || lstrcmpi((char*)cbData, "yes")) { RegCloseKey(hKey); return; } RegCloseKey(hKey); if (GetWindowsDirectory(szTmp, sizeof(szTmp))) { wsprintf(szLogFileName, "%s\\%s", szTmp, LOGFILENAME); if (GetFileAttributes(szLogFileName) != 0xFFFFFFFF) { // Make a backup of the current log file lstrcpyn(szTmp, szLogFileName, lstrlen(szLogFileName) - 2 ); // don't copy extension lstrcat(szTmp, "BAK"); SetFileAttributes(szTmp, FILE_ATTRIBUTE_NORMAL); DeleteFile(szTmp); MoveFile(szLogFileName, szTmp); } g_hLogFile = CreateFile(szLogFileName, GENERIC_WRITE|GENERIC_READ, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0,0); } } void WriteToLog(char *pszFormatString, ...) { va_list args; char *pszFullErrMsg = NULL; DWORD dwBytesWritten; if (g_hLogFile && g_hLogFile != INVALID_HANDLE_VALUE) { va_start(args, pszFormatString); FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_STRING, (LPCVOID) pszFormatString, 0, 0, (LPTSTR) &pszFullErrMsg, 0, &args); if (pszFullErrMsg) { WriteFile(g_hLogFile, pszFullErrMsg, lstrlen(pszFullErrMsg), &dwBytesWritten, NULL); LocalFree(pszFullErrMsg); } } } DWORD GenerateUniqueClientId() { CHAR MachineName[MAX_COMPUTERNAME_LENGTH + 1 ]; DWORD MachineNameLength; DWORD UniqueId; MachineNameLength = sizeof( MachineName ); GetComputerName( MachineName, &MachineNameLength ); do { UniqueId = GetTickCount(); UniqueId = Crc32( UniqueId, MachineName, MachineNameLength ); UniqueId = UniqueId & 0xFFFFFFF0; }while ( UniqueId == 0 ); return UniqueId; } BOOL MySetupDecompressOrCopyFile(IN LPCSTR SourceFile, IN LPCSTR TargetFile) { DWORD ErrorCode = pfSetupDecompressOrCopyFile( SourceFile, TargetFile, 0 ); if ( ErrorCode != NO_ERROR ) { SetLastError( ErrorCode ); return FALSE; } else { SetFileAttributes( TargetFile, FILE_ATTRIBUTE_NORMAL ); return TRUE; } } ULONG __fastcall TextToUnsignedNum(IN LPCSTR Text) { LPCSTR p = Text; ULONG n = 0; // // Very simplistic conversion stops at first non digit character, does // not require null-terminated string, and does not skip any whitespace // or commas. // while (( *p >= '0' ) && ( *p <= '9' )) { n = ( n * 10 ) + ( *p++ - '0' ); } return n; } LPSTR CombinePaths( IN LPCSTR ParentPath, IN LPCSTR ChildPath, OUT LPSTR TargetPath // can be same as ParentPath if want to append ) { ULONG ParentLength = strlen( ParentPath ); LPSTR p; if ( ParentPath != TargetPath ) { memcpy( TargetPath, ParentPath, ParentLength ); } p = TargetPath + ParentLength; if (( ParentLength > 0 ) && ( *( p - 1 ) != '\\' ) && ( *( p - 1 ) != '/' )) { *p++ = '\\'; } strcpy( p, ChildPath ); return TargetPath; } BOOL FixTimeStampOnCompressedFile(IN LPCSTR FileName) { // // NT4 setupapi uses timestamp on compressed file to set on // the target decompressed file. With streaming download, we // lose the timestamp on the file. But, the correct timestamp // lives inside the compressed file, so we'll open the file // to see if it is a diamond compressed file and if so, // extract the timestamp and set it on the compressed file. // Then, when setupapi expands the compressed file, it will // use that timestamp on the expanded file. // // A better fix is probably to tunnel the timestamp in the // pstream protocol, but too late to change that at this // point. // FILETIME LocalFileTime; FILETIME UtcFileTime; BOOL TimeSuccess; BOOL MapSuccess; HANDLE hSourceFile; PUCHAR pSourceFileMapped; DWORD dwSourceFileSize; DWORD dwOffset; USHORT DosDate; USHORT DosTime; PUCHAR p; TimeSuccess = FALSE; MapSuccess = MyMapViewOfFile(FileName, &dwSourceFileSize, &hSourceFile, (void**)&pSourceFileMapped); if ( MapSuccess ) { __try { p = pSourceFileMapped; if (( *(DWORD*)( p ) == 'FCSM' ) && // "MSCF" ( *(BYTE *)( p + 24 ) == 3 ) && // minor version ( *(BYTE *)( p + 25 ) == 1 ) && // major version ( *(WORD *)( p + 26 ) == 1 ) && // 1 folder ( *(WORD *)( p + 28 ) == 1 )) { // 1 file dwOffset = *(DWORD*)( p + 16 ); if (( dwOffset + 16 ) < dwSourceFileSize ) { DosDate = *(UNALIGNED WORD*)( p + dwOffset + 10 ); DosTime = *(UNALIGNED WORD*)( p + dwOffset + 12 ); if ( DosDateTimeToFileTime( DosDate, DosTime, &LocalFileTime ) && LocalFileTimeToFileTime( &LocalFileTime, &UtcFileTime )) { TimeSuccess = TRUE; } } } } __except(EXCEPTION_EXECUTE_HANDLER) { } MyUnmapViewOfFile( hSourceFile, pSourceFileMapped ); } if ( TimeSuccess ) { hSourceFile = CreateFile( FileName, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL ); if ( hSourceFile != INVALID_HANDLE_VALUE ) { if ( ! SetFileTime( hSourceFile, &UtcFileTime, &UtcFileTime, &UtcFileTime )) { TimeSuccess = FALSE; } CloseHandle( hSourceFile ); } } return TimeSuccess; } BOOL Assert(LPCSTR szText, LPCSTR szFile, DWORD dwLine) { CHAR Buffer[ 256 ]; wsprintf( Buffer, "ASSERT( %s ) FAILED, %s (%d)\n", szText, szFile, dwLine ); OutputDebugString( Buffer ); DebugBreak(); return FALSE; } BOOL MyMapViewOfFileByHandle(IN HANDLE FileHandle, OUT ULONG *FileSize, OUT PVOID *MapBase) { ULONG InternalFileSize; ULONG InternalFileSizeHigh; HANDLE InternalMapHandle; PVOID InternalMapBase; InternalFileSize = GetFileSize( FileHandle, &InternalFileSizeHigh ); if ( InternalFileSizeHigh != 0 ) { SetLastError( ERROR_OUTOFMEMORY ); return FALSE; } if ( InternalFileSize == 0 ) { *MapBase = NULL; *FileSize = 0; return TRUE; } if ( InternalFileSize != 0xFFFFFFFF ) { InternalMapHandle = CreateFileMapping( FileHandle, NULL, PAGE_WRITECOPY, 0, 0, NULL ); if ( InternalMapHandle != NULL ) { InternalMapBase = MapViewOfFile(InternalMapHandle, FILE_MAP_COPY, 0, 0, 0); CloseHandle( InternalMapHandle ); if ( InternalMapBase != NULL ) { DWORD dw = ROUNDUP2(InternalFileSize, 64); if(dw != InternalFileSize) { ZeroMemory((PBYTE)InternalMapBase + InternalFileSize, dw - InternalFileSize); } *MapBase = InternalMapBase; *FileSize = InternalFileSize; return TRUE; } } } return FALSE; } BOOL MyMapViewOfFile(IN LPCSTR FileName, OUT ULONG *FileSize, OUT HANDLE *FileHandle, OUT PVOID *MapBase) { HANDLE InternalFileHandle; BOOL Success; InternalFileHandle = CreateFileA( FileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL ); if ( InternalFileHandle != INVALID_HANDLE_VALUE ) { Success = MyMapViewOfFileByHandle(InternalFileHandle, FileSize, MapBase); if ( Success ) { *FileHandle = InternalFileHandle; return TRUE; } CloseHandle( InternalFileHandle ); } return FALSE; } VOID MyUnmapViewOfFile(IN HANDLE FileHandle, IN PVOID MapBase ) { ULONG LastError = GetLastError(); UnmapViewOfFile( MapBase ); CloseHandle( FileHandle ); SetLastError( LastError ); } VOID __fastcall ConvertToCompressedFileName(IN OUT LPSTR FileName) { ULONG NameLength = strlen( FileName ); ULONG DotIndex = NameLength; while (( DotIndex > 0 ) && ( FileName[ --DotIndex ] != '.' )) { if ( FileName[ DotIndex ] == '\\' ) { // end of filename part of path DotIndex = 0; // name has no extension break; } } if ( DotIndex > 0 ) { // name has an extension if (( NameLength - DotIndex ) <= 3 ) { // extension less than 3 chars FileName[ NameLength++ ] = '_'; // append '_' to extension FileName[ NameLength ] = 0; // terminate } else { // extension more than 3 chars FileName[ NameLength - 1 ] = '_'; // replace last char with '_' } } else { // name has no extension FileName[ NameLength++ ] = '.'; // append '.' FileName[ NameLength++ ] = '_'; // append '_' FileName[ NameLength ] = 0; // terminate } } LPTSTR __fastcall MySubAllocStrDup(IN HANDLE SubAllocator, IN LPCSTR String) { ULONG Length = lstrlen( String ); LPTSTR Buffer = (LPTSTR)SubAllocate( SubAllocator, Length + 1 ); if ( Buffer ) { memcpy( Buffer, String, Length ); // no need to copy NULL terminator } return Buffer; } // // Copied from Windows 95 unistal.exe cfg.c function CfgGetField BOOL GetFieldString(LPSTR lpszLine, int iField, LPSTR lpszField, int cbSize) { int cbField; LPSTR lpszChar, lpszEnd; // Find the field we are looking for lpszChar = lpszLine; // Each time we see a separator, decrement iField while (iField > 0 && (BYTE)*lpszChar > CR) { if (*lpszChar == '=' || *lpszChar == ',' || *lpszChar == ' ' ) { iField--; while (*lpszChar == '=' || *lpszChar== ',' || *lpszChar == ' ' && (BYTE)*lpszChar > 13) lpszChar++; } else lpszChar++; } // If we still have fields remaining then something went wrong if (iField) return FALSE; // Now find the end of this field lpszEnd = lpszChar; while (*lpszEnd != '=' && *lpszEnd != ',' && *lpszEnd != ' ' && (BYTE)*lpszEnd > CR) lpszEnd++; // Find the length of this field - make sure it'll fit in the buffer cbField = (int)((lpszEnd - lpszChar) + 1); if (cbField > cbSize) { // I return an error if the requested //cbField = cbSize; // data won't fit, rather than truncating return FALSE; // it at some random point! -JTP } // Note that the C runtime treats cbField as the number of characters // to copy from the source, and if that doesn't happen to transfer a NULL, // too bad. The Windows implementation of _lstrcpyn treats cbField as // the number of characters that can be stored in the destination, and // always copies a NULL (even if it means copying only cbField-1 characters // from the source). // The C runtime also pads the destination with NULLs if a NULL in the // source is found before cbField is exhausted. _lstrcpyn essentially quits // after copying a NULL. lstrcpyn(lpszField, lpszChar, cbField); return TRUE; } #define NUM_VERSION_NUM 4 void ConvertVersionStrToDwords(LPSTR pszVer, LPDWORD pdwVer, LPDWORD pdwBuild) { WORD rwVer[NUM_VERSION_NUM]; for(int i = 0; i < NUM_VERSION_NUM; i++) rwVer[i] = 0; for(i = 0; i < NUM_VERSION_NUM && pszVer; i++) { rwVer[i] = (WORD) StrToInt(pszVer); pszVer = ScanForChar(pszVer, '.', lstrlen(pszVer)); if (pszVer) pszVer++; } *pdwVer = (rwVer[0]<< 16) + rwVer[1]; *pdwBuild = (rwVer[2] << 16) + rwVer[3]; } LPSTR FindChar(LPSTR pszStr, char ch) { while( *pszStr != 0 && *pszStr != ch ) pszStr++; return pszStr; } DWORD GetStringField(LPSTR szStr, UINT uField, LPSTR szBuf, UINT cBufSize) { LPSTR pszBegin = szStr; LPSTR pszEnd; UINT i = 0; DWORD dwToCopy; if(cBufSize == 0) return 0; szBuf[0] = 0; if(szStr == NULL) return 0; while(*pszBegin != 0 && i < uField) { pszBegin = FindChar(pszBegin, ','); if(*pszBegin != 0) pszBegin++; i++; } // we reached end of string, no field if(*pszBegin == 0) { return 0; } pszEnd = FindChar(pszBegin, ','); while(pszBegin <= pszEnd && *pszBegin == ' ') pszBegin++; while(pszEnd > pszBegin && *(pszEnd - 1) == ' ') pszEnd--; if(pszEnd > (pszBegin + 1) && *pszBegin == '"' && *(pszEnd-1) == '"') { pszBegin++; pszEnd--; } dwToCopy = pszEnd - pszBegin + 1; if(dwToCopy > cBufSize) dwToCopy = cBufSize; lstrcpynA(szBuf, pszBegin, dwToCopy); return dwToCopy - 1; } BOOL GetHashidFromINF(LPCTSTR lpFileName, LPTSTR lpszHash, DWORD dwSize) { INFCONTEXT InfContext; if (pfSetupFindFirstLine(g_hInf, "SourceDisksFiles", lpFileName, &InfContext )) { if (pfSetupGetStringField(&InfContext, 5, lpszHash, dwSize, NULL )) { return TRUE; } } return FALSE; } #ifdef _M_IX86 // // Stupid x86 compiler doesn't have an intrinsic memchr, so we'll do our own. // #pragma warning( disable: 4035 ) // no return value LPSTR ScanForChar( IN LPSTR Buffer, IN CHAR SearchFor, IN ULONG MaxLength ) { __asm { mov edi, Buffer // pointer for scasb in edi mov al, SearchFor // looking for this char mov ecx, MaxLength // don't scan past this repne scasb // find the char lea eax, [edi-1] // edi points one past the found char jz RETURNIT // if didn't find it, xor eax, eax // return NULL RETURNIT: } } #pragma warning( default: 4035 ) // no return value #else // ! _M_IX86 LPSTR ScanForChar(IN LPSTR Buffer, IN CHAR SearchFor, IN ULONG MaxLength) { return memchr( Buffer, SearchFor, MaxLength ); } #endif // ! _M_IX86 PCHAR ScanForSequence(IN PCHAR Buffer, IN ULONG BufferLength, IN PCHAR Sequence, IN ULONG SequenceLength) { if ( BufferLength >= SequenceLength ) { PCHAR ScanEnd = Buffer + ( BufferLength - SequenceLength ) + 1; PCHAR ScanPtr = Buffer; while ( ScanPtr < ScanEnd ) { ScanPtr = ScanForChar( ScanPtr, *Sequence, ScanEnd - ScanPtr ); if ( ScanPtr == NULL ) { return NULL; } if ( memcmp( ScanPtr, Sequence, SequenceLength ) == 0 ) { return ScanPtr; } ++ScanPtr; } } return NULL; } //From shlwapi.... #define FAST_CharNext(p) CharNext(p) #define FILENAME_SEPARATOR '\\' #define CH_WHACK TEXT(FILENAME_SEPARATOR) LPTSTR PathFindFileName(LPCTSTR pPath) { LPCTSTR pT = pPath; if (pPath) { for ( ; *pPath; pPath = FAST_CharNext(pPath)) { if ((pPath[0] == TEXT('\\') || pPath[0] == TEXT(':') || pPath[0] == TEXT('/')) && pPath[1] && pPath[1] != TEXT('\\') && pPath[1] != TEXT('/')) pT = pPath + 1; } } return (LPTSTR)pT; // const -> non const } LPTSTR PathFindExtension(LPCTSTR pszPath) { LPCTSTR pszDot = NULL; if (pszPath) { for (; *pszPath; pszPath = FAST_CharNext(pszPath)) { switch (*pszPath) { case TEXT('.'): pszDot = pszPath; // remember the last dot break; case CH_WHACK: case TEXT(' '): // extensions can't have spaces pszDot = NULL; // forget last dot, it was in a directory break; } } } // if we found the extension, return ptr to the dot, else // ptr to end of the string (NULL extension) (cast->non const) return pszDot ? (LPTSTR)pszDot : (LPTSTR)pszPath; } LPSTR StrDup(LPCSTR psz) { LPSTR pszRet = (LPSTR)LocalAlloc(LPTR, (lstrlenA(psz) + 1) * sizeof(*pszRet)); if (pszRet) { lstrcpyA(pszRet, psz); } return pszRet; } DWORD MyFileSize( PCSTR pszFile ) { HFILE hFile; OFSTRUCT ofStru; DWORD dwSize = 0; if ( *pszFile == 0 ) return 0; hFile = OpenFile( pszFile, &ofStru, OF_READ ); if ( hFile != HFILE_ERROR ) { dwSize = GetFileSize( (HANDLE)hFile, NULL ); _lclose( hFile ); } return dwSize; } void GetLanguageString(LPTSTR lpszLang) { char szTmp[MAX_PATH]; DWORD dwLang, dwCharSet; //default to EN lstrcpy(lpszLang, "EN"); GetModuleFileName( g_hInstance, szTmp, sizeof(szTmp) ); MyGetVersionFromFile(szTmp, &dwLang, &dwCharSet, FALSE); for(int i = 0; g_LangID[i].dwLangID != -1; i++) { if(g_LangID[i].dwLangID == dwLang) { lstrcpy(lpszLang, g_LangID[i].szLanguage); break; } } } BOOL CenterWindow (HWND hwndChild, HWND hwndParent) { RECT rChild, rParent; int wChild, hChild, wParent, hParent; int wScreen, hScreen, xNew, yNew; HDC hdc; // Get the Height and Width of the child window GetWindowRect (hwndChild, &rChild); wChild = rChild.right - rChild.left; hChild = rChild.bottom - rChild.top; // Get the Height and Width of the parent window GetWindowRect (hwndParent, &rParent); wParent = rParent.right - rParent.left; hParent = rParent.bottom - rParent.top; // Get the display limits hdc = GetDC (hwndChild); wScreen = GetDeviceCaps (hdc, HORZRES); hScreen = GetDeviceCaps (hdc, VERTRES); ReleaseDC (hwndChild, hdc); // Calculate new X position, then adjust for screen xNew = rParent.left + ((wParent - wChild) /2); if (xNew < 0) { xNew = 0; } else if ((xNew+wChild) > wScreen) { xNew = wScreen - wChild; } // Calculate new Y position, then adjust for screen yNew = rParent.top + ((hParent - hChild) /2); if (yNew < 0) { yNew = 0; } else if ((yNew+hChild) > hScreen) { yNew = hScreen - hChild; } // Set it, and return return SetWindowPos (hwndChild, NULL, xNew, yNew, 0, 0, SWP_NOSIZE | SWP_NOZORDER); }