Skip to content

Commit

Permalink
All: Implemented a mechanism to stop repeated crashes
Browse files Browse the repository at this point in the history
When Windows is updated, it may fundamentally change the layout and
functionality of `explorer`'s internal data structure. Since
ExplorerPatcher intensively patches those in order to deliver its
functionality, it may lead to `explorer` crashing when it performs
these modifications while being unaware (outdated) of the latest OS
changes. The worst scenario is when the crashes happen when `explorer`
starts up (for example, when the user logs in), and could cause the
user to experience an endless loop of `explorer` crashes, and leave
him/her unable to practically use the computer.

In order to mitigate this scenario, when `explorer` starts up as the
shell, ExplorerPatcher increments a counter stored in the registry.
After a predefined timeout (by default, 10 seconds), ExplorerPatcher
will reset the counter to 0. If `explorer` crashes during this period,
the counter will not be reset. When `explorer` restarts, the cycle
repeats. If the counter reaches a predefined value (by default, 3),
instead of starting up again normally (and very probably crashing
again), ExplorerPatcher will display a message window informing the
user about what is happening, offering a few suggestions on how to
proceed next and disable its entire functionality until the next
File Explorer restart, in order to give the user a chance to perform
maintenance on the machine.
  • Loading branch information
valinet committed Mar 18, 2023
1 parent 0ad140c commit d7e5b7d
Showing 1 changed file with 136 additions and 4 deletions.
140 changes: 136 additions & 4 deletions ExplorerPatcher/dllmain.c
Expand Up @@ -228,7 +228,7 @@ HRESULT WINAPI _DllGetClassObject(

#pragma region "Updates"
#ifdef _WIN64
DWORD CheckForUpdatesThread(LPVOID unused)
DWORD CheckForUpdatesThread(LPVOID timeout)
{
HRESULT hr = S_OK;
HSTRING_HEADER header_AppIdHString;
Expand All @@ -252,7 +252,7 @@ DWORD CheckForUpdatesThread(LPVOID unused)
);
if (hShell_TrayWnd)
{
Sleep(5000);
Sleep(timeout);
break;
}
Sleep(100);
Expand Down Expand Up @@ -10783,7 +10783,7 @@ DWORD Inject(BOOL bIsExplorer)



CreateThread(NULL, 0, CheckForUpdatesThread, 0, 0, NULL);
CreateThread(NULL, 0, CheckForUpdatesThread, 5000, 0, NULL);



Expand Down Expand Up @@ -12249,6 +12249,90 @@ void InjectShellExperienceHostFor22H2OrHigher() {
#endif
}

HRESULT InformUserAboutCrashCallback(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData) {
if (msg == TDN_HYPERLINK_CLICKED) {
if (wcschr(lParam, L'\'')) {
for (int i = 0; i < wcslen(lParam); ++i) if (*(((wchar_t*)lParam) + i) == L'\'') *(((wchar_t*)lParam) + i) = L'"';
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
if (CreateProcessW(NULL, lParam, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
for (int i = 0; i < wcslen(lParam); ++i) if (*(((wchar_t*)lParam) + i) == L'"') *(((wchar_t*)lParam) + i) = L'\'';
}
else if (!wcsncmp(lParam, L"eplink://update", 15)) {
if (!wcsncmp(lParam, L"eplink://update/stable", 22)) {
DWORD no = 0;
RegSetKeyValueW(HKEY_CURRENT_USER, _T(REGPATH), L"UpdatePreferStaging", REG_DWORD, &no, sizeof(DWORD));
} else if (!wcsncmp(lParam, L"eplink://update/staging", 23)) {
DWORD yes = 1;
RegSetKeyValueW(HKEY_CURRENT_USER, _T(REGPATH), L"UpdatePreferStaging", REG_DWORD, &yes, sizeof(DWORD));
}
SetLastError(0);
HANDLE sigInstallUpdates = CreateEventW(NULL, FALSE, FALSE, L"EP_Ev_InstallUpdates_" _T(EP_CLSID));
if (sigInstallUpdates && GetLastError() == ERROR_ALREADY_EXISTS) {
SetEvent(sigInstallUpdates);
}
else {
CreateThread(NULL, 0, CheckForUpdatesThread, 0, 0, NULL);
Sleep(100);
SetLastError(0);
sigInstallUpdates = CreateEventW(NULL, FALSE, FALSE, L"EP_Ev_InstallUpdates_" _T(EP_CLSID));
if (sigInstallUpdates && GetLastError() == ERROR_ALREADY_EXISTS) {
SetEvent(sigInstallUpdates);
}
}
}
else ShellExecuteW(NULL, L"open", lParam, L"", NULL, SW_SHOWNORMAL);
return S_FALSE;
}
return S_OK;
}

DWORD InformUserAboutCrash(LPVOID msg) {
TASKDIALOG_BUTTON buttons[1];
buttons[0].nButtonID = IDNO;
buttons[0].pszButtonText = L"Dismiss";

TASKDIALOGCONFIG td;
ZeroMemory(&td, sizeof(TASKDIALOGCONFIG));
td.cbSize = sizeof(TASKDIALOGCONFIG);
td.hInstance = hModule;
td.hwndParent = NULL;
td.dwFlags = TDF_SIZE_TO_CONTENT | TDF_ENABLE_HYPERLINKS;
td.pszWindowTitle = L"ExplorerPatcher";
td.pszMainInstruction = L"Unfortunately, File Explorer is crashing :(";
td.pszContent = msg;
td.cButtons = sizeof buttons / sizeof buttons[0];
td.pButtons = buttons;
td.cRadioButtons = 0;
td.pRadioButtons = NULL;
td.cxWidth = 0;
td.pfCallback = InformUserAboutCrashCallback;

HMODULE hComCtl32 = NULL;
HRESULT(*pfTaskDialogIndirect)(const TASKDIALOGCONFIG*, int*, int*, BOOL*) = NULL;
int res = td.nDefaultButton;
if (!(hComCtl32 = GetModuleHandleA("Comctl32.dll")) ||
!(pfTaskDialogIndirect = GetProcAddress(hComCtl32, "TaskDialogIndirect")) ||
FAILED(pfTaskDialogIndirect(&td, &res, NULL, NULL))) {
wcscat_s(msg, 10000, L" Would you like to open the ExplorerPatcher status web page on GitHub in your default browser?");
res = MessageBoxW(NULL, msg, L"ExplorerPatcher", MB_ICONASTERISK | MB_YESNO);
}
if (res == IDYES) ShellExecuteW(NULL, L"open", L"https://github.com/valinet/ExplorerPatcher/discussions/1102", L"", NULL, SW_SHOWNORMAL);
free(msg);
}

DWORD WINAPI ClearCrashCounter(INT64 timeout) {
Sleep(timeout);
DWORD zero = 0;
RegSetKeyValueW(HKEY_CURRENT_USER, _T(REGPATH), L"CrashCounter", REG_DWORD, &zero, sizeof(DWORD));
}

#define DLL_INJECTION_METHOD_DXGI 0
#define DLL_INJECTION_METHOD_COM 1
#define DLL_INJECTION_METHOD_START_INJECTION 2
Expand Down Expand Up @@ -12336,7 +12420,55 @@ HRESULT EntryPoint(DWORD dwMethod)
bIsExplorerProcess = bIsThisExplorer;
if (bIsThisExplorer)
{
Inject(!IsDesktopWindowAlreadyPresent());
BOOL desktopExists = IsDesktopWindowAlreadyPresent();
if (!desktopExists) {
DWORD crashCounterDisabled = 0, crashCounter = 0, crashThresholdTime = 10000, crashCounterThreshold = 3, dwTCSize = sizeof(DWORD);
RegGetValueW(HKEY_CURRENT_USER, _T(REGPATH), L"CrashCounterDisabled", RRF_RT_DWORD, NULL, &crashCounterDisabled, &dwTCSize); dwTCSize = sizeof(DWORD);
if (crashCounterDisabled != 0 && crashCounterDisabled != 1) crashCounterDisabled = 0;
RegGetValueW(HKEY_CURRENT_USER, _T(REGPATH), L"CrashCounter", RRF_RT_DWORD, NULL, &crashCounter, &dwTCSize); dwTCSize = sizeof(DWORD);
if (crashCounter < 0) crashCounter = 0;
RegGetValueW(HKEY_CURRENT_USER, _T(REGPATH), L"CrashThresholdTime", RRF_RT_DWORD, NULL, &crashThresholdTime, &dwTCSize); dwTCSize = sizeof(DWORD);
if (crashThresholdTime < 100 || crashThresholdTime > 60000) crashThresholdTime = 10000;
RegGetValueW(HKEY_CURRENT_USER, _T(REGPATH), L"CrashCounterThreshold", RRF_RT_DWORD, NULL, &crashCounterThreshold, &dwTCSize); dwTCSize = sizeof(DWORD);
if (crashCounterThreshold <= 1 || crashCounterThreshold >= 10) crashCounterThreshold = 3;
if (!crashCounterDisabled) {
if (crashCounter >= crashCounterThreshold) {
crashCounter = 0;
RegSetKeyValueW(HKEY_CURRENT_USER, _T(REGPATH), L"CrashCounter", REG_DWORD, &crashCounter, sizeof(DWORD));
wchar_t times[100];
ZeroMemory(times, sizeof(wchar_t) * 100);
swprintf_s(times, 100, crashCounterThreshold == 1 ? L"once" : L"%d times", crashCounterThreshold);
wchar_t uninstallLink[MAX_PATH];
ZeroMemory(uninstallLink, sizeof(wchar_t) * MAX_PATH);
uninstallLink[0] = L'\'';
SHGetFolderPathW(NULL, SPECIAL_FOLDER, NULL, SHGFP_TYPE_CURRENT, uninstallLink + 1);
wcscat_s(uninstallLink, MAX_PATH, _T(APP_RELATIVE_PATH) L"\\" _T(SETUP_UTILITY_NAME) L"' /uninstall");
wchar_t* msg = calloc(sizeof(wchar_t), 10000);
swprintf_s(msg, 10000,
L"It seems that File Explorer closed unexpectedly %s in less than %d seconds each time when starting up. "
L"This might indicate a problem caused by ExplorerPatcher, which might be unaware of recent changes in Windows, for example "
L"when running on a new OS build.\n"
L"Here are a few recommendations:\n"
L"\u2022 If an updated version is available, you can <A HREF=\"eplink://update\">update ExplorerPatcher and restart File Explorer</A>.\n"
L"\u2022 On GitHub, you can <A HREF=\"https://github.com/valinet/ExplorerPatcher/releases\">view releases</A>, <A HREF=\"https://github.com/valinet/ExplorerPatcher/discussions/1102\">check the current status</A>, <A HREF=\"https://github.com/valinet/ExplorerPatcher/discussions\">discuss</A> or <A HREF=\"https://github.com/valinet/ExplorerPatcher/issues\">review the latest issues</A>.\n"
L"\u2022 If you suspect this is not caused by ExplorerPatcher, please uninstall any recently installed shell extensions or similar utilities.\n"
L"\u2022 If no fix is available for the time being, you can <A HREF=\"%s\">uninstall ExplorerPatcher</A>, and then later reinstall it when a fix is published on "
L"GitHub. Rest assured, even if you uninstall, your program configuration will be preserved.\n"
L"\n"
L"I am sorry for the inconvenience this might cause; I am doing my best to try to keep this program updated and working.\n\n"
L"ExplorerPatcher is disabled until the next File Explorer restart, in order to allow you to perform maintenance tasks and take the necessary actions.",
times, crashThresholdTime / 1000, uninstallLink);
SHCreateThread(InformUserAboutCrash, msg, 0, NULL);
IncrementDLLReferenceCount(hModule);
bInstanced = TRUE;
return E_NOINTERFACE;
}
crashCounter++;
RegSetKeyValueW(HKEY_CURRENT_USER, _T(REGPATH), L"CrashCounter", REG_DWORD, &crashCounter, sizeof(DWORD));
SHCreateThread(ClearCrashCounter, crashThresholdTime, 0, NULL);
}
}
Inject(!desktopExists);
IncrementDLLReferenceCount(hModule);
bInstanced = TRUE;
}
Expand Down

3 comments on commit d7e5b7d

@FatheredPuma81
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@petersvp
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. It's sad that so many people ended up reinstalling Windows because of... The root cause... Microsoft's weird design decisions.

Now I can safely upgrade again to Windows 11

@ThinkAndFly
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh nice. I got around and could uninstall it using cmd from the TaskManager when it happened to me.

Please sign in to comment.