From c8c09a84e610b28493b237bb1fb4014f89c38a5f Mon Sep 17 00:00:00 2001 From: Christopher Gurnee Date: Sun, 14 Aug 2016 13:30:29 -0400 Subject: [PATCH] add several safety/consistency and UI improvements HashCheck will never save a partially complete checksum file: * canceling an in-progress save causes the checksum file to be deleted * the Save button on file properties is unavailable if Stop is pressed File read errors are now more visible: * in a checksum file, they are included as all 0's so that verify fails * on file properties they are included as all X's to stand out When selected hashes in Options change, the tab is updated immediately: * any hashes which have already been calculated are not recalculated Saving checksum files with an unselected hash from file properties works --- HashCalc.c | 89 ++++++++--- HashCalc.h | 5 +- HashCheck.rc | 2 +- HashCheckCommon.c | 48 ++++-- HashCheckCommon.h | 17 ++- HashCheckTranslations.rc | 2 +- HashProp.c | 316 +++++++++++++++++++++++++++------------ HashSave.cpp | 32 +++- HashVerify.cpp | 21 +-- installer/HashCheck.nsi | 8 +- libs/SimpleList.c | 21 ++- libs/SimpleList.h | 8 +- libs/WinHash.cpp | 29 ++-- libs/WinHash.h | 7 +- version.h | 4 +- 15 files changed, 424 insertions(+), 185 deletions(-) diff --git a/HashCalc.c b/HashCalc.c index 774849c..723620f 100644 --- a/HashCalc.c +++ b/HashCalc.c @@ -44,7 +44,7 @@ __forceinline VOID WINAPI HashCalcSetSavePrefix( PHASHCALCCONTEXT phcctx, PTSTR Path processing \*============================================================================*/ -VOID WINAPI HashCalcPrepare( PHASHCALCCONTEXT phcctx ) +BOOL WINAPI HashCalcPrepare( PHASHCALCCONTEXT phcctx ) { PTSTR pszPrev = NULL; PTSTR pszCurrent, pszCurrentEnd; @@ -128,7 +128,7 @@ VOID WINAPI HashCalcPrepare( PHASHCALCCONTEXT phcctx ) if (pItem) { - pItem->bValid = FALSE; + pItem->results.dwFlags = 0; pItem->cchPath = cchCurrent; memcpy(pItem->szPath, pszCurrent, cbCurrent); @@ -143,10 +143,11 @@ VOID WINAPI HashCalcPrepare( PHASHCALCCONTEXT phcctx ) if (phcctx->status == PAUSED) WaitForSingleObject(phcctx->hUnpauseEvent, INFINITE); if (phcctx->status == CANCEL_REQUESTED) - return; + return(FALSE); pszPrev = pszCurrent; } + return(TRUE); } VOID WINAPI HashCalcWalkDirectory( PHASHCALCCONTEXT phcctx, PTSTR pszPath, UINT cchPath ) @@ -192,7 +193,7 @@ VOID WINAPI HashCalcWalkDirectory( PHASHCALCCONTEXT phcctx, PTSTR pszPath, UINT if (pItem) { - pItem->bValid = FALSE; + pItem->results.dwFlags = 0; pItem->cchPath = cchNew; memcpy(pItem->szPath, pszPath, cbPathBuffer); @@ -300,8 +301,6 @@ VOID WINAPI HashCalcInitSave( PHASHCALCCONTEXT phcctx ) phcctx->ofn.nFilterIndex && phcctx->ofn.nFilterIndex <= NUM_HASHES) { - BOOL bSuccess = FALSE; - // Save the filter in the user's preferences if (phcctx->opt.dwFilterIndex != phcctx->ofn.nFilterIndex) { @@ -330,7 +329,7 @@ VOID WINAPI HashCalcInitSave( PHASHCALCCONTEXT phcctx ) // Open the file for output phcctx->hFileOut = CreateFile( pszFile, - FILE_APPEND_DATA, + FILE_APPEND_DATA | DELETE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, @@ -385,19 +384,32 @@ VOID WINAPI HashCalcSetSaveFormat( PHASHCALCCONTEXT phcctx ) BOOL WINAPI HashCalcWriteResult( PHASHCALCCONTEXT phcctx, PHASHCALCITEM pItem ) { - PCTSTR pszHash; - WCHAR szWbuffer[MAX_PATH_BUFFER]; - CHAR szAbuffer[MAX_PATH_BUFFER]; + PCTSTR pszHash; // will be pointed to the hash name + WCHAR szWbuffer[MAX_PATH_BUFFER]; // wide-char buffer + CHAR szAbuffer[MAX_PATH_BUFFER]; // narrow-char buffer #ifdef UNICODE # define szTbuffer szWbuffer #else # define szTbuffer szAbuffer #endif - PVOID pvLine; // Will be pointed to the buffer to write out - size_t cchLine, cbLine; // Length of line, in TCHARs or BYTEs, EXCLUDING the terminator - - if (!pItem->bValid) - return(FALSE); + PTSTR szTbufferAppend = szTbuffer; // current end of the buffer used to build output + size_t cchLine = MAX_PATH_BUFFER; // starts off as count of remaining TCHARS in the buffer + PVOID pvLine; // will be pointed to the buffer to write out + size_t cbLine; // will be line length in bytes, EXCLUDING nul terminator + BOOL bRetval = TRUE; + + // If the checksum to save isn't present in the results + if (! ((1 << (phcctx->ofn.nFilterIndex - 1)) & pItem->results.dwFlags)) + { + // Start with a commented-out error message - "; UNREADABLE:" + WCHAR szUnreadable[MAX_STRINGRES]; + LoadString(g_hModThisDll, IDS_HV_STATUS_UNREADABLE, szUnreadable, MAX_STRINGRES); + StringCchPrintfEx(szTbufferAppend, cchLine, &szTbufferAppend, &cchLine, 0, TEXT("; %s:\r\n"), szUnreadable); + + // We'll still output a hash, but it will be all 0's, that way Verify will indicate an mismatch + HashCalcClearInvalid(&pItem->results, TEXT('0')); + bRetval = FALSE; + } // Translate the filter index to a hash switch (phcctx->ofn.nFilterIndex) @@ -409,19 +421,18 @@ BOOL WINAPI HashCalcWriteResult( PHASHCALCCONTEXT phcctx, PHASHCALCITEM pItem ) } // Format the line - #define HashCalcFormat(a, b) StringCchPrintfEx(szTbuffer, MAX_PATH_BUFFER, NULL, &cchLine, 0, phcctx->szFormat, a, b) + #define HashCalcFormat(a, b) StringCchPrintfEx(szTbufferAppend, cchLine, &szTbufferAppend, &cchLine, 0, phcctx->szFormat, a, b) (phcctx->ofn.nFilterIndex == 1) ? HashCalcFormat(pItem->szPath + phcctx->cchAdjusted, pszHash) : // SFV HashCalcFormat(pszHash, pItem->szPath + phcctx->cchAdjusted); // everything else #undef HashCalcFormat - // cchLine is temporarily the count of characters left in the buffer instead of the line length #ifdef _TIMED - StringCchPrintfEx(szTbuffer + (MAX_PATH_BUFFER-cchLine), cchLine, NULL, &cchLine, 0, + StringCchPrintfEx(szTbufferAppend, cchLine, NULL, &cchLine, 0, _T("; Elapsed: %d ms\r\n"), pItem->dwElapsed); #endif - cchLine = MAX_PATH_BUFFER - cchLine; // now it's back to being the line length + cchLine = MAX_PATH_BUFFER - cchLine; // from now on cchLine is the line length in bytes, EXCLUDING nul terminator if (cchLine > 0) { // Convert to the correct encoding @@ -479,7 +490,45 @@ BOOL WINAPI HashCalcWriteResult( PHASHCALCCONTEXT phcctx, PHASHCALCITEM pItem ) } else return(FALSE); - return(TRUE); + return(bRetval); +} + +VOID WINAPI HashCalcClearInvalid( PWHRESULTEX pwhres, WCHAR cInvalid ) +{ +#ifdef UNICODE +# define _tmemset wmemset +#else +# define _tmemset memset +#endif + +#define HASH_CLEAR_INVALID_op(alg) \ + if (! (pwhres->dwFlags & WHEX_CHECK##alg)) \ + { \ + _tmemset(pwhres->szHex##alg, cInvalid, countof(pwhres->szHex##alg) - 1); \ + pwhres->szHex##alg[countof(pwhres->szHex##alg) - 1] = L'\0'; \ + } + FOR_EACH_HASH(HASH_CLEAR_INVALID_op) +} + +// This can only succeed on Windows Vista and later; +// returns FALSE on failure +BOOL WINAPI HashCalcDeleteFileByHandle(HANDLE hFile) +{ + if (hFile == INVALID_HANDLE_VALUE) + return(FALSE); + + HMODULE hKernel32 = GetModuleHandle(TEXT("kernel32.dll")); + if (hKernel32 == NULL) + return(FALSE); + + typedef BOOL(WINAPI* PFN_SFIBH)(_In_ HANDLE, _In_ FILE_INFO_BY_HANDLE_CLASS, _In_ LPVOID, _In_ DWORD); + PFN_SFIBH pfnSetFileInformationByHandle = (PFN_SFIBH)GetProcAddress(hKernel32, "SetFileInformationByHandle"); + if (pfnSetFileInformationByHandle == NULL) + return(FALSE); + + FILE_DISPOSITION_INFO fdi; + fdi.DeleteFile = TRUE; + return(pfnSetFileInformationByHandle(hFile, FileDispositionInfo, &fdi, sizeof(fdi))); } VOID WINAPI HashCalcSetSavePrefix( PHASHCALCCONTEXT phcctx, PTSTR pszSave ) diff --git a/HashCalc.h b/HashCalc.h index 6f7c8ba..25ad3e0 100644 --- a/HashCalc.h +++ b/HashCalc.h @@ -90,7 +90,6 @@ typedef struct { // Per-file data typedef struct { - BOOL bValid; // FALSE if the file could not be opened UINT cchPath; // length of path in characters, not including NULL WHRESULTEX results; // hash results #ifdef _TIMED @@ -101,10 +100,12 @@ typedef struct { } HASHCALCITEM, *PHASHCALCITEM; // Public functions -VOID WINAPI HashCalcPrepare( PHASHCALCCONTEXT phcctx ); +BOOL WINAPI HashCalcPrepare( PHASHCALCCONTEXT phcctx ); VOID WINAPI HashCalcInitSave( PHASHCALCCONTEXT phcctx ); VOID WINAPI HashCalcSetSaveFormat( PHASHCALCCONTEXT phcctx ); BOOL WINAPI HashCalcWriteResult( PHASHCALCCONTEXT phcctx, PHASHCALCITEM pItem ); +VOID WINAPI HashCalcClearInvalid( PWHRESULTEX pwhres, WCHAR cInvalid ); +BOOL WINAPI HashCalcDeleteFileByHandle( HANDLE hFile ); VOID WINAPI HashCalcTogglePrep( PHASHCALCCONTEXT phcctx, BOOL bState ); #ifdef __cplusplus diff --git a/HashCheck.rc b/HashCheck.rc index 9bcdd74..50e93d5 100644 --- a/HashCheck.rc +++ b/HashCheck.rc @@ -128,7 +128,7 @@ IDD_OPTIONS DIALOGEX 10, 10, 200, 264 AUTORADIOBUTTON "", IDC_OPT_ENCODING_UTF16, 13, 99, 174, 10 AUTORADIOBUTTON "", IDC_OPT_ENCODING_ANSI, 13, 113, 174, 10 GROUPBOX "", IDC_OPT_CHK, 7, 137, 186, 58, WS_GROUP - AUTOCHECKBOX "CRC-32",IDC_OPT_CHK_CRC32, 13, 150, 54, 10, WS_TABSTOP + AUTOCHECKBOX "C&RC-32",IDC_OPT_CHK_CRC32, 13, 150, 54, 10, WS_TABSTOP AUTOCHECKBOX "MD5",IDC_OPT_CHK_MD5, 13, 164, 54, 10, WS_TABSTOP AUTOCHECKBOX "SHA-1",IDC_OPT_CHK_SHA1, 13, 178, 54, 10, WS_TABSTOP AUTOCHECKBOX "SHA-256",IDC_OPT_CHK_SHA256, 100, 150, 54, 10, WS_TABSTOP diff --git a/HashCheckCommon.c b/HashCheckCommon.c index 755134b..205b76a 100644 --- a/HashCheckCommon.c +++ b/HashCheckCommon.c @@ -28,6 +28,8 @@ HANDLE __fastcall CreateThreadCRT( PVOID pThreadProc, PVOID pvParam ) pcmnctx->cHandledMsgs = 0; pcmnctx->hWndPBTotal = GetDlgItem(pcmnctx->hWnd, IDC_PROG_TOTAL); pcmnctx->hWndPBFile = GetDlgItem(pcmnctx->hWnd, IDC_PROG_FILE); + if (pcmnctx->hUnpauseEvent == NULL) + pcmnctx->hUnpauseEvent = CreateEvent(NULL, TRUE, TRUE, NULL); SendMessage(pcmnctx->hWndPBFile, PBM_SETRANGE, 0, MAKELPARAM(0, PROGRESS_BAR_STEPS)); pThreadProc = WorkerThreadStartup; @@ -138,7 +140,8 @@ VOID WINAPI SetProgressBarPause( PCOMMONCONTEXT pcmnctx, WPARAM iState ) // Vista's progress bar is buggy--if you pause it while it is animating, // the color will not change (but it will stop animating), so it may // be necessary to send another PBM_SETSTATE to get it right - SetTimer(pcmnctx->hWnd, TIMER_ID_PAUSE, 750, NULL); + if (iState == PBST_PAUSED) + SetTimer(pcmnctx->hWnd, TIMER_ID_PAUSE, 750, NULL); } } @@ -188,7 +191,7 @@ VOID WINAPI WorkerThreadStop( PCOMMONCONTEXT pcmnctx ) } // Disable the control buttons - if (!(pcmnctx->dwFlags & HCF_EXIT_PENDING)) + if (! (pcmnctx->dwFlags & (HCF_EXIT_PENDING | HCF_RESTARTING))) { EnableWindow(GetDlgItem(pcmnctx->hWnd, IDC_PAUSE), FALSE); EnableWindow(GetDlgItem(pcmnctx->hWnd, IDC_STOP), FALSE); @@ -203,11 +206,11 @@ VOID WINAPI WorkerThreadCleanup( PCOMMONCONTEXT pcmnctx ) // There are only two times this function gets called: // Case 1: The worker thread has exited on its own, and this function // was invoked in response to the thread's exit notification. - // Case 2: A forced abort was requested (app exit, system shutdown, etc.), + // Case 2: A forced abort was requested (app exit, settings change, etc.), // where this is called right after calling WorkerThreadStop to signal the // thread to exit. - if (pcmnctx->hThread) + if (pcmnctx->hThread != NULL) { if (pcmnctx->status != INACTIVE) { @@ -220,12 +223,19 @@ VOID WINAPI WorkerThreadCleanup( PCOMMONCONTEXT pcmnctx ) } CloseHandle(pcmnctx->hThread); - CloseHandle(pcmnctx->hUnpauseEvent); + pcmnctx->hThread = NULL; } + // If we're done with the Unpause event and it's open, close it + if (!(pcmnctx->dwFlags & HCF_RESTARTING) && pcmnctx->hUnpauseEvent != NULL) + { + CloseHandle(pcmnctx->hUnpauseEvent); + pcmnctx->hUnpauseEvent = NULL; + } + pcmnctx->status = CLEANUP_COMPLETED; - if (!(pcmnctx->dwFlags & HCF_EXIT_PENDING)) + if (! (pcmnctx->dwFlags & (HCF_EXIT_PENDING | HCF_RESTARTING))) { static const UINT16 arCtrls[] = { @@ -244,13 +254,11 @@ VOID WINAPI WorkerThreadCleanup( PCOMMONCONTEXT pcmnctx ) DWORD WINAPI WorkerThreadStartup( PCOMMONCONTEXT pcmnctx ) { - pcmnctx->hUnpauseEvent = CreateEvent(NULL, TRUE, TRUE, NULL); - pcmnctx->pfnWorkerMain(pcmnctx); pcmnctx->status = INACTIVE; - if (!(pcmnctx->dwFlags & HCF_EXIT_PENDING)) + if (! (pcmnctx->dwFlags & (HCF_EXIT_PENDING | HCF_RESTARTING))) PostMessage(pcmnctx->hWnd, HM_WORKERTHREAD_DONE, (WPARAM)pcmnctx, 0); return(0); @@ -323,7 +331,7 @@ __inline VOID UpdateProgressBar( HWND hWndPBFile, PCRITICAL_SECTION pCritSec, } } -VOID WINAPI WorkerThreadHashFile( PCOMMONCONTEXT pcmnctx, PCTSTR pszPath, PBOOL pbSuccess, +VOID WINAPI WorkerThreadHashFile( PCOMMONCONTEXT pcmnctx, PCTSTR pszPath, PWHCTXEX pwhctx, PWHRESULTEX pwhres, PBYTE pbuffer, PFILESIZE pFileSize, LPARAM lParam, PCRITICAL_SECTION pUpdateCritSec, volatile ULONGLONG* pcbCurrentMaxSize @@ -334,8 +342,6 @@ VOID WINAPI WorkerThreadHashFile( PCOMMONCONTEXT pcmnctx, PCTSTR pszPath, PBOOL { HANDLE hFile; - *pbSuccess = FALSE; - // If the worker thread is working so fast that the UI cannot catch up, // pause for a bit to let things settle down while (pcmnctx->cSentMsgs > pcmnctx->cHandledMsgs + 50) @@ -347,6 +353,17 @@ VOID WINAPI WorkerThreadHashFile( PCOMMONCONTEXT pcmnctx, PCTSTR pszPath, PBOOL return; } + // This can happen if a user changes the hash selection in HashProp (if no + // new hashes were selected; we still want to run the throttling code above) + if (pwhctx->dwFlags == 0) + { +#ifdef _TIMED + if (pdwElapsed) + *pdwElapsed = 0; +#endif + return; + } + // Indicate that we want lower-case results (TODO: make this an option) pwhctx->uCaseMode = WHFMT_LOWERCASE; @@ -413,8 +430,11 @@ VOID WINAPI WorkerThreadHashFile( PCOMMONCONTEXT pcmnctx, PCTSTR pszPath, PBOOL if (pdwElapsed) *pdwElapsed = GetTickCount() - dwStarted; #endif - if (cbFileRead == cbFileSize) - *pbSuccess = TRUE; + // If we encountered a file read error + if (cbFileRead != cbFileSize) + // Clear the valid-results bits for the hashes we just calculated + // (they are set by WHFinishEx, but they're apparently *not* valid) + pwhres->dwFlags &= ~pwhctx->dwFlags; if (bUpdateProgress) UpdateProgressBar(pcmnctx->hWndPBFile, pUpdateCritSec, &bCurrentlyUpdating, diff --git a/HashCheckCommon.h b/HashCheckCommon.h index 38e81c5..afad59c 100644 --- a/HashCheckCommon.h +++ b/HashCheckCommon.h @@ -35,12 +35,15 @@ extern "C" { #define THREAD_SUSPEND_ERROR ((DWORD)-1) #define TIMER_ID_PAUSE 1 -// Flags -#define HCF_EXIT_PENDING 0x0001 -#define HCF_MARQUEE 0x0002 -#define HVF_HAS_SET_TYPE 0x0004 -#define HVF_ITEM_HILITE 0x0008 -#define HPF_HAS_RESIZED 0x0004 +// Flags of DWORD width (which is an unsigned long) +#define HCF_EXIT_PENDING 0x0001UL +#define HCF_MARQUEE 0x0002UL +#define HCF_RESTARTING 0x0004UL +#define HVF_HAS_SET_TYPE 0x0008UL +#define HVF_ITEM_HILITE 0x0010UL +#define HPF_HAS_RESIZED 0x0008UL +#define HPF_HLIST_PREPPED 0x0010UL +#define HPF_INTERRUPTED 0x0020UL // Messages #define HM_WORKERTHREAD_DONE (WM_APP + 0) // wParam = ctx, lParam = 0 @@ -100,7 +103,7 @@ VOID WINAPI WorkerThreadCleanup( PCOMMONCONTEXT pcmnctx ); // Worker thread functions DWORD WINAPI WorkerThreadStartup( PCOMMONCONTEXT pcmnctx ); -VOID WINAPI WorkerThreadHashFile( PCOMMONCONTEXT pcmnctx, PCTSTR pszPath, PBOOL pbSuccess, +VOID WINAPI WorkerThreadHashFile( PCOMMONCONTEXT pcmnctx, PCTSTR pszPath, PWHCTXEX pwhctx, PWHRESULTEX pwhres, PBYTE pbuffer, PFILESIZE pFileSize, LPARAM lParam, PCRITICAL_SECTION pUpdateCritSec, volatile ULONGLONG* pcbCurrentMaxSize diff --git a/HashCheckTranslations.rc b/HashCheckTranslations.rc index 29a5f1b..ff1dd93 100644 --- a/HashCheckTranslations.rc +++ b/HashCheckTranslations.rc @@ -229,7 +229,7 @@ STRINGTABLE LANGUAGE LANG_CZECH, SUBLANG_DEFAULT IDS_OPT_TITLE "HashCheck nastavení" IDS_OPT_CM "Integrace" IDS_OPT_CM_ALWAYS "&Zobraz v kontextovém menu" - IDS_OPT_CM_EXTENDED "Zobraz v &rozšířeném kontextovém menu" + IDS_OPT_CM_EXTENDED "Zobraz v rozšířeném &kontextovém menu" IDS_OPT_CM_NEVER "&Nezobrazuj v kontextovém menu" IDS_OPT_ENCODING "Znaková sada souboru kontrolního součtu" IDS_OPT_ENCODING_UTF8 "Ulož v U&TF-8" diff --git a/HashProp.c b/HashProp.c index 5a18b7b..7165c0a 100644 --- a/HashProp.c +++ b/HashProp.c @@ -13,6 +13,7 @@ #include "HashCalc.h" #include "libs/WinHash.h" #include +#include // Control structures, from HashCalc.h #define HASHPROPSCRATCH HASHCALCSCRATCH @@ -29,6 +30,7 @@ // Worker thread VOID __fastcall HashPropWorkerMain( PHASHPROPCONTEXT phpctx ); +VOID WINAPI HashPropRestart( PHASHPROPCONTEXT phpctx ); // Dialog general VOID WINAPI HashPropDlgInit( PHASHPROPCONTEXT phpctx ); @@ -44,6 +46,8 @@ VOID WINAPI HashPropFinalStatus( PHASHPROPCONTEXT phpctx ); // Dialog commands VOID WINAPI HashPropFindText( PHASHPROPCONTEXT phpctx, BOOL bIncremental ); VOID WINAPI HashPropSaveResults( PHASHPROPCONTEXT phpctx ); +VOID WINAPI HashPropDoSaveResults( PHASHPROPCONTEXT phpctx ); +VOID WINAPI HashPropSaveResultsCleanup( PHASHPROPCONTEXT phpctx ); VOID WINAPI HashPropOptions( PHASHPROPCONTEXT phpctx ); @@ -94,16 +98,20 @@ VOID __fastcall HashPropWorkerMain( PHASHPROPCONTEXT phpctx ) // be asynchronous, or else there may be a deadlock. PHASHPROPITEM pItem; + WHCTXEX whctx; - // Prep: expand directories, establish prefix, etc. - PostMessage(phpctx->hWnd, HM_WORKERTHREAD_TOGGLEPREP, (WPARAM)phpctx, TRUE); - HashCalcPrepare(phpctx); + // Prep: if not already done, expand directories, establish prefix, etc. + if (! (phpctx->dwFlags & HPF_HLIST_PREPPED)) + { + PostMessage(phpctx->hWnd, HM_WORKERTHREAD_TOGGLEPREP, (WPARAM)phpctx, TRUE); + if (HashCalcPrepare(phpctx)) + phpctx->dwFlags |= HPF_HLIST_PREPPED; + } PostMessage(phpctx->hWnd, HM_WORKERTHREAD_TOGGLEPREP, (WPARAM)phpctx, FALSE); - // Indicate which hash types we want to calculate + // Which checksum types we want to calculate // (this is loaded earlier in HashPropDlgInit()) - WHCTXEX whctx; - whctx.flags = (UINT8)phpctx->opt.dwChecksums; + DWORD checksumFlags = (UINT8)phpctx->opt.dwChecksums; // Read buffer PBYTE pbBuffer = (PBYTE)VirtualAlloc(NULL, READ_BUFFER_SIZE, MEM_COMMIT, PAGE_READWRITE); @@ -117,11 +125,15 @@ VOID __fastcall HashPropWorkerMain( PHASHPROPCONTEXT phpctx ) while (pItem = SLGetDataAndStep(phpctx->hList)) { + // Some results might already be present if the user changes which checksum types + // to calculate and we're going through the list a second+ time for all/some items; + // only calculate the checksums we don't already have (usually all those requested) + whctx.dwFlags = checksumFlags & ~pItem->results.dwFlags; + // Get the hash WorkerThreadHashFile( (PCOMMONCONTEXT)phpctx, pItem->szPath, - &pItem->bValid, &whctx, &pItem->results, pbBuffer, @@ -213,12 +225,14 @@ INT_PTR CALLBACK HashPropDlgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lP (LONG_PTR)phpctx->wpResultsBox ); - // Kill the worker thread + // Kill the worker thread; HCF_EXIT_PENDING indicates + // we don't need to do any further GUI updates phpctx->dwFlags |= HCF_EXIT_PENDING; WorkerThreadStop((PCOMMONCONTEXT)phpctx); WorkerThreadCleanup((PCOMMONCONTEXT)phpctx); // Cleanup + HashPropSaveResultsCleanup(phpctx); if (phpctx->hFont) DeleteObject(phpctx->hFont); if (phpctx->hList) SLRelease(phpctx->hList); @@ -256,7 +270,9 @@ INT_PTR CALLBACK HashPropDlgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lP case IDC_STOP: { + phpctx->dwFlags |= HPF_INTERRUPTED; WorkerThreadStop((PCOMMONCONTEXT)phpctx); + HashPropSaveResultsCleanup(phpctx); return(TRUE); } @@ -307,6 +323,8 @@ INT_PTR CALLBACK HashPropDlgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lP { phpctx = (PHASHPROPCONTEXT)wParam; WorkerThreadCleanup((PCOMMONCONTEXT)phpctx); + if (phpctx->hFileOut != INVALID_HANDLE_VALUE) + HashPropDoSaveResults(phpctx); HashPropFinalStatus(phpctx); return(TRUE); } @@ -315,7 +333,15 @@ INT_PTR CALLBACK HashPropDlgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lP { phpctx = (PHASHPROPCONTEXT)wParam; ++phpctx->cHandledMsgs; - HashPropUpdateResults(phpctx, (PHASHPROPITEM)lParam); + // If we're restarting the worker thread, no need to update the GUI + if (phpctx->dwFlags & HCF_RESTARTING) + { + // Only restart the worker thread once we're caught up on handled messages + if (phpctx->cHandledMsgs >= phpctx->cSentMsgs) + HashPropRestart(phpctx); + } + else + HashPropUpdateResults(phpctx, (PHASHPROPITEM)lParam); return(TRUE); } @@ -443,6 +469,9 @@ VOID WINAPI HashPropDlgInit( PHASHPROPCONTEXT phpctx ) phpctx->cTotal = 0; phpctx->cSuccess = 0; phpctx->obScratch = 0; + phpctx->hThread = NULL; + phpctx->hUnpauseEvent = NULL; + phpctx->hFileOut = INVALID_HANDLE_VALUE; ZeroMemory(&phpctx->ofn, sizeof(phpctx->ofn)); } } @@ -560,97 +589,99 @@ VOID WINAPI HashPropUpdateResults( PHASHPROPCONTEXT phpctx, PHASHPROPITEM pItem HWND hWnd = phpctx->hWnd; HWND hWndResults = GetDlgItem(hWnd, IDC_RESULTS); - if (pItem->bValid) - { - /** - * It turns out that when hashing large numbers of small files, the - * worker thread can far outpace the UI thread, leaving the UI thread - * with a large backlog of updates; the solution is to keep track of - * the size of this backlog so that steps can be taken to alleviate it. - * - * 1) cSentMsgs and cHandledMsgs are used to keep track of the backlog; - * cSentMsgs will always be >= to cHandledMsgs (there are no race - * conditions to worry about), and their difference is the number - * of updates pending in the queue AFTER the update currently being - * handled is completed; therefore, zero means no pending backlog. - * 2) If there are more than 50 backlogged updates, the worker thread - * will throttle back enough to keep the backlog <= 50. - * 3) If there is any backlog at all, results will be coalesced into - * batches to reduce the number of costly EM_REPLACESEL calls. - * INVARIANT: the scratch buffer into which the results are coalesced - * has at least enough remaining space for adding the text results - * of a single file (it is cleared before returning if necessary) - **/ - - PTSTR pszScratchAppend; - size_t cchMaxBufferRequired = 0; // max tchar count for text results of one file - - // First, we can increment the success count + /** + * It turns out that when hashing large numbers of small files, the + * worker thread can far outpace the UI thread, leaving the UI thread + * with a large backlog of updates; the solution is to keep track of + * the size of this backlog so that steps can be taken to alleviate it. + * + * 1) cSentMsgs and cHandledMsgs are used to keep track of the backlog; + * cSentMsgs will always be >= to cHandledMsgs (there are no race + * conditions to worry about), and their difference is the number + * of updates pending in the queue AFTER the update currently being + * handled is completed; therefore, zero means no pending backlog. + * 2) If there are more than 50 backlogged updates, the worker thread + * will throttle back enough to keep the backlog <= 50. + * 3) If there is any backlog at all, results will be coalesced into + * batches to reduce the number of costly EM_REPLACESEL calls. + * INVARIANT: the scratch buffer into which the results are coalesced + * has at least enough remaining space for adding the text results + * of a single file (it is cleared before returning if necessary) + **/ + + PTSTR pszScratchAppend; + size_t cchMaxBufferRequired = 0; // max tchar count for text results of one file + + // Check to see of any desired hashes are not present in the results + if (phpctx->opt.dwChecksums & ~pItem->results.dwFlags) + // Replace the invalid hashes with X's + HashCalcClearInvalid(&pItem->results, TEXT('X')); + else + // Otherwise, we can increment the success count ++phpctx->cSuccess; - // Get the scratch buffer; we will be using the entire scratch struct - // as a single monolithic buffer - pszScratchAppend = BYTEADD(&phpctx->scratch, phpctx->obScratch); + // Get the scratch buffer; we will be using the entire scratch struct + // as a single monolithic buffer + pszScratchAppend = BYTEADD(&phpctx->scratch, phpctx->obScratch); - // Copy the file label - pszScratchAppend += LoadString(g_hModThisDll, IDS_HP_FILELABEL, - pszScratchAppend, MAX_STRINGRES); - cchMaxBufferRequired += MAX_STRINGRES; + // Copy the file label + pszScratchAppend += LoadString(g_hModThisDll, IDS_HP_FILELABEL, + pszScratchAppend, MAX_STRINGRES); + cchMaxBufferRequired += MAX_STRINGRES; - // Copy the path, appending CRLF - pszScratchAppend = SSChainNCpy2( - pszScratchAppend, - pItem->szPath + phpctx->cchPrefix, pItem->cchPath - phpctx->cchPrefix, - CRLF, CCH_CRLF + // Copy the path, appending CRLF + pszScratchAppend = SSChainNCpy2( + pszScratchAppend, + pItem->szPath + phpctx->cchPrefix, pItem->cchPath - phpctx->cchPrefix, + CRLF, CCH_CRLF + ); + cchMaxBufferRequired += MAX_PATH_BUFFER - phpctx->cchPrefix + CCH_CRLF; + + // Copy the results + PTSTR pszScratchBeforeResults = pszScratchAppend; +#define HASH_RESULT_APPEND_op(alg) \ + if (phpctx->opt.dwChecksums & WHEX_CHECK##alg) \ + pszScratchAppend = SSChainNCpy3( \ + pszScratchAppend, \ + HASH_RESULT_op(alg), sizeof(HASH_RESULT_op(alg))/sizeof(TCHAR) - 1, /* the "- 1" excludes the terminating NUL */ \ + pItem->results.szHex##alg, alg##_DIGEST_LENGTH * 2, \ + CRLF, CCH_CRLF \ ); - cchMaxBufferRequired += MAX_PATH_BUFFER - phpctx->cchPrefix + CCH_CRLF; - - // Copy the results - PTSTR pszScratchBeforeResults = pszScratchAppend; -#define HASH_RESULT_APPEND_op(alg) \ - if (phpctx->opt.dwChecksums & WHEX_CHECK##alg) \ - pszScratchAppend = SSChainNCpy3( \ - pszScratchAppend, \ - HASH_RESULT_op(alg), sizeof(HASH_RESULT_op(alg))/sizeof(TCHAR) - 1, /* the "- 1" excludes the terminating NUL */ \ - pItem->results.szHex##alg, alg##_DIGEST_LENGTH * 2, \ - CRLF, CCH_CRLF \ - ); - FOR_EACH_HASH(HASH_RESULT_APPEND_op) - cchMaxBufferRequired += pszScratchAppend - pszScratchBeforeResults; // always the same length + FOR_EACH_HASH(HASH_RESULT_APPEND_op) + cchMaxBufferRequired += pszScratchAppend - pszScratchBeforeResults; // always the same length #ifndef _TIMED - // Append CRLF and a terminating NUL - pszScratchAppend = SSChainNCpy( - pszScratchAppend, - CRLF _T("\0"), CCH_CRLF + 1 - ); - cchMaxBufferRequired += CCH_CRLF + 1; - pszScratchAppend--; // it now points to the terminating NUL + // Append CRLF and a terminating NUL + pszScratchAppend = SSChainNCpy( + pszScratchAppend, + CRLF _T("\0"), CCH_CRLF + 1 + ); + cchMaxBufferRequired += CCH_CRLF + 1; + pszScratchAppend--; // it now points to the terminating NUL #else - StringCchPrintfEx(pszScratchAppend, 30, &pszScratchAppend, NULL, 0, _T("Elapsed: %d ms") CRLF CRLF, pItem->dwElapsed); - cchMaxBufferRequired += 30; + StringCchPrintfEx(pszScratchAppend, 30, &pszScratchAppend, NULL, 0, _T("Elapsed: %d ms") CRLF CRLF, pItem->dwElapsed); + cchMaxBufferRequired += 30; #endif - // Update the new buffer offset for use by the next update - phpctx->obScratch = (UINT)BYTEDIFF(pszScratchAppend, &phpctx->scratch); + // Update the new buffer offset for use by the next update + phpctx->obScratch = (UINT)BYTEDIFF(pszScratchAppend, &phpctx->scratch); - // Determine if we can skip flushing the buffer - if ( phpctx->cSentMsgs > phpctx->cHandledMsgs && - phpctx->obScratch + (cchMaxBufferRequired * sizeof(TCHAR)) <= sizeof(HASHPROPSCRATCH) ) - { - return; - } + // Determine if we can skip flushing the buffer + if ( phpctx->cSentMsgs > phpctx->cHandledMsgs && + phpctx->obScratch + (cchMaxBufferRequired * sizeof(TCHAR)) <= sizeof(HASHPROPSCRATCH) ) + { + return; + } - // Flush the buffer to the text box - phpctx->obScratch = 0; - SendMessage(hWndResults, EM_SETSEL, -2, -2); - SendMessage(hWndResults, EM_REPLACESEL, FALSE, (LPARAM)&phpctx->scratch); + // Flush the buffer to the text box + phpctx->obScratch = 0; + SendMessage(hWndResults, EM_SETSEL, -2, -2); + SendMessage(hWndResults, EM_REPLACESEL, FALSE, (LPARAM)&phpctx->scratch); - // ClearType will sometimes leave artifacts, so redraw if the user will - // be looking at this text for a while - if (phpctx->cSentMsgs == phpctx->cHandledMsgs) - InvalidateRect(hWndResults, NULL, FALSE); - } + // ClearType will sometimes leave artifacts, so redraw if the user will + // be looking at this text for a while + if (phpctx->cSentMsgs == phpctx->cHandledMsgs) + InvalidateRect(hWndResults, NULL, FALSE); // Yes, this means that if we defer the text box update, we also end up // deferring the progress bar update too, which is what we want; progress @@ -688,8 +719,8 @@ VOID WINAPI HashPropFinalStatus( PHASHPROPCONTEXT phpctx ) EnableControl(phpctx->hWnd, IDC_SEARCHBOX, TRUE); EnableControl(phpctx->hWnd, IDC_FIND_NEXT, TRUE); - // Enable the save button only if there are results to save - if (phpctx->cSuccess) + // Enable the save button only if there exists completed results to save + if (!(phpctx->dwFlags & HPF_INTERRUPTED) && phpctx->cSuccess > 0) EnableControl(phpctx->hWnd, IDC_SAVE, TRUE); } @@ -769,29 +800,90 @@ VOID WINAPI HashPropFindText( PHASHPROPCONTEXT phpctx, BOOL bIncremental ) } } + VOID WINAPI HashPropSaveResults( PHASHPROPCONTEXT phpctx ) { - // HashCalcInitSave will set the file handle and output format - HashCalcInitSave(phpctx); - HashCalcSetSaveFormat(phpctx); + assert(! (phpctx->dwFlags & HPF_INTERRUPTED)); + assert(phpctx->cSuccess > 0); + + // HashCalcInitSave will set the file handle + HashCalcInitSave(phpctx); + + if (phpctx->hFileOut != INVALID_HANDLE_VALUE) + { + // If the last item in the list already has the desired hash computed + DWORD dwDesiredHash = 1 << (phpctx->ofn.nFilterIndex - 1); + if (((PHASHPROPITEM)SLGetDataLast(phpctx->hList))->results.dwFlags & dwDesiredHash) + { + HashPropDoSaveResults(phpctx); + } + else + { + // Ensure the desired hash is enabled, and begin generating the new hash(es) + assert(phpctx->status == CLEANUP_COMPLETED); + assert(phpctx->cHandledMsgs >= phpctx->cSentMsgs); + phpctx->opt.dwChecksums |= dwDesiredHash; + HashPropRestart(phpctx); + // HashPropDoSaveResults() is called when the worker thread posts a HM_WORKERTHREAD_DONE msg + } + } +} + +VOID WINAPI HashPropDoSaveResults(PHASHPROPCONTEXT phpctx) +{ + assert(phpctx->hFileOut != INVALID_HANDLE_VALUE); - if (phpctx->hFileOut != INVALID_HANDLE_VALUE) + if (! (phpctx->dwFlags & HPF_INTERRUPTED)) { + HashCalcSetSaveFormat(phpctx); + PHASHPROPITEM pItem; SLReset(phpctx->hList); while (pItem = SLGetDataAndStep(phpctx->hList)) HashCalcWriteResult(phpctx, pItem); + } + + CloseHandle(phpctx->hFileOut); + phpctx->hFileOut = INVALID_HANDLE_VALUE; +} + +VOID WINAPI HashPropSaveResultsCleanup( PHASHPROPCONTEXT phpctx ) +{ + if (phpctx->hFileOut != INVALID_HANDLE_VALUE) + { + // Don't keep partially generated checksum files + BOOL bDeleted = HashCalcDeleteFileByHandle(phpctx->hFileOut); CloseHandle(phpctx->hFileOut); + + // Should only happen on Windows XP + if (!bDeleted) + DeleteFile(phpctx->ofn.lpstrFile); + + phpctx->hFileOut = INVALID_HANDLE_VALUE; } } + VOID WINAPI HashPropOptions( PHASHPROPCONTEXT phpctx ) { OptionsDialog(phpctx->hWnd, &phpctx->opt); + // Update the results, but only if a file save isn't in progress + if (phpctx->opt.dwFlags & HCOF_CHECKSUMS && phpctx->hFileOut == INVALID_HANDLE_VALUE) + { + phpctx->dwFlags |= HCF_RESTARTING; + WorkerThreadStop((PCOMMONCONTEXT)phpctx); + WorkerThreadCleanup((PCOMMONCONTEXT)phpctx); + + if (phpctx->cHandledMsgs >= phpctx->cSentMsgs) + HashPropRestart(phpctx); + // Otherwise the call to HashPropRestart() is made following the + // last pending HM_WORKERTHREAD_UPDATE message in HashPropDlgProc() + } + if (phpctx->opt.dwFlags & HCOF_FONT) { HFONT hFont = CreateFontIndirect(&phpctx->opt.lfFont); @@ -804,3 +896,41 @@ VOID WINAPI HashPropOptions( PHASHPROPCONTEXT phpctx ) } } } + +VOID WINAPI HashPropRestart( PHASHPROPCONTEXT phpctx ) +{ + // Reset these flags back to the default + phpctx->dwFlags &= ~(HCF_RESTARTING | HPF_INTERRUPTED); + + // Just reset the list if it's fully loaded, else reload it from scratch + if (phpctx->dwFlags & HPF_HLIST_PREPPED) + SLReset(phpctx->hList); + else + { + SLRelease(phpctx->hList); + phpctx->hList = SLCreateEx(TRUE); + phpctx->cTotal = 0; + } + + // Reset the GUI to its initial state + EnableControl( phpctx->hWnd, IDC_SAVE, FALSE); + EnableControl( phpctx->hWnd, IDC_FIND_NEXT, FALSE); + EnableControl( phpctx->hWnd, IDC_SEARCHBOX, FALSE); + EnableControl( phpctx->hWnd, IDC_PROG_TOTAL, TRUE); + EnableControl( phpctx->hWnd, IDC_PROG_FILE, TRUE); + EnableControl( phpctx->hWnd, IDC_PAUSE, TRUE); + EnableControl( phpctx->hWnd, IDC_STOP, TRUE); + SetDlgItemText(phpctx->hWnd, IDC_RESULTS, TEXT("")); + SetControlText(phpctx->hWnd, IDC_PAUSE, IDS_HC_PAUSE); + SetProgressBarPause((PCOMMONCONTEXT)phpctx, PBST_NORMAL); + SendMessage(phpctx->hWndPBFile, PBM_SETPOS, 0, 0); + SendMessage(phpctx->hWndPBTotal, PBM_SETPOS, 0, 0); + + phpctx->cSuccess = 0; + phpctx->obScratch = 0; + + phpctx->hThread = CreateThreadCRT(NULL, phpctx); + + if (!phpctx->hThread) + WorkerThreadCleanup((PCOMMONCONTEXT)phpctx); +} diff --git a/HashSave.cpp b/HashSave.cpp index 38116d0..608fadb 100644 --- a/HashSave.cpp +++ b/HashSave.cpp @@ -103,9 +103,10 @@ DWORD WINAPI HashSaveThread( PHASHSAVECONTEXT phsctx ) if (phsctx->hFileOut != INVALID_HANDLE_VALUE) { + BOOL bDeletionFailed = TRUE; if (phsctx->hList = SLCreateEx(TRUE)) { - DialogBoxParam( + bDeletionFailed = ! DialogBoxParam( g_hModThisDll, MAKEINTRESOURCE(IDD_HASHSAVE), NULL, @@ -117,6 +118,10 @@ DWORD WINAPI HashSaveThread( PHASHSAVECONTEXT phsctx ) } CloseHandle(phsctx->hFileOut); + + // Should only happen on Windows XP + if (bDeletionFailed) + DeleteFile(phsctx->ofn.lpstrFile); } // This must be the last thing that we free, since this is what supports @@ -187,7 +192,7 @@ VOID __fastcall HashSaveWorkerMain( PHASHSAVECONTEXT phsctx ) WHCTXEX whctx; // Indicate which hash type we are after, see WHEX... values in WinHash.h - whctx.flags = 1 << (phsctx->ofn.nFilterIndex - 1); + whctx.dwFlags = 1 << (phsctx->ofn.nFilterIndex - 1); PBYTE pbBuffer; #ifdef USE_PPL @@ -206,7 +211,6 @@ VOID __fastcall HashSaveWorkerMain( PHASHSAVECONTEXT phsctx ) WorkerThreadHashFile( (PCOMMONCONTEXT)phsctx, pItem->szPath, - &pItem->bValid, &whctx, &pItem->results, bMultithreaded ? pbBuffer : pbTheBuffer, @@ -297,11 +301,17 @@ INT_PTR CALLBACK HashSaveDlgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lP phsctx->pfnWorkerMain = (PFNWORKERMAIN)HashSaveWorkerMain; phsctx->hThread = CreateThreadCRT(NULL, phsctx); - if (!phsctx->hThread || WaitForSingleObject(phsctx->hThread, 1000) != WAIT_TIMEOUT) + if (!phsctx->hThread) { WorkerThreadCleanup((PCOMMONCONTEXT)phsctx); - EndDialog(hWnd, 0); + BOOL bDeleted = HashCalcDeleteFileByHandle(phsctx->hFileOut); + EndDialog(hWnd, bDeleted); } + if (WaitForSingleObject(phsctx->hThread, 1000) != WAIT_TIMEOUT) + { + WorkerThreadCleanup((PCOMMONCONTEXT)phsctx); + EndDialog(hWnd, TRUE); + } return(TRUE); } @@ -337,7 +347,11 @@ INT_PTR CALLBACK HashSaveDlgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lP phsctx->dwFlags |= HCF_EXIT_PENDING; WorkerThreadStop((PCOMMONCONTEXT)phsctx); WorkerThreadCleanup((PCOMMONCONTEXT)phsctx); - EndDialog(hWnd, 0); + + // Don't keep partially generated checksum files + BOOL bDeleted = HashCalcDeleteFileByHandle(phsctx->hFileOut); + + EndDialog(hWnd, bDeleted); break; } } @@ -359,7 +373,7 @@ INT_PTR CALLBACK HashSaveDlgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lP { phsctx = (PHASHSAVECONTEXT)wParam; WorkerThreadCleanup((PCOMMONCONTEXT)phsctx); - EndDialog(hWnd, 0); + EndDialog(hWnd, TRUE); return(TRUE); } @@ -417,5 +431,7 @@ VOID WINAPI HashSaveDlgInit( PHASHSAVECONTEXT phsctx ) { phsctx->dwFlags = 0; phsctx->cTotal = 0; - } + phsctx->hThread = NULL; + phsctx->hUnpauseEvent = NULL; + } } diff --git a/HashVerify.cpp b/HashVerify.cpp index d7f83c6..818add8 100644 --- a/HashVerify.cpp +++ b/HashVerify.cpp @@ -107,7 +107,7 @@ typedef struct { DWORD dwStarted; // GetTickCount() start time HASHVERIFYPREV prev; // previous update data, used for update coalescing UINT uMaxBatch; // maximum number of updates to coalesce - UINT8 whctxFlags; // WinHash library flags (which checksums to use) + DWORD whctxFlags; // WinHash library dwFlags (which checksums to use) TCHAR szStatus[4][MAX_STRINGRES]; } HASHVERIFYCONTEXT, *PHASHVERIFYCONTEXT; @@ -536,8 +536,6 @@ VOID __fastcall HashVerifyWorkerMain( PHASHVERIFYCONTEXT phvctx ) // concurrency::parallel_for_each(phvctx->index, phvctx->index + phvctx->cTotal, ... auto per_file_worker = [&](PHASHVERIFYITEM pItem) { - BOOL bSuccess; - PBYTE pbBuffer; #ifdef USE_PPL if (bMultithreaded) @@ -571,13 +569,14 @@ VOID __fastcall HashVerifyWorkerMain( PHASHVERIFYCONTEXT phvctx ) // Part 2: Calculate the checksum WHCTXEX whctx; - whctx.flags = phvctx->whctxFlags; + WHRESULTEX whres; + whctx.dwFlags = phvctx->whctxFlags; + whres.dwFlags = 0; WorkerThreadHashFile( (PCOMMONCONTEXT)phvctx, (PTSTR)pbBuffer, - &bSuccess, &whctx, - NULL, + &whres, pbBuffer, &pItem->filesize, pItem->nListviewIndex, @@ -598,13 +597,13 @@ VOID __fastcall HashVerifyWorkerMain( PHASHVERIFYCONTEXT phvctx ) return; // Part 3: Do something with the results - if (bSuccess) + if (whres.dwFlags) { - switch (whctx.flags) + switch (whres.dwFlags) { #define HASH_VERIFY_COPY_RESULTS_op(alg) \ case WHEX_CHECK##alg: \ - SSStaticCpy(pItem->szActual, whctx.results.szHex##alg); \ + SSStaticCpy(pItem->szActual, whres.szHex##alg); \ break; FOR_EACH_HASH(HASH_VERIFY_COPY_RESULTS_op) } @@ -800,7 +799,7 @@ INT_PTR CALLBACK HashVerifyDlgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM case HM_WORKERTHREAD_SETSIZE: { phvctx = (PHASHVERIFYCONTEXT)wParam; - assert(lParam < phvctx->cTotal && lParam >= 0); + assert(lParam >= 0 && (UINT)lParam < phvctx->cTotal); if (phvctx->index[lParam]->bBeenSeen) ListView_RedrawItems(phvctx->hWndList, lParam, lParam); return(TRUE); @@ -939,6 +938,8 @@ VOID WINAPI HashVerifyDlgInit( PHASHVERIFYCONTEXT phvctx ) { phvctx->uMaxBatch = (phvctx->cTotal < (0x20 << 8)) ? 0x20 : phvctx->cTotal >> 8; phvctx->dwStarted = 0; + phvctx->hThread = NULL; + phvctx->hUnpauseEvent = NULL; } } diff --git a/installer/HashCheck.nsi b/installer/HashCheck.nsi index 0617de2..58c6633 100644 --- a/installer/HashCheck.nsi +++ b/installer/HashCheck.nsi @@ -53,15 +53,15 @@ FunctionEnd !insertmacro MUI_LANGUAGE "Ukrainian" !insertmacro MUI_LANGUAGE "Catalan" -VIProductVersion "2.3.5.22" +VIProductVersion "2.3.6.23" VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductName" "HashCheck Shell Extension" -VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductVersion" "2.3.5.22" +VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductVersion" "2.3.6.23" VIAddVersionKey /LANG=${LANG_ENGLISH} "Comments" "Installer distributed from https://github.com/gurnec/HashCheck/releases" VIAddVersionKey /LANG=${LANG_ENGLISH} "CompanyName" "" VIAddVersionKey /LANG=${LANG_ENGLISH} "LegalTrademarks" "" VIAddVersionKey /LANG=${LANG_ENGLISH} "LegalCopyright" "Copyright © Kai Liu, Christopher Gurnee, Tim Schlueter, et al. All rights reserved." VIAddVersionKey /LANG=${LANG_ENGLISH} "FileDescription" "Installer (x86/x64) from https://github.com/gurnec/HashCheck/releases" -VIAddVersionKey /LANG=${LANG_ENGLISH} "FileVersion" "2.3.5.22" +VIAddVersionKey /LANG=${LANG_ENGLISH} "FileVersion" "2.3.6.23" ; With solid compression, files that are required before the ; actual installation should be stored first in the data block, @@ -129,7 +129,9 @@ Section abort_on_error: Delete $0 + IfSilent +2 MessageBox MB_ICONSTOP|MB_OK "An unexpected error occurred during installation" + Quit SectionEnd diff --git a/libs/SimpleList.c b/libs/SimpleList.c index 53c4b65..b3f226b 100644 --- a/libs/SimpleList.c +++ b/libs/SimpleList.c @@ -1,7 +1,8 @@ /** * SimpleList Library - * Last modified: 2009/01/04 - * Copyright (C) Kai Liu. All rights reserved. + * Last modified: 2016/08/12 + * Original work copyright (C) Kai Liu. All rights reserved. + * Modified work copyright (C) 2016 Christopher Gurnee. All rights reserved. **/ #include "WinIntrinsics.h" @@ -26,7 +27,7 @@ typedef struct { PVOID pvContext; // optional user context data associated with this list PSLITEM pItemStart; // the first item; NULL if list is empty PSLITEM pItemCurrent; // the current item (used only by read/step) - PSLITEM pItemLast; // the last item; NULL if list is empty (used only by add) + PSLITEM pItemLast; // the last item; NULL if list is empty (used by add and GetDataLast) PSLITEM pItemNew; // the tail (used only by add) PSLBLOCK pBlockCurrent; // the current block to which new items will be added UINT cbRemaining; // bytes remaining in the current block @@ -259,6 +260,7 @@ BOOL SLFAPI SLStep( HSIMPLELIST hSimpleList ) * SLGetDataEx * SLGetDataAndStep * SLGetDataAndStepEx + * SLGetDataLast **/ PVOID SLFAPI SLGetData( HSIMPLELIST hSimpleList ) @@ -317,6 +319,19 @@ PVOID SLFAPI SLGetDataAndStepEx( HSIMPLELIST hSimpleList, PUINT pcbData ) return(NULL); } +PVOID SLFAPI SLGetDataLast(HSIMPLELIST hSimpleList) +{ + CONST PSLHEADER pHeader = (PSLHEADER)hSimpleList; + + if (pHeader && pHeader->pItemLast) + { + PVOID pvData = pHeader->pItemLast->data; + return(pvData); + } + + return(NULL); +} + /** * SLAddItem * SLAddString diff --git a/libs/SimpleList.h b/libs/SimpleList.h index 92759ee..b2c8334 100644 --- a/libs/SimpleList.h +++ b/libs/SimpleList.h @@ -1,7 +1,8 @@ /** * SimpleList Library - * Last modified: 2009/01/04 - * Copyright (C) Kai Liu. All rights reserved. + * Last modified: 2016/08/12 + * Original work copyright (C) Kai Liu. All rights reserved. + * Modified work copyright (C) 2016 Christopher Gurnee. All rights reserved. * * This library implements a highly efficient, fast, light-weight, and simple * list structure in C, with the goal of replacing the slower, heavier and @@ -106,6 +107,9 @@ PVOID SLFAPI SLGetDataEx( HSIMPLELIST hSimpleList, PUINT pcbData ); PVOID SLFAPI SLGetDataAndStep( HSIMPLELIST hSimpleList ); PVOID SLFAPI SLGetDataAndStepEx( HSIMPLELIST hSimpleList, PUINT pcbData ); +// Returns a pointer to the data of the last item or NULL +PVOID SLFAPI SLGetDataLast(HSIMPLELIST hSimpleList); + /** * SLAddItem: Adds a block of data to the end of the list; a pointer to a * newly-created data block of cbData bytes long is returned if memory diff --git a/libs/WinHash.cpp b/libs/WinHash.cpp index b19b3da..8534805 100644 --- a/libs/WinHash.cpp +++ b/libs/WinHash.cpp @@ -115,8 +115,8 @@ PTSTR WHAPI WHByteToHex( PBYTE pbSrc, PTSTR pszDest, UINT cchHex, UINT8 uCaseMod VOID WHAPI WHInitEx( PWHCTXEX pContext ) { -#define WIN_HASH_INIT_op(alg) \ - if (pContext->flags & WHEX_CHECK##alg) \ +#define WIN_HASH_INIT_op(alg) \ + if (pContext->dwFlags & WHEX_CHECK##alg) \ WHInit##alg(&pContext->ctx##alg); FOR_EACH_HASH(WIN_HASH_INIT_op) } @@ -127,8 +127,8 @@ VOID WHAPI WHUpdateEx( PWHCTXEX pContext, PCBYTE pbIn, UINT cbIn ) if (cbIn > 384u) { // determined experimentally--smaller than this and multithreading doesn't help, but ymmv int cTasks = 0; -#define WIN_HASH_UPDATE_COUNT_op(alg) \ - if (pContext->flags & WHEX_CHECK##alg) \ +#define WIN_HASH_UPDATE_COUNT_op(alg) \ + if (pContext->dwFlags & WHEX_CHECK##alg) \ cTasks++; FOR_EACH_HASH(WIN_HASH_UPDATE_COUNT_op) @@ -141,8 +141,8 @@ VOID WHAPI WHUpdateEx( PWHCTXEX pContext, PCBYTE pbIn, UINT cbIn ) concurrency::structured_task_group hashing_task_group; -#define WIN_HASH_UPDATE_RUN_TASK_op(alg) \ - if (pContext->flags & WHEX_CHECK##alg) \ +#define WIN_HASH_UPDATE_RUN_TASK_op(alg) \ + if (pContext->dwFlags & WHEX_CHECK##alg) \ hashing_task_group.run(task_WHUpdate##alg); FOR_EACH_HASH_R(WIN_HASH_UPDATE_RUN_TASK_op) @@ -152,22 +152,21 @@ VOID WHAPI WHUpdateEx( PWHCTXEX pContext, PCBYTE pbIn, UINT cbIn ) } #endif -#define WIN_HASH_UPDATE_RUN_op(alg) \ - if (pContext->flags & WHEX_CHECK##alg) \ +#define WIN_HASH_UPDATE_RUN_op(alg) \ + if (pContext->dwFlags & WHEX_CHECK##alg) \ WHUpdate##alg(&pContext->ctx##alg, pbIn, cbIn); FOR_EACH_HASH(WIN_HASH_UPDATE_RUN_op) } VOID WHAPI WHFinishEx( PWHCTXEX pContext, PWHRESULTEX pResults ) { - if (pResults == NULL) - pResults = &pContext->results; - -#define WIN_HASH_FINISH_op(alg) \ - if (pContext->flags & WHEX_CHECK##alg) \ - { \ - WHFinish##alg(&pContext->ctx##alg); \ +#define WIN_HASH_FINISH_op(alg) \ + if (pContext->dwFlags & WHEX_CHECK##alg) \ + { \ + WHFinish##alg(&pContext->ctx##alg); \ WHByteToHex(pContext->ctx##alg.result, pResults->szHex##alg, alg##_DIGEST_LENGTH * 2, pContext->uCaseMode); \ } FOR_EACH_HASH(WIN_HASH_FINISH_op) + + pResults->dwFlags |= pContext->dwFlags; } diff --git a/libs/WinHash.h b/libs/WinHash.h index 8f9992b..d1a6410 100644 --- a/libs/WinHash.h +++ b/libs/WinHash.h @@ -71,7 +71,6 @@ enum hash_algorithm { #define DEFAULT_HASH_ALGORITHMS (WHEX_CHECKCRC32 | WHEX_CHECKSHA1 | WHEX_CHECKSHA256 | WHEX_CHECKSHA512) // Bitwise representation of the hash algorithms -// (up to 8 hashes is OK before WHCTXEX.flags and HASHVERIFYCONTEXT.whctxFlags need a wider types) #define WHEX_CHECKCRC32 (1UL << (CRC32 - 1)) #define WHEX_CHECKMD5 (1UL << (MD5 - 1)) #define WHEX_CHECKSHA1 (1UL << (SHA1 - 1)) @@ -280,18 +279,18 @@ typedef struct { TCHAR szHexSHA1[SHA1_DIGEST_STRING_LENGTH]; TCHAR szHexSHA256[SHA256_DIGEST_STRING_LENGTH]; TCHAR szHexSHA512[SHA512_DIGEST_STRING_LENGTH]; + DWORD dwFlags; } WHRESULTEX, *PWHRESULTEX; // Align all the hash contexts to avoid false sharing (of L1/2 cache lines in multi-core systems) typedef struct { - UINT8 flags; - UINT8 uCaseMode; __declspec(align(64)) WHCTXCRC32 ctxCRC32; __declspec(align(64)) WHCTXMD5 ctxMD5; __declspec(align(64)) WHCTXSHA1 ctxSHA1; __declspec(align(64)) WHCTXSHA256 ctxSHA256; __declspec(align(64)) WHCTXSHA512 ctxSHA512; - WHRESULTEX results; + DWORD dwFlags; + UINT8 uCaseMode; } WHCTXEX, *PWHCTXEX; diff --git a/version.h b/version.h index 4cf26a7..1f61572 100644 --- a/version.h +++ b/version.h @@ -12,10 +12,10 @@ #define HASHCHECK_NAME_STR "HashCheck Shell Extension" // Full version: MUST be in the form of major,minor,revision,build -#define HASHCHECK_VERSION_FULL 2,3,5,22 +#define HASHCHECK_VERSION_FULL 2,3,6,23 // String version: May be any suitable string -#define HASHCHECK_VERSION_STR "2.3.5.22" +#define HASHCHECK_VERSION_STR "2.3.6.23" #ifdef _USRDLL // PE version: MUST be in the form of major.minor