iw4x-client/src/Components/Modules/Exception.cpp

217 lines
6.4 KiB
C++
Raw Normal View History

2022-02-27 07:53:44 -05:00
#include <STDInclude.hpp>
#include "Console.hpp"
2017-01-19 16:23:59 -05:00
#include <version.hpp>
2017-01-19 16:23:59 -05:00
namespace Components
{
Utils::Hook Exception::SetFilterHook;
int Exception::MiniDumpType;
2017-01-19 16:23:59 -05:00
2022-12-25 12:23:53 -05:00
__declspec(noreturn) void Exception::LongJmp_Internal_Stub(jmp_buf env, int status)
{
AssetHandler::ResetBypassState();
2022-12-25 12:23:53 -05:00
Game::longjmp_internal(env, status);
}
void Exception::SuspendProcess()
{
FreeConsole();
if (IsWindow(Console::GetWindow()) != FALSE)
{
CloseWindow(Console::GetWindow());
DestroyWindow(Console::GetWindow());
}
2017-02-10 13:45:31 -05:00
if (IsWindow(Window::GetWindow()) != FALSE)
{
2017-02-10 13:45:31 -05:00
CloseWindow(Window::GetWindow());
DestroyWindow(Window::GetWindow());
std::this_thread::sleep_for(2s);
// This makes sure we either destroy the windows or wait till they are destroyed
MSG msg;
Utils::Time::Interval interval;
while (IsWindow(Window::GetWindow()) != FALSE && !interval.elapsed(2s))
2017-02-10 13:45:31 -05:00
{
if (PeekMessageA(&msg, nullptr, NULL, NULL, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessageA(&msg);
}
std::this_thread::sleep_for(10ms);
2017-02-10 13:45:31 -05:00
}
}
// This only suspends the main game threads, which is enough for us
Game::Sys_SuspendOtherThreads();
}
void Exception::CopyMessageToClipboard(const std::string& error)
{
const auto hWndNewOwner = GetDesktopWindow();
const auto result = OpenClipboard(hWndNewOwner);
if (result == FALSE)
return;
EmptyClipboard();
auto* hMem = GlobalAlloc(GMEM_MOVEABLE, error.size() + 1);
2022-02-10 09:23:53 -05:00
if (hMem == nullptr)
{
2022-02-10 09:23:53 -05:00
CloseClipboard();
return;
}
2022-02-10 09:41:51 -05:00
2022-12-25 12:23:53 -05:00
auto* lock = GlobalLock(hMem);
2022-02-10 09:23:53 -05:00
if (lock != nullptr)
{
std::memcpy(lock, error.data(), error.size() + 1);
GlobalUnlock(hMem);
SetClipboardData(1, hMem);
}
CloseClipboard();
2022-02-10 09:23:53 -05:00
GlobalFree(hMem);
}
2017-01-19 16:23:59 -05:00
LONG WINAPI Exception::ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo)
{
// Pass on harmless errors
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_INTEGER_OVERFLOW ||
ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_FLOAT_OVERFLOW)
{
return EXCEPTION_CONTINUE_EXECUTION;
}
2017-05-02 07:37:41 -04:00
std::string errorStr;
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW)
{
2022-02-13 19:35:59 -05:00
errorStr = "Termination because of a stack overflow.\nCopy exception address to clipboard?";
}
else
{
2022-02-13 19:35:59 -05:00
errorStr = Utils::String::VA("Fatal error (0x%08X) at 0x%08X.\nCopy exception address to clipboard?", ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo->ExceptionRecord->ExceptionAddress);
}
2022-02-10 09:41:51 -05:00
// Message should be copied to the keyboard if no button is pressed
if (MessageBoxA(nullptr, errorStr.data(), nullptr, MB_YESNO | MB_ICONERROR) == IDYES)
{
2022-12-25 12:23:53 -05:00
CopyMessageToClipboard(Utils::String::VA("0x%08X", ExceptionInfo->ExceptionRecord->ExceptionAddress));
2022-02-10 09:41:51 -05:00
}
2022-02-10 09:41:51 -05:00
if (Flags::HasFlag("bigminidumps"))
{
2022-12-25 12:23:53 -05:00
SetMiniDumpType(true, false);
}
// Current executable name
char exeFileName[MAX_PATH];
GetModuleFileNameA(nullptr, exeFileName, MAX_PATH);
PathStripPathA(exeFileName);
PathRemoveExtensionA(exeFileName);
// Generate filename
2022-12-27 04:40:54 -05:00
char filenameFriendlyTime[MAX_PATH]{};
__time64_t time;
tm ltime;
_time64(&time);
_localtime64_s(&ltime, &time);
strftime(filenameFriendlyTime, sizeof(filenameFriendlyTime) - 1, "%Y%m%d%H%M%S", &ltime);
2022-12-25 12:23:53 -05:00
// Combine with queued MinidumpsFolder
char filename[MAX_PATH]{};
CreateDirectoryA("minidumps", nullptr);
PathCombineA(filename, "minidumps\\", Utils::String::VA("%s-" VERSION "-%s.dmp", exeFileName, filenameFriendlyTime));
2022-12-25 12:23:53 -05:00
constexpr auto fileShare = FILE_SHARE_READ | FILE_SHARE_WRITE;
HANDLE hFile = CreateFileA(filename, GENERIC_WRITE | GENERIC_READ, fileShare, nullptr, (fileShare & FILE_SHARE_WRITE) > 0 ? OPEN_ALWAYS : OPEN_EXISTING, NULL, nullptr);
MINIDUMP_EXCEPTION_INFORMATION ex = { GetCurrentThreadId(), ExceptionInfo, FALSE };
2022-12-25 12:23:53 -05:00
if (!MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, static_cast<MINIDUMP_TYPE>(MiniDumpType), &ex, nullptr, nullptr))
2017-01-19 16:23:59 -05:00
{
2022-12-25 12:23:53 -05:00
MessageBoxA(nullptr, Utils::String::Format("There was an error creating the minidump ({})! Hit OK to close the program.", Utils::GetLastWindowsError()), "ERROR", MB_OK | MB_ICONERROR);
2017-01-19 16:23:59 -05:00
OutputDebugStringA("Failed to create new minidump!");
Utils::OutputDebugLastError();
TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode);
2017-01-19 16:23:59 -05:00
}
{
TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode);
2017-01-19 16:23:59 -05:00
}
return EXCEPTION_CONTINUE_SEARCH;
}
void Exception::SetMiniDumpType(bool codeseg, bool dataseg)
{
2022-12-25 12:23:53 -05:00
MiniDumpType = MiniDumpIgnoreInaccessibleMemory;
MiniDumpType |= MiniDumpWithHandleData;
MiniDumpType |= MiniDumpScanMemory;
MiniDumpType |= MiniDumpWithProcessThreadData;
MiniDumpType |= MiniDumpWithFullMemoryInfo;
MiniDumpType |= MiniDumpWithThreadInfo;
if (codeseg)
{
2022-12-25 12:23:53 -05:00
MiniDumpType |= MiniDumpWithCodeSegs;
}
2022-12-25 12:23:53 -05:00
if (dataseg)
{
2022-12-25 12:23:53 -05:00
MiniDumpType |= MiniDumpWithDataSegs;
}
}
2022-12-27 04:40:54 -05:00
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI Exception::SetUnhandledExceptionFilter_Stub(LPTOP_LEVEL_EXCEPTION_FILTER)
{
SetFilterHook.uninstall();
LPTOP_LEVEL_EXCEPTION_FILTER result = ::SetUnhandledExceptionFilter(&ExceptionFilter);
SetFilterHook.install();
return result;
}
2017-01-19 16:23:59 -05:00
Exception::Exception()
{
2022-12-25 12:23:53 -05:00
SetMiniDumpType(Flags::HasFlag("bigminidumps"), Flags::HasFlag("reallybigminidumps"));
2017-01-19 16:23:59 -05:00
2022-12-27 04:40:54 -05:00
SetFilterHook.initialize(::SetUnhandledExceptionFilter, SetUnhandledExceptionFilter_Stub, HOOK_JUMP);
SetFilterHook.install();
::SetUnhandledExceptionFilter(&ExceptionFilter);
2017-01-19 16:23:59 -05:00
2022-12-25 12:23:53 -05:00
Utils::Hook(0x4B241F, LongJmp_Internal_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x61DB44, LongJmp_Internal_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x61F17D, LongJmp_Internal_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x61F248, LongJmp_Internal_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x61F5E7, LongJmp_Internal_Stub, HOOK_CALL).install()->quick();
2017-01-19 16:23:59 -05:00
2022-12-25 12:23:53 -05:00
#ifdef MAP_TEST
2017-01-19 16:23:59 -05:00
Command::Add("mapTest", [](Command::Params* params)
2022-02-10 10:00:45 -05:00
{
Game::UI_UpdateArenas();
2017-01-19 16:23:59 -05:00
2022-02-10 10:00:45 -05:00
std::string command;
2022-03-17 14:50:20 -04:00
for (auto i = 0; i < (params->size() >= 2 ? atoi(params->get(1)) : *Game::arenaCount); ++i)
2022-02-10 10:00:45 -05:00
{
2022-06-12 17:07:53 -04:00
const auto* mapName = ArenaLength::NewArenas[i % *Game::arenaCount].mapName;
2017-01-19 16:23:59 -05:00
2022-02-10 14:45:00 -05:00
if (!(i % 2)) command.append("wait 250;disconnect;wait 750;"); // Test a disconnect
2022-02-10 10:00:45 -05:00
else command.append("wait 500;"); // Test direct map switch
2022-06-12 17:07:53 -04:00
command.append(Utils::String::VA("map %s;", mapName));
2022-02-10 10:00:45 -05:00
}
2022-02-10 09:41:51 -05:00
2022-02-10 10:00:45 -05:00
Command::Execute(command, false);
});
2022-06-12 17:07:53 -04:00
#endif
2017-01-19 16:23:59 -05:00
}
Exception::~Exception()
{
2022-12-25 12:23:53 -05:00
SetFilterHook.uninstall();
2017-01-19 16:23:59 -05:00
}
}