BOOL WINAPI InstallFile( LPCTSTR lpszSource, LPTSTR lpszDest, LPTSTR lpszDestAppend ) { static const TCHAR szShellExt[] = TEXT("ShellExt"); static const TCHAR szDestFile[] = TEXT("\\") TEXT(HASHCHECK_FILENAME_STR); SSStaticCpy(lpszDestAppend, szShellExt); lpszDestAppend += countof(szShellExt) - 1; // Create directory if necessary if (! PathFileExists(lpszDest)) CreateDirectory(lpszDest, NULL); SSStaticCpy(lpszDestAppend, szDestFile); lpszDestAppend += countof(szDestFile) - 1; // No need to copy if the source and destination are the same if (StrCmpI(lpszSource, lpszDest) == 0) return(TRUE); // If the destination file does not already exist, just copy if (! PathFileExists(lpszDest)) return(CopyFile(lpszSource, lpszDest, FALSE)); // If destination file exists and cannot be overwritten TCHAR szTemp[MAX_PATH + 0x20]; SIZE_T cbDest = BYTEDIFF(lpszDestAppend, lpszDest); LPTSTR lpszTempAppend = (LPTSTR)BYTEADD(szTemp, cbDest); StringCbCopy(szTemp, sizeof(szTemp), lpszDest); *lpszTempAppend++ = TEXT('.'); SSCpy2Ch(lpszTempAppend, 0, 0); for (TCHAR ch = TEXT('0'); ch <= TEXT('9'); ++ch) { if (CopyFile(lpszSource, lpszDest, FALSE)) return(TRUE); *lpszTempAppend = ch; if (MoveFileEx(lpszDest, szTemp, MOVEFILE_REPLACE_EXISTING)) MoveFileEx(szTemp, NULL, MOVEFILE_DELAY_UNTIL_REBOOT); } return(FALSE); }
HRESULT Uninstall( ) { HRESULT hr = S_OK; TCHAR szCurrentDllPath[MAX_PATH << 1]; TCHAR szTemp[MAX_PATH << 1]; LPTSTR lpszFileToDelete = szCurrentDllPath; LPTSTR lpszTempAppend = szTemp + GetModuleFileName(g_hModThisDll, szTemp, countof(szTemp)); StringCbCopy(szCurrentDllPath, sizeof(szCurrentDllPath), szTemp); #ifdef _WIN64 // If this 64-bit dll was installed to the default location, // uninstall the 32-bit dll if it exists in its default location TCHAR lpszDefInstallPath[MAX_PATH + 0x20]; UINT uSize = GetSystemDirectory(lpszDefInstallPath, MAX_PATH); if (uSize && uSize < MAX_PATH) { LPTSTR lpszPathAppend = lpszDefInstallPath + uSize; if (*(lpszPathAppend - 1) != TEXT('\\')) *lpszPathAppend++ = TEXT('\\'); static const TCHAR szFolderAndFilename[] = TEXT("ShellExt") TEXT("\\") TEXT(HASHCHECK_FILENAME_STR); SSStaticCpy(lpszPathAppend, szFolderAndFilename); // If this 64-bit dll was installed to the default location if (StrCmpI(szCurrentDllPath, lpszDefInstallPath) == 0) { TCHAR lpszSystemWow64[MAX_PATH + 0x20]; uSize = GetSystemWow64Directory(lpszSystemWow64, MAX_PATH); if (uSize && uSize < MAX_PATH) { LPTSTR lpszSystemWow64Append = lpszSystemWow64 + uSize; if (*(lpszSystemWow64Append - 1) != TEXT('\\')) SSCpy2Ch(lpszSystemWow64Append++, TEXT('\\'), 0); StringCbCopyEx(lpszDefInstallPath, sizeof(lpszDefInstallPath), lpszSystemWow64, &lpszPathAppend, NULL, 0); SSStaticCpy(lpszPathAppend, szFolderAndFilename); // If the 32-bit dll exists in its default location if (PathFileExists(lpszDefInstallPath)) { static const TCHAR szRegsvr32[] = TEXT("regsvr32.exe"); SSStaticCpy(lpszSystemWow64Append, szRegsvr32); // the lpszSystemWow64 buffer now contains the full regsvr32.exe path TCHAR lpszCommandLine[MAX_PATH + 0x20]; LPTSTR lpszCommandLineAppend; static const TCHAR szCommandOpts[] = TEXT("regsvr32.exe /u /i /n /s "); lpszCommandLineAppend = SSStaticCpy(lpszCommandLine, szCommandOpts) - 1; StringCbCopy(lpszCommandLineAppend, sizeof(lpszCommandLine)-sizeof(szCommandOpts), lpszDefInstallPath); STARTUPINFO si; memset(&si, 0, sizeof(si)); si.cb = sizeof(si); PROCESS_INFORMATION pi; memset(&pi, 0, sizeof(pi)); if (!CreateProcess(lpszSystemWow64, lpszCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) return E_FAIL; DWORD dwExit; WaitForSingleObject(pi.hProcess, INFINITE); GetExitCodeProcess(pi.hProcess, &dwExit); CloseHandle(pi.hThread); CloseHandle(pi.hProcess); if (dwExit != 0) return E_FAIL; } } } } #endif // Rename the DLL prior to scheduling it for deletion *lpszTempAppend++ = TEXT('.'); SSCpy2Ch(lpszTempAppend, 0, 0); for (TCHAR ch = TEXT('0'); ch <= TEXT('9'); ++ch) { *lpszTempAppend = ch; if (MoveFileEx(szCurrentDllPath, szTemp, MOVEFILE_REPLACE_EXISTING)) { lpszFileToDelete = szTemp; break; } } // Schedule the DLL to be deleted at shutdown/reboot if (!MoveFileEx(lpszFileToDelete, NULL, MOVEFILE_DELAY_UNTIL_REBOOT)) hr = E_FAIL; // Unregister if (DllUnregisterServer() != S_OK) hr = E_FAIL; // Disassociate file extensions; see the comment in DllUnregisterServer for // why this step is skipped for Wow64 processes if (!Wow64CheckProcess()) { for (UINT i = 0; i < countof(g_szHashExtsTab); ++i) { HKEY hKey; if (hKey = RegOpen(HKEY_CLASSES_ROOT, g_szHashExtsTab[i], NULL, FALSE)) { RegGetSZ(hKey, NULL, szTemp, sizeof(szTemp)); if (_tcscmp(szTemp, PROGID_STR_HashCheck) == 0) RegDeleteValue(hKey, NULL); RegCloseKey(hKey); } } } // We don't need the uninstall strings any more... RegDelete(HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%s"), CLSNAME_STR_HashCheck); return(hr); }
BOOL WINAPI HashCalcWriteResult( PHASHCALCCONTEXT phcctx, PHASHCALCITEM pItem ) { PCTSTR pszHash; PVOID pvLine; INT cchLine, cbLine; // Length of line, in TCHARs or BYTEs, EXCLUDING the terminator if (!pItem->bValid) return(FALSE); // Set szFormat is necessary if (phcctx->szFormat[0] == 0) { // Did I ever mention that I hate SFV? // The reason we tracked cchMax was because of this idiotic format if (phcctx->ofn.nFilterIndex == 1) { wnsprintf( phcctx->szFormat, countof(phcctx->szFormat), TEXT("%%-%ds %%s\r\n"), phcctx->cchMax - phcctx->cchAdjusted ); } else { SSStaticCpy(phcctx->szFormat, TEXT("%s *%s\r\n")); } } // Translate the filter index to a hash switch (phcctx->ofn.nFilterIndex) { case 1: pszHash = pItem->results.szHexCRC32; break; case 2: pszHash = pItem->results.szHexMD4; break; case 3: pszHash = pItem->results.szHexMD5; break; case 4: pszHash = pItem->results.szHexSHA1; break; } // Format the line #define HashCalcFormat(a, b) wnsprintf(phcctx->scratch.sz, MAX_PATH_BUFFER, phcctx->szFormat, a, b) cchLine = (phcctx->ofn.nFilterIndex == 1) ? HashCalcFormat(pItem->szPath + phcctx->cchAdjusted, pszHash) : // SFV HashCalcFormat(pszHash, pItem->szPath + phcctx->cchAdjusted); // everything else #undef HashCalcFormat if (cchLine > 0) { // Convert to the correct encoding switch (phcctx->opt.dwSaveEncoding) { case 0: { // UTF-8 #ifdef UNICODE cbLine = WStrToUTF8(phcctx->scratch.szW, phcctx->scratch.szA, countof(phcctx->scratch.szA)) - 1; #else AStrToWStr(phcctx->scratch.szA, phcctx->scratch.szW, countof(phcctx->scratch.szW)); cbLine = WStrToUTF8(phcctx->scratch.szW, phcctx->scratch.szA, countof(phcctx->scratch.szA)) - 1; #endif pvLine = phcctx->scratch.szA; break; } case 1: { // UTF-16 #ifndef UNICODE cchLine = AStrToWStr(phcctx->scratch.szA, phcctx->scratch.szW, countof(phcctx->scratch.szW)) - 1; #endif cbLine = cchLine * sizeof(WCHAR); pvLine = phcctx->scratch.szW; break; } case 2: { // ANSI #ifdef UNICODE cbLine = WStrToAStr(phcctx->scratch.szW, phcctx->scratch.szA, countof(phcctx->scratch.szA)) - 1; #else cbLine = cchLine; #endif pvLine = phcctx->scratch.szA; break; } } if (cbLine > 0) { INT cbWritten; WriteFile(phcctx->hFileOut, pvLine, cbLine, &cbWritten, NULL); if (cbLine != cbWritten) return(FALSE); } else return(FALSE); } else return(FALSE); return(TRUE); }
HRESULT Install( BOOL bRegisterUninstaller, BOOL bCopyFile ) { TCHAR szCurrentDllPath[MAX_PATH << 1]; GetModuleFileName(g_hModThisDll, szCurrentDllPath, countof(szCurrentDllPath)); TCHAR szSysDir[MAX_PATH + 0x20]; UINT uSize = GetSystemDirectory(szSysDir, MAX_PATH); if (uSize && uSize < MAX_PATH) { LPTSTR lpszPath = szSysDir; LPTSTR lpszPathAppend = lpszPath + uSize; if (*(lpszPathAppend - 1) != TEXT('\\')) *lpszPathAppend++ = TEXT('\\'); LPTSTR lpszTargetPath = (bCopyFile) ? lpszPath : szCurrentDllPath; if ( (!bCopyFile || InstallFile(szCurrentDllPath, lpszTargetPath, lpszPathAppend)) && DllRegisterServerEx(lpszTargetPath) == S_OK ) { HKEY hKey, hKeySub; // Associate file extensions for (UINT i = 0; i < countof(g_szHashExtsTab); ++i) { if (hKey = RegOpen(HKEY_CLASSES_ROOT, g_szHashExtsTab[i], NULL, TRUE)) { RegSetSZ(hKey, NULL, PROGID_STR_HashCheck); RegSetSZ(hKey, TEXT("PerceivedType"), TEXT("text")); if (hKeySub = RegOpen(hKey, TEXT("PersistentHandler"), NULL, TRUE)) { RegSetSZ(hKeySub, NULL, TEXT("{5e941d80-bf96-11cd-b579-08002b30bfeb}")); RegCloseKey(hKeySub); } RegCloseKey(hKey); } } // Disassociate former file extensions; see the comment in DllUnregisterServer for // why this step is skipped for Wow64 processes if (!Wow64CheckProcess()) { for (UINT i = 0; i < countof(szFormerHashExtsTab); ++i) { HKEY hKey; if (hKey = RegOpen(HKEY_CLASSES_ROOT, szFormerHashExtsTab[i], NULL, FALSE)) { TCHAR szTemp[countof(PROGID_STR_HashCheck)]; RegGetSZ(hKey, NULL, szTemp, sizeof(szTemp)); if (_tcscmp(szTemp, PROGID_STR_HashCheck) == 0) RegDeleteValue(hKey, NULL); RegCloseKey(hKey); } } } // Uninstaller entries RegDelete(HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%s"), CLSNAME_STR_HashCheck); if (bRegisterUninstaller && (hKey = RegOpen(HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%s"), CLSNAME_STR_HashCheck, TRUE))) { TCHAR szUninstall[MAX_PATH << 1]; StringCchPrintf(szUninstall, countof(szUninstall), TEXT("regsvr32.exe /u /i /n \"%s\""), lpszTargetPath); static const TCHAR szURLFull[] = TEXT("https://github.com/gurnec/HashCheck/issues"); TCHAR szURLBase[countof(szURLFull)]; SSStaticCpy(szURLBase, szURLFull); szURLBase[35] = 0; // strlen("https://github.com/gurnec/HashCheck") RegSetSZ(hKey, TEXT("DisplayIcon"), lpszTargetPath); RegSetSZ(hKey, TEXT("DisplayName"), TEXT(HASHCHECK_NAME_STR)); RegSetSZ(hKey, TEXT("DisplayVersion"), TEXT(HASHCHECK_VERSION_STR)); RegSetDW(hKey, TEXT("EstimatedSize"), 1073); RegSetSZ(hKey, TEXT("HelpLink"), szURLFull); RegSetDW(hKey, TEXT("NoModify"), 1); RegSetDW(hKey, TEXT("NoRepair"), 1); RegSetSZ(hKey, TEXT("UninstallString"), szUninstall); RegSetSZ(hKey, TEXT("URLInfoAbout"), szURLBase); RegSetSZ(hKey, TEXT("URLUpdateInfo"), TEXT("https://github.com/gurnec/HashCheck/releases/latest")); RegCloseKey(hKey); } return(S_OK); } // if copied & registered } // if valid sysdir return(E_FAIL); }
VOID __fastcall HashVerifyWorkerMain( PHASHVERIFYCONTEXT phvctx ) { // Note that ALL message communication to and from the main window MUST // be asynchronous, or else there may be a deadlock PHASHVERIFYITEM pItem; // We need to keep track of the thread's execution time so that we can do a // sound notification of completion when appropriate DWORD dwTickStart = GetTickCount(); // Initialize the path prefix length; used for building the full path PTSTR pszPathTail = StrRChr(phvctx->pszPath, NULL, TEXT('\\')); SIZE_T cchPathPrefix = (pszPathTail) ? pszPathTail + 1 - phvctx->pszPath : 0; while (pItem = SLGetDataAndStep(phvctx->hList)) { BOOL bSuccess; // Part 1: Build the path { SIZE_T cchPrefix = cchPathPrefix; // Do not use the prefix if pszDisplayName is an absolute path if ( pItem->pszDisplayName[0] == TEXT('\\') || pItem->pszDisplayName[1] == TEXT(':') ) { cchPrefix = 0; } SSChainNCpy2( phvctx->ex.pszPath, phvctx->pszPath, cchPrefix, pItem->pszDisplayName, pItem->cchDisplayName ); } // Part 2: Calculate the checksum WorkerThreadHashFile( (PCOMMONCONTEXT)phvctx, phvctx->ex.pszPath, &bSuccess, &phvctx->whctx, NULL, &pItem->filesize ); if (phvctx->status == CANCEL_REQUESTED) return; // Part 3: Do something with the results if (bSuccess) { if (phvctx->whctx.flags == WHEX_ALL128) { // If the MD4/MD5 STILL has not been settled by this point, then // settle it by a simple heuristic: if the checksum matches MD4, // go with that, otherwise default to MD5. if (StrCmpI(pItem->pszExpected, phvctx->whctx.results.szHexMD4) == 0) phvctx->whctx.flags = WHEX_CHECKMD4; else phvctx->whctx.flags = WHEX_CHECKMD5; } switch (phvctx->whctx.flags) { case WHEX_CHECKCRC32: SSStaticCpy(pItem->szActual, phvctx->whctx.results.szHexCRC32); break; case WHEX_CHECKMD4: SSStaticCpy(pItem->szActual, phvctx->whctx.results.szHexMD4); break; case WHEX_CHECKMD5: SSStaticCpy(pItem->szActual, phvctx->whctx.results.szHexMD5); break; case WHEX_CHECKSHA1: SSStaticCpy(pItem->szActual, phvctx->whctx.results.szHexSHA1); break; } if (StrCmpI(pItem->pszExpected, pItem->szActual) == 0) pItem->uStatusID = HV_STATUS_MATCH; else pItem->uStatusID = HV_STATUS_MISMATCH; } else { pItem->uStatusID = HV_STATUS_UNREADABLE; } // Part 4: Update the UI ++phvctx->cSentMsgs; PostMessage(phvctx->hWnd, HM_WORKERTHREAD_UPDATE, (WPARAM)phvctx, (LPARAM)pItem); } // Play a sound to signal the normal, successful termination of operations, // but exempt operations that were nearly instantaneous if (phvctx->cTotal && GetTickCount() - dwTickStart >= 2000) MessageBeep(MB_ICONASTERISK); }