diff --git a/libraries/ZWidget/CMakeLists.txt b/libraries/ZWidget/CMakeLists.txt index 4ce83efe3..9967b4ec6 100644 --- a/libraries/ZWidget/CMakeLists.txt +++ b/libraries/ZWidget/CMakeLists.txt @@ -33,6 +33,9 @@ set(ZWIDGET_SOURCES src/widgets/listview/listview.cpp src/widgets/tabwidget/tabwidget.cpp src/window/window.cpp + src/systemdialogs/folder_browse_dialog.cpp + src/systemdialogs/open_file_dialog.cpp + src/systemdialogs/save_file_dialog.cpp ) set(ZWIDGET_INCLUDES @@ -63,6 +66,9 @@ set(ZWIDGET_INCLUDES include/zwidget/widgets/listview/listview.h include/zwidget/widgets/tabwidget/tabwidget.h include/zwidget/window/window.h + include/zwidget/systemdialogs/folder_browse_dialog.h + include/zwidget/systemdialogs/open_file_dialog.h + include/zwidget/systemdialogs/save_file_dialog.h ) set(ZWIDGET_WIN32_SOURCES @@ -97,6 +103,7 @@ source_group("src\\widgets\\checkboxlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_S source_group("src\\widgets\\listview" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/listview/.+") source_group("src\\widgets\\tabwidget" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/tabwidget/.+") source_group("src\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/.+") +source_group("src\\systemdialogs" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/systemdialogs/.+") source_group("include" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/.+") source_group("include\\core" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/core/.+") source_group("include\\widgets" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/.+") @@ -116,6 +123,7 @@ source_group("include\\widgets\\tabwidget" REGULAR_EXPRESSION "${CMAKE_CURRENT_S source_group("include\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/.+") source_group("include\\window\\win32" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/win32/.+") source_group("include\\window\\sdl2" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/sdl2/.+") +source_group("include\\systemdialogs" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/systemdialogs/.+") include_directories(include include/zwidget src) diff --git a/libraries/ZWidget/include/zwidget/core/widget.h b/libraries/ZWidget/include/zwidget/core/widget.h index 02e87729c..43f9094b4 100644 --- a/libraries/ZWidget/include/zwidget/core/widget.h +++ b/libraries/ZWidget/include/zwidget/core/widget.h @@ -129,6 +129,8 @@ public: static Size GetScreenSize(); + void* GetNativeHandle(); + protected: virtual void OnPaintFrame(Canvas* canvas); virtual void OnPaint(Canvas* canvas) { } diff --git a/libraries/ZWidget/include/zwidget/systemdialogs/folder_browse_dialog.h b/libraries/ZWidget/include/zwidget/systemdialogs/folder_browse_dialog.h new file mode 100644 index 000000000..991869a5a --- /dev/null +++ b/libraries/ZWidget/include/zwidget/systemdialogs/folder_browse_dialog.h @@ -0,0 +1,30 @@ + +#pragma once + +#include +#include + +class Widget; + +/// \brief Displays the system folder browsing dialog +class BrowseFolderDialog +{ +public: + /// \brief Constructs an browse folder dialog. + static std::unique_ptr Create(Widget*owner); + + virtual ~BrowseFolderDialog() = default; + + /// \brief Get the full path of the directory selected. + virtual std::string SelectedPath() const = 0; + + /// \brief Sets the initial directory that is displayed. + virtual void SetInitialDirectory(const std::string& path) = 0; + + /// \brief Sets the text that appears in the title bar. + virtual void SetTitle(const std::string& title) = 0; + + /// \brief Shows the file dialog. + /// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise. + virtual bool Show() = 0; +}; diff --git a/libraries/ZWidget/include/zwidget/systemdialogs/open_file_dialog.h b/libraries/ZWidget/include/zwidget/systemdialogs/open_file_dialog.h new file mode 100644 index 000000000..29ede966e --- /dev/null +++ b/libraries/ZWidget/include/zwidget/systemdialogs/open_file_dialog.h @@ -0,0 +1,55 @@ + +#pragma once + +#include +#include +#include + +class Widget; + +/// \brief Displays the system open file dialog. +class OpenFileDialog +{ +public: + /// \brief Constructs an open file dialog. + static std::unique_ptr Create(Widget* owner); + + virtual ~OpenFileDialog() = default; + + /// \brief Get the full path of the file selected. + /// + /// If multiple files are selected, this returns the first file. + virtual std::string Filename() const = 0; + + /// \brief Gets an array that contains one file name for each selected file. + virtual std::vector Filenames() const = 0; + + /// \brief Sets if multiple files can be selected or not. + /// \param multiselect = When true, multiple items can be selected. + virtual void SetMultiSelect(bool multiselect) = 0; + + /// \brief Sets a string containing the full path of the file selected. + virtual void SetFilename(const std::string &filename) = 0; + + /// \brief Sets the default extension to use. + virtual void SetDefaultExtension(const std::string& extension) = 0; + + /// \brief Add a filter that determines what types of files are displayed. + virtual void AddFilter(const std::string &filter_description, const std::string &filter_extension) = 0; + + /// \brief Clears all filters. + virtual void ClearFilters() = 0; + + /// \brief Sets a default filter, on a 0-based index. + virtual void SetFilterIndex(int filter_index) = 0; + + /// \brief Sets the initial directory that is displayed. + virtual void SetInitialDirectory(const std::string &path) = 0; + + /// \brief Sets the text that appears in the title bar. + virtual void SetTitle(const std::string &title) = 0; + + /// \brief Shows the file dialog. + /// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise. + virtual bool Show() = 0; +}; diff --git a/libraries/ZWidget/include/zwidget/systemdialogs/save_file_dialog.h b/libraries/ZWidget/include/zwidget/systemdialogs/save_file_dialog.h new file mode 100644 index 000000000..ce10a127f --- /dev/null +++ b/libraries/ZWidget/include/zwidget/systemdialogs/save_file_dialog.h @@ -0,0 +1,46 @@ + +#pragma once + +#include +#include +#include + +class Widget; + +/// \brief Displays the system save file dialog. +class SaveFileDialog +{ +public: + /// \brief Constructs a save file dialog. + static std::unique_ptr Create(Widget *owner); + + virtual ~SaveFileDialog() = default; + + /// \brief Get the full path of the file selected. + virtual std::string Filename() const = 0; + + /// \brief Sets a string containing the full path of the file selected. + virtual void SetFilename(const std::string &filename) = 0; + + /// \brief Sets the default extension to use. + virtual void SetDefaultExtension(const std::string& extension) = 0; + + /// \brief Add a filter that determines what types of files are displayed. + virtual void AddFilter(const std::string &filter_description, const std::string &filter_extension) = 0; + + /// \brief Clears all filters. + virtual void ClearFilters() = 0; + + /// \brief Sets a default filter, on a 0-based index. + virtual void SetFilterIndex(int filter_index) = 0; + + /// \brief Sets the initial directory that is displayed. + virtual void SetInitialDirectory(const std::string &path) = 0; + + /// \brief Sets the text that appears in the title bar. + virtual void SetTitle(const std::string &title) = 0; + + /// \brief Shows the file dialog. + /// \return true if the user clicks the OK button of the dialog that is displayed, false otherwise. + virtual bool Show() = 0; +}; diff --git a/libraries/ZWidget/include/zwidget/window/window.h b/libraries/ZWidget/include/zwidget/window/window.h index 2c9a413b9..8515f5004 100644 --- a/libraries/ZWidget/include/zwidget/window/window.h +++ b/libraries/ZWidget/include/zwidget/window/window.h @@ -165,4 +165,6 @@ public: virtual std::string GetClipboardText() = 0; virtual void SetClipboardText(const std::string& text) = 0; + + virtual void* GetNativeHandle() = 0; }; diff --git a/libraries/ZWidget/src/core/widget.cpp b/libraries/ZWidget/src/core/widget.cpp index e27fff171..299857cfc 100644 --- a/libraries/ZWidget/src/core/widget.cpp +++ b/libraries/ZWidget/src/core/widget.cpp @@ -733,6 +733,12 @@ Size Widget::GetScreenSize() return DisplayWindow::GetScreenSize(); } +void* Widget::GetNativeHandle() +{ + Widget* w = Window(); + return w ? w->DispWindow->GetNativeHandle() : nullptr; +} + void Widget::SetStyleClass(const std::string& themeClass) { if (StyleClass != themeClass) diff --git a/libraries/ZWidget/src/systemdialogs/folder_browse_dialog.cpp b/libraries/ZWidget/src/systemdialogs/folder_browse_dialog.cpp new file mode 100644 index 000000000..754e397e2 --- /dev/null +++ b/libraries/ZWidget/src/systemdialogs/folder_browse_dialog.cpp @@ -0,0 +1,188 @@ + +#include "systemdialogs/folder_browse_dialog.h" +#include "core/widget.h" +#include "window/window.h" +#include + +#if defined(WIN32) +#include + +namespace +{ + static std::string from_utf16(const std::wstring& str) + { + if (str.empty()) return {}; + int needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0, nullptr, nullptr); + if (needed == 0) + throw std::runtime_error("WideCharToMultiByte failed"); + std::string result; + result.resize(needed); + needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size(), nullptr, nullptr); + if (needed == 0) + throw std::runtime_error("WideCharToMultiByte failed"); + return result; + } + + static std::wstring to_utf16(const std::string& str) + { + if (str.empty()) return {}; + int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0); + if (needed == 0) + throw std::runtime_error("MultiByteToWideChar failed"); + std::wstring result; + result.resize(needed); + needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size()); + if (needed == 0) + throw std::runtime_error("MultiByteToWideChar failed"); + return result; + } + + template + class ComPtr + { + public: + ComPtr() { Ptr = nullptr; } + ComPtr(const ComPtr& other) { Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } + ComPtr(ComPtr&& move) { Ptr = move.Ptr; move.Ptr = nullptr; } + ~ComPtr() { reset(); } + ComPtr& operator=(const ComPtr& other) { if (this != &other) { if (Ptr) Ptr->Release(); Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } return *this; } + void reset() { if (Ptr) Ptr->Release(); Ptr = nullptr; } + T* get() { return Ptr; } + static IID GetIID() { return __uuidof(T); } + void** InitPtr() { return (void**)TypedInitPtr(); } + T** TypedInitPtr() { reset(); return &Ptr; } + operator T* () const { return Ptr; } + T* operator ->() const { return Ptr; } + T* Ptr; + }; +} + +class BrowseFolderDialogImpl : public BrowseFolderDialog +{ +public: + BrowseFolderDialogImpl(Widget *owner) : owner(owner) + { + } + + Widget *owner = nullptr; + + std::string selected_path; + std::string initial_directory; + std::string title; + + bool Show() override + { + std::wstring title16 = to_utf16(title); + std::wstring initial_directory16 = to_utf16(initial_directory); + + HRESULT result; + ComPtr open_dialog; + + result = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_ALL, open_dialog.GetIID(), open_dialog.InitPtr()); + throw_if_failed(result, "CoCreateInstance(FileOpenDialog) failed"); + + result = open_dialog->SetTitle(title16.c_str()); + throw_if_failed(result, "IFileOpenDialog.SetTitle failed"); + + result = open_dialog->SetOptions(FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST); + throw_if_failed(result, "IFileOpenDialog.SetOptions((FOS_PICKFOLDERS) failed"); + + if (initial_directory16.length() > 0) + { + LPITEMIDLIST item_id_list = nullptr; + SFGAOF flags = 0; + result = SHParseDisplayName(initial_directory16.c_str(), nullptr, &item_id_list, SFGAO_FILESYSTEM, &flags); + throw_if_failed(result, "SHParseDisplayName failed"); + + ComPtr folder_item; + result = SHCreateShellItem(nullptr, nullptr, item_id_list, folder_item.TypedInitPtr()); + ILFree(item_id_list); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + + /* This code requires Windows Vista or newer: + ComPtr folder_item; + result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr()); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + */ + + if (folder_item) + { + result = open_dialog->SetFolder(folder_item); + throw_if_failed(result, "IFileOpenDialog.SetFolder failed"); + } + } + + if (owner && owner->Window()) + result = open_dialog->Show((HWND)owner->Window()->GetNativeHandle()); + else + result = open_dialog->Show(0); + + if (SUCCEEDED(result)) + { + ComPtr chosen_folder; + result = open_dialog->GetResult(chosen_folder.TypedInitPtr()); + throw_if_failed(result, "IFileOpenDialog.GetResult failed"); + + WCHAR *buffer = nullptr; + result = chosen_folder->GetDisplayName(SIGDN_FILESYSPATH, &buffer); + throw_if_failed(result, "IShellItem.GetDisplayName failed"); + + std::wstring output_directory16; + if (buffer) + { + try + { + output_directory16 = buffer; + } + catch (...) + { + CoTaskMemFree(buffer); + throw; + } + } + + CoTaskMemFree(buffer); + selected_path = from_utf16(output_directory16); + return true; + } + else + { + return false; + } + } + + std::string BrowseFolderDialog::SelectedPath() const override + { + return selected_path; + } + + void BrowseFolderDialog::SetInitialDirectory(const std::string& path) override + { + initial_directory = path; + } + + void BrowseFolderDialog::SetTitle(const std::string& newtitle) override + { + title = newtitle; + } + + void throw_if_failed(HRESULT result, const std::string &error) + { + if (FAILED(result)) + throw std::runtime_error(error); + } +}; + +std::unique_ptr BrowseFolderDialog::Create(Widget* owner) +{ + return std::make_unique(owner); +} + +#else + +std::unique_ptr BrowseFolderDialog::Create(Widget* owner) +{ + return {}; +} + +#endif diff --git a/libraries/ZWidget/src/systemdialogs/open_file_dialog.cpp b/libraries/ZWidget/src/systemdialogs/open_file_dialog.cpp new file mode 100644 index 000000000..ca7bc8f30 --- /dev/null +++ b/libraries/ZWidget/src/systemdialogs/open_file_dialog.cpp @@ -0,0 +1,286 @@ + +#include "systemdialogs/open_file_dialog.h" +#include "core/widget.h" +#include "window/window.h" +#include + +#if defined(WIN32) +#include + +namespace +{ + static std::string from_utf16(const std::wstring& str) + { + if (str.empty()) return {}; + int needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0, nullptr, nullptr); + if (needed == 0) + throw std::runtime_error("WideCharToMultiByte failed"); + std::string result; + result.resize(needed); + needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size(), nullptr, nullptr); + if (needed == 0) + throw std::runtime_error("WideCharToMultiByte failed"); + return result; + } + + static std::wstring to_utf16(const std::string& str) + { + if (str.empty()) return {}; + int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0); + if (needed == 0) + throw std::runtime_error("MultiByteToWideChar failed"); + std::wstring result; + result.resize(needed); + needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size()); + if (needed == 0) + throw std::runtime_error("MultiByteToWideChar failed"); + return result; + } + + template + class ComPtr + { + public: + ComPtr() { Ptr = nullptr; } + ComPtr(const ComPtr& other) { Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } + ComPtr(ComPtr&& move) { Ptr = move.Ptr; move.Ptr = nullptr; } + ~ComPtr() { reset(); } + ComPtr& operator=(const ComPtr& other) { if (this != &other) { if (Ptr) Ptr->Release(); Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } return *this; } + void reset() { if (Ptr) Ptr->Release(); Ptr = nullptr; } + T* get() { return Ptr; } + static IID GetIID() { return __uuidof(T); } + void** InitPtr() { return (void**)TypedInitPtr(); } + T** TypedInitPtr() { reset(); return &Ptr; } + operator T* () const { return Ptr; } + T* operator ->() const { return Ptr; } + T* Ptr; + }; +} + +class OpenFileDialogImpl : public OpenFileDialog +{ +public: + OpenFileDialogImpl(Widget* owner) : owner(owner) + { + } + + Widget* owner = nullptr; + + std::string initial_directory; + std::string initial_filename; + std::string title; + std::vector filenames; + bool multi_select = false; + + struct Filter + { + std::string description; + std::string extension; + }; + std::vector filters; + int filterindex = 0; + std::string defaultext; + + bool Show() override + { + std::wstring title16 = to_utf16(title); + std::wstring initial_directory16 = to_utf16(initial_directory); + + HRESULT result; + ComPtr open_dialog; + + result = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_ALL, open_dialog.GetIID(), open_dialog.InitPtr()); + throw_if_failed(result, "CoCreateInstance(FileOpenDialog) failed"); + + result = open_dialog->SetTitle(title16.c_str()); + throw_if_failed(result, "IFileOpenDialog.SetTitle failed"); + + if (!initial_filename.empty()) + { + result = open_dialog->SetFileName(to_utf16(initial_filename).c_str()); + throw_if_failed(result, "IFileOpenDialog.SetFileName failed"); + } + + FILEOPENDIALOGOPTIONS options = FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST; + if (multi_select) + options |= FOS_ALLOWMULTISELECT; + result = open_dialog->SetOptions(options); + throw_if_failed(result, "IFileOpenDialog.SetOptions() failed"); + + if (!filters.empty()) + { + std::vector filterspecs(filters.size()); + std::vector descriptions(filters.size()); + std::vector extensions(filters.size()); + for (size_t i = 0; i < filters.size(); i++) + { + descriptions[i] = to_utf16(filters[i].description); + extensions[i] = to_utf16(filters[i].extension); + COMDLG_FILTERSPEC& spec = filterspecs[i]; + spec.pszName = descriptions[i].c_str(); + spec.pszSpec = extensions[i].c_str(); + } + result = open_dialog->SetFileTypes((UINT)filterspecs.size(), filterspecs.data()); + throw_if_failed(result, "IFileOpenDialog.SetFileTypes() failed"); + + if ((size_t)filterindex < filters.size()) + { + result = open_dialog->SetFileTypeIndex(filterindex); + throw_if_failed(result, "IFileOpenDialog.SetFileTypeIndex() failed"); + } + } + + if (!defaultext.empty()) + { + result = open_dialog->SetDefaultExtension(to_utf16(defaultext).c_str()); + throw_if_failed(result, "IFileOpenDialog.SetDefaultExtension() failed"); + } + + if (initial_directory16.length() > 0) + { + LPITEMIDLIST item_id_list = nullptr; + SFGAOF flags = 0; + result = SHParseDisplayName(initial_directory16.c_str(), nullptr, &item_id_list, SFGAO_FILESYSTEM, &flags); + throw_if_failed(result, "SHParseDisplayName failed"); + + ComPtr folder_item; + result = SHCreateShellItem(nullptr, nullptr, item_id_list, folder_item.TypedInitPtr()); + ILFree(item_id_list); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + + /* This code requires Windows Vista or newer: + ComPtr folder_item; + result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr()); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + */ + + if (folder_item) + { + result = open_dialog->SetFolder(folder_item); + throw_if_failed(result, "IFileOpenDialog.SetFolder failed"); + } + } + + if (owner && owner->Window()) + result = open_dialog->Show((HWND)owner->Window()->GetNativeHandle()); + else + result = open_dialog->Show(0); + + if (SUCCEEDED(result)) + { + ComPtr items; + result = open_dialog->GetSelectedItems(items.TypedInitPtr()); + throw_if_failed(result, "IFileOpenDialog.GetSelectedItems failed"); + + DWORD num_items = 0; + result = items->GetCount(&num_items); + throw_if_failed(result, "IShellItemArray.GetCount failed"); + + for (DWORD i = 0; i < num_items; i++) + { + ComPtr item; + result = items->GetItemAt(i, item.TypedInitPtr()); + throw_if_failed(result, "IShellItemArray.GetItemAt failed"); + + WCHAR* buffer = nullptr; + result = item->GetDisplayName(SIGDN_FILESYSPATH, &buffer); + throw_if_failed(result, "IShellItem.GetDisplayName failed"); + + std::wstring output16; + if (buffer) + { + try + { + output16 = buffer; + } + catch (...) + { + CoTaskMemFree(buffer); + throw; + } + } + + CoTaskMemFree(buffer); + filenames.push_back(from_utf16(output16)); + } + return true; + } + else + { + return false; + } + } + + std::string Filename() const override + { + return !filenames.empty() ? filenames.front() : std::string(); + } + + std::vector Filenames() const override + { + return filenames; + } + + void SetMultiSelect(bool new_multi_select) override + { + multi_select = new_multi_select; + } + + void SetFilename(const std::string& filename) override + { + initial_filename = filename; + } + + void AddFilter(const std::string& filter_description, const std::string& filter_extension) override + { + Filter f; + f.description = filter_description; + f.extension = filter_extension; + filters.push_back(std::move(f)); + } + + void ClearFilters() override + { + filters.clear(); + } + + void SetFilterIndex(int filter_index) override + { + filterindex = filter_index; + } + + void SetInitialDirectory(const std::string& path) override + { + initial_directory = path; + } + + void SetTitle(const std::string& newtitle) override + { + title = newtitle; + } + + void SetDefaultExtension(const std::string& extension) override + { + defaultext = extension; + } + + void throw_if_failed(HRESULT result, const std::string& error) + { + if (FAILED(result)) + throw std::runtime_error(error); + } +}; + +std::unique_ptr OpenFileDialog::Create(Widget* owner) +{ + return std::make_unique(owner); +} + +#else + +std::unique_ptr OpenFileDialog::Create(Widget* owner) +{ + return {}; +} + +#endif diff --git a/libraries/ZWidget/src/systemdialogs/save_file_dialog.cpp b/libraries/ZWidget/src/systemdialogs/save_file_dialog.cpp new file mode 100644 index 000000000..7bd8c00cd --- /dev/null +++ b/libraries/ZWidget/src/systemdialogs/save_file_dialog.cpp @@ -0,0 +1,263 @@ + +#include "systemdialogs/save_file_dialog.h" +#include "core/widget.h" +#include "window/window.h" +#include + +#if defined(WIN32) +#include + +namespace +{ + static std::string from_utf16(const std::wstring& str) + { + if (str.empty()) return {}; + int needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0, nullptr, nullptr); + if (needed == 0) + throw std::runtime_error("WideCharToMultiByte failed"); + std::string result; + result.resize(needed); + needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size(), nullptr, nullptr); + if (needed == 0) + throw std::runtime_error("WideCharToMultiByte failed"); + return result; + } + + static std::wstring to_utf16(const std::string& str) + { + if (str.empty()) return {}; + int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0); + if (needed == 0) + throw std::runtime_error("MultiByteToWideChar failed"); + std::wstring result; + result.resize(needed); + needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size()); + if (needed == 0) + throw std::runtime_error("MultiByteToWideChar failed"); + return result; + } + + template + class ComPtr + { + public: + ComPtr() { Ptr = nullptr; } + ComPtr(const ComPtr& other) { Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } + ComPtr(ComPtr&& move) { Ptr = move.Ptr; move.Ptr = nullptr; } + ~ComPtr() { reset(); } + ComPtr& operator=(const ComPtr& other) { if (this != &other) { if (Ptr) Ptr->Release(); Ptr = other.Ptr; if (Ptr) Ptr->AddRef(); } return *this; } + void reset() { if (Ptr) Ptr->Release(); Ptr = nullptr; } + T* get() { return Ptr; } + static IID GetIID() { return __uuidof(T); } + void** InitPtr() { return (void**)TypedInitPtr(); } + T** TypedInitPtr() { reset(); return &Ptr; } + operator T* () const { return Ptr; } + T* operator ->() const { return Ptr; } + T* Ptr; + }; +} + +class SaveFileDialogImpl : public SaveFileDialog +{ +public: + SaveFileDialogImpl(Widget* owner) : owner(owner) + { + } + + Widget* owner = nullptr; + + std::string filename; + std::string initial_directory; + std::string initial_filename; + std::string title; + std::vector filenames; + + struct Filter + { + std::string description; + std::string extension; + }; + std::vector filters; + int filterindex = 0; + std::string defaultext; + + bool Show() override + { + std::wstring title16 = to_utf16(title); + std::wstring initial_directory16 = to_utf16(initial_directory); + + HRESULT result; + ComPtr save_dialog; + + result = CoCreateInstance(CLSID_FileSaveDialog, nullptr, CLSCTX_ALL, save_dialog.GetIID(), save_dialog.InitPtr()); + throw_if_failed(result, "CoCreateInstance(FileSaveDialog) failed"); + + result = save_dialog->SetTitle(title16.c_str()); + throw_if_failed(result, "IFileSaveDialog.SetTitle failed"); + + if (!initial_filename.empty()) + { + result = save_dialog->SetFileName(to_utf16(initial_filename).c_str()); + throw_if_failed(result, "IFileSaveDialog.SetFileName failed"); + } + + FILEOPENDIALOGOPTIONS options = FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST; + result = save_dialog->SetOptions(options); + throw_if_failed(result, "IFileSaveDialog.SetOptions() failed"); + + if (!filters.empty()) + { + std::vector filterspecs(filters.size()); + std::vector descriptions(filters.size()); + std::vector extensions(filters.size()); + for (size_t i = 0; i < filters.size(); i++) + { + descriptions[i] = to_utf16(filters[i].description); + extensions[i] = to_utf16(filters[i].extension); + COMDLG_FILTERSPEC& spec = filterspecs[i]; + spec.pszName = descriptions[i].c_str(); + spec.pszSpec = extensions[i].c_str(); + } + result = save_dialog->SetFileTypes((UINT)filterspecs.size(), filterspecs.data()); + throw_if_failed(result, "IFileOpenDialog.SetFileTypes() failed"); + + if ((size_t)filterindex < filters.size()) + { + result = save_dialog->SetFileTypeIndex(filterindex); + throw_if_failed(result, "IFileOpenDialog.SetFileTypeIndex() failed"); + } + } + + if (!defaultext.empty()) + { + result = save_dialog->SetDefaultExtension(to_utf16(defaultext).c_str()); + throw_if_failed(result, "IFileOpenDialog.SetDefaultExtension() failed"); + } + + if (initial_directory16.length() > 0) + { + LPITEMIDLIST item_id_list = nullptr; + SFGAOF flags = 0; + result = SHParseDisplayName(initial_directory16.c_str(), nullptr, &item_id_list, SFGAO_FILESYSTEM, &flags); + throw_if_failed(result, "SHParseDisplayName failed"); + + ComPtr folder_item; + result = SHCreateShellItem(nullptr, nullptr, item_id_list, folder_item.TypedInitPtr()); + ILFree(item_id_list); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + + /* This code requires Windows Vista or newer: + ComPtr folder_item; + result = SHCreateItemFromParsingName(initial_directory16.c_str(), nullptr, folder_item.GetIID(), folder_item.InitPtr()); + throw_if_failed(result, "SHCreateItemFromParsingName failed"); + */ + + if (folder_item) + { + result = save_dialog->SetFolder(folder_item); + throw_if_failed(result, "IFileSaveDialog.SetFolder failed"); + } + } + + if (owner && owner->Window()) + result = save_dialog->Show((HWND)owner->Window()->GetNativeHandle()); + else + result = save_dialog->Show(0); + + if (SUCCEEDED(result)) + { + ComPtr chosen_folder; + result = save_dialog->GetResult(chosen_folder.TypedInitPtr()); + throw_if_failed(result, "IFileSaveDialog.GetResult failed"); + + WCHAR* buffer = nullptr; + result = chosen_folder->GetDisplayName(SIGDN_FILESYSPATH, &buffer); + throw_if_failed(result, "IShellItem.GetDisplayName failed"); + + std::wstring output16; + if (buffer) + { + try + { + output16 = buffer; + } + catch (...) + { + CoTaskMemFree(buffer); + throw; + } + } + + CoTaskMemFree(buffer); + filename = from_utf16(output16); + return true; + } + else + { + return false; + } + } + + std::string Filename() const override + { + return filename; + } + + void SetFilename(const std::string& filename) override + { + initial_filename = filename; + } + + void AddFilter(const std::string& filter_description, const std::string& filter_extension) override + { + Filter f; + f.description = filter_description; + f.extension = filter_extension; + filters.push_back(std::move(f)); + } + + void ClearFilters() override + { + filters.clear(); + } + + void SetFilterIndex(int filter_index) override + { + filterindex = filter_index; + } + + void SetInitialDirectory(const std::string& path) override + { + initial_directory = path; + } + + void SetTitle(const std::string& newtitle) override + { + title = newtitle; + } + + void SetDefaultExtension(const std::string& extension) override + { + defaultext = extension; + } + + void throw_if_failed(HRESULT result, const std::string& error) + { + if (FAILED(result)) + throw std::runtime_error(error); + } +}; + +std::unique_ptr SaveFileDialog::Create(Widget* owner) +{ + return std::make_unique(owner); +} + +#else + +std::unique_ptr SaveFileDialog::Create(Widget* owner) +{ + return {}; +} + +#endif diff --git a/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp b/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp index 38b928f95..2905604af 100644 --- a/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp +++ b/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp @@ -1,4 +1,4 @@ - +#include #include "widgets/lineedit/lineedit.h" #include "core/utf8reader.h" #include "core/colorf.h" diff --git a/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.cpp b/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.cpp index 98988cd0b..f80fe86c0 100644 --- a/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.cpp +++ b/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.cpp @@ -286,11 +286,10 @@ void SDL2DisplayWindow::RunLoop() while (!ExitRunLoop) { - SDL_Event event; + SDL_Event event = {}; int result = SDL_WaitEvent(&event); - if (result == 0) - throw std::runtime_error(std::string("SDL_WaitEvent failed:") + SDL_GetError()); - DispatchEvent(event); + if (result == 1) + DispatchEvent(event); // Silently ignore if it fails and pray it doesn't busy loop, because SDL and Linux utterly sucks! } } diff --git a/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.h b/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.h index cb5c37219..ca0a61f0e 100644 --- a/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.h +++ b/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.h @@ -45,6 +45,8 @@ public: std::string GetClipboardText() override; void SetClipboardText(const std::string& text) override; + void* GetNativeHandle() override { return WindowHandle; } + static void DispatchEvent(const SDL_Event& event); static SDL2DisplayWindow* FindEventWindow(const SDL_Event& event); diff --git a/libraries/ZWidget/src/window/win32/win32displaywindow.cpp b/libraries/ZWidget/src/window/win32/win32displaywindow.cpp index c703a39b7..2bf0acff5 100644 --- a/libraries/ZWidget/src/window/win32/win32displaywindow.cpp +++ b/libraries/ZWidget/src/window/win32/win32displaywindow.cpp @@ -128,8 +128,6 @@ void Win32DisplayWindow::SetWindowFrame(const Rect& box) void Win32DisplayWindow::SetClientFrame(const Rect& box) { - // This function is currently unused but needs to be disabled because it contains Windows API calls that were only added in Windows 10. -#if 0 double dpiscale = GetDpiScale(); RECT rect = {}; @@ -143,7 +141,6 @@ void Win32DisplayWindow::SetClientFrame(const Rect& box) AdjustWindowRectExForDpi(&rect, style, FALSE, exstyle, GetDpiForWindow(WindowHandle)); SetWindowPos(WindowHandle, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOZORDER); -#endif } void Win32DisplayWindow::Show() @@ -271,22 +268,9 @@ int Win32DisplayWindow::GetPixelHeight() const return box.bottom; } -typedef UINT(WINAPI* GetDpiForWindow_t)(HWND); double Win32DisplayWindow::GetDpiScale() const { - static GetDpiForWindow_t pGetDpiForWindow = nullptr; - static bool done = false; - if (!done) - { - HMODULE hMod = GetModuleHandleA("User32.dll"); - if (hMod != nullptr) pGetDpiForWindow = reinterpret_cast(GetProcAddress(hMod, "GetDpiForWindow")); - done = true; - } - - if (pGetDpiForWindow) - return pGetDpiForWindow(WindowHandle) / 96.0; - else - return 1.0; + return GetDpiForWindow(WindowHandle) / 96.0; } std::string Win32DisplayWindow::GetClipboardText() diff --git a/libraries/ZWidget/src/window/win32/win32displaywindow.h b/libraries/ZWidget/src/window/win32/win32displaywindow.h index fd7ae3eae..8aa00e42a 100644 --- a/libraries/ZWidget/src/window/win32/win32displaywindow.h +++ b/libraries/ZWidget/src/window/win32/win32displaywindow.h @@ -55,6 +55,8 @@ public: Point GetLParamPos(LPARAM lparam) const; + void* GetNativeHandle() override { return reinterpret_cast(WindowHandle); } + static void ProcessEvents(); static void RunLoop(); static void ExitLoop(); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8d935e723..b313c1faa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -408,19 +408,14 @@ set( PLAT_WIN32_SOURCES common/platform/win32/st_start.cpp common/platform/win32/base_sysfb.cpp common/platform/win32/win32basevideo.cpp + common/platform/win32/i_crash.cpp ) if (HAVE_VULKAN) list (APPEND PLAT_WIN32_SOURCES common/platform/win32/win32vulkanvideo.cpp ) endif() -# todo: implement an actual crash catcher for ARM -# for now this is purely experimental -if (NOT ${TARGET_ARCHITECTURE} MATCHES "arm" ) - set (PLAT_WIN32_SOURCES ${PLAT_WIN32_SOURCES} common/platform/win32/i_crash.cpp ) -endif() if (MSVC AND ${TARGET_ARCHITECTURE} MATCHES "arm") - set (PLAT_WIN32_SOURCES ${PLAT_WIN32_SOURCES} common/platform/win32/i_crash_arm.cpp ) add_definitions( -DNO_SSE -D__ARM__ -DRAPIDJSON_ENDIAN=RAPIDJSON_LITTLEENDIAN) endif() diff --git a/src/common/platform/win32/i_crash.cpp b/src/common/platform/win32/i_crash.cpp index 7bb6a83b4..e8f77cc76 100644 --- a/src/common/platform/win32/i_crash.cpp +++ b/src/common/platform/win32/i_crash.cpp @@ -1,2291 +1,406 @@ /* -** i_crash.cpp -** Gathers exception information when the program crashes. +** Windows Crash Reporter +** Copyright (c) 2024 Magnus Norddahl ** -**--------------------------------------------------------------------------- -** Copyright 2002-2006 Randy Heit -** All rights reserved. +** This software is provided 'as-is', without any express or implied +** warranty. In no event will the authors be held liable for any damages +** arising from the use of this software. ** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions -** are met: -** -** 1. Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** 2. Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** 3. The name of the author may not be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.s -** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -**--------------------------------------------------------------------------- +** Permission is granted to anyone to use this software for any purpose, +** including commercial applications, and to alter it and redistribute it +** freely, subject to the following restrictions: ** +** 1. The origin of this software must not be misrepresented; you must not +** claim that you wrote the original software. If you use this software +** in a product, an acknowledgment in the product documentation would be +** appreciated but is not required. +** 2. Altered source versions must be plainly marked as such, and must not be +** misrepresented as being the original software. +** 3. This notice may not be removed or altered from any source distribution. */ -// HEADER FILES ------------------------------------------------------------ - -#pragma warning(disable:4996) #define WIN32_LEAN_AND_MEAN #include -#include -#include -#ifndef _M_IX86 -#include -#endif -#ifndef __GNUC__ -#if _MSC_VER -#pragma warning(disable:4091) // this silences a warning for a bogus definition in the Windows 8.1 SDK. -#endif -#include -#if _MSC_VER -#pragma warning(default:4091) -#endif -#endif -#include -#include -#include -#include - -#include -#include -#include "resource.h" -#include "version.h" -#include "m_swap.h" -#include "basics.h" -#include "zstring.h" -#include "printf.h" -#include "cmdlib.h" +#include +#include +#include +#include #include "i_mainwindow.h" -#include -#include +#ifdef _MSC_VER +#include +#include -// MACROS ------------------------------------------------------------------ - -// Maximum number of files that might appear in a crash report. -#define MAX_FILES 5 - -#define ZIP_LOCALFILE MAKE_ID('P','K',3,4) -#define ZIP_CENTRALFILE MAKE_ID('P','K',1,2) -#define ZIP_ENDOFDIR MAKE_ID('P','K',5,6) - -// DBGHELP.H --------------------------------------------------------------- - -// w32api does not include dbghelp.h, so if I don't include these here, -// a person using GCC will need to download the Platform SDK, grab dbghelp.h -// from it, and edit it so it works with GCC. -#ifdef __GNUC__ -typedef enum _MINIDUMP_TYPE +class CrashReporter { - MiniDumpNormal - // Other types omitted. -} MINIDUMP_TYPE; +public: + CrashReporter(const std::wstring& reports_directory, const std::wstring& uploader_executable = std::wstring()); + ~CrashReporter(); -typedef struct _MINIDUMP_EXCEPTION_INFORMATION { - DWORD ThreadId; - PEXCEPTION_POINTERS ExceptionPointers; - BOOL ClientPointers; -} MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION; + static void hook_thread(); + static void invoke(); + static void generate_report(); -typedef struct _MINIDUMP_USER_STREAM_INFORMATION { - ULONG UserStreamCount; - void *UserStreamArray; // Not really void * -} MINIDUMP_USER_STREAM_INFORMATION, *PMINIDUMP_USER_STREAM_INFORMATION; +private: + struct DumpParams + { + HANDLE hprocess; + HANDLE hthread; + int thread_id; + PEXCEPTION_POINTERS exception_pointers; + unsigned int exception_code; + }; -typedef BOOL (WINAPI * MINIDUMP_CALLBACK_ROUTINE) ( - IN PVOID CallbackParam, - IN CONST void *CallbackInput, // Not really void * - IN OUT void *CallbackOutput // Not really void * - ); + void load_dbg_help(); + static void create_dump(DumpParams* params, bool launch_uploader); + static std::wstring reports_directory; + static std::wstring uploader_exe; -typedef struct _MINIDUMP_CALLBACK_INFORMATION { - MINIDUMP_CALLBACK_ROUTINE CallbackRoutine; - PVOID CallbackParam; -} MINIDUMP_CALLBACK_INFORMATION, *PMINIDUMP_CALLBACK_INFORMATION; + static std::recursive_mutex mutex; + + static DWORD WINAPI create_dump_main(LPVOID thread_parameter); + + static int generate_report_filter(unsigned int code, struct _EXCEPTION_POINTERS* ep); + static void on_terminate(); + static void on_sigabort(int); + static void on_se_unhandled_exception(unsigned int exception_code, PEXCEPTION_POINTERS exception_pointers); + static LONG WINAPI on_win32_unhandled_exception(PEXCEPTION_POINTERS exception_pointers); + + typedef BOOL(WINAPI* MiniDumpWriteDumpPointer)(HANDLE, DWORD, HANDLE, MINIDUMP_TYPE, CONST PMINIDUMP_EXCEPTION_INFORMATION, CONST PMINIDUMP_USER_STREAM_INFORMATION, CONST PMINIDUMP_CALLBACK_INFORMATION); + HMODULE module_dbghlp; + static MiniDumpWriteDumpPointer func_MiniDumpWriteDump; + + bool enforce_filter(bool enforce); + bool write_memory(BYTE* target, const BYTE* source, DWORD size); +#if defined(_M_X64) + static const BYTE patch_bytes[6]; + BYTE original_bytes[6]; +#elif defined(_M_IX86) + static const BYTE patch_bytes[5]; + BYTE original_bytes[5]; #endif +}; -// Dbghelp.dll is loaded at runtime so we don't create any needless -// dependencies on it. -typedef BOOL (WINAPI *THREADWALK) (HANDLE, LPTHREADENTRY32); -typedef BOOL (WINAPI *MODULEWALK) (HANDLE, LPMODULEENTRY32); -typedef HANDLE (WINAPI *CREATESNAPSHOT) (DWORD, DWORD); -typedef BOOL (WINAPI *WRITEDUMP) (HANDLE, DWORD, HANDLE, int, - PMINIDUMP_EXCEPTION_INFORMATION, - PMINIDUMP_USER_STREAM_INFORMATION, - PMINIDUMP_CALLBACK_INFORMATION); - -// TYPES ------------------------------------------------------------------- - -#ifdef _M_X64 -typedef PRUNTIME_FUNCTION (WINAPI *RTLLOOKUPFUNCTIONENTRY) - (ULONG64 ControlPc, PULONG64 ImageBase, void *HistoryTable); -#endif - -// Damn Microsoft for doing Get/SetWindowLongPtr half-assed. Instead of -// giving them proper prototypes under Win32, they are just macros for -// Get/SetWindowLong, meaning they take LONGs and not LONG_PTRs. -#ifdef _WIN64 -typedef LONG_PTR WLONG_PTR; #else -typedef LONG WLONG_PTR; + +class CrashReporter +{ +public: + CrashReporter(const std::wstring& reports_directory, const std::wstring& uploader_executable = std::wstring()) { } + + static void hook_thread() {} + static void invoke() { } + static void generate_report() { } +}; + #endif -namespace zip +void InitCrashReporter(const std::wstring& reports_directory, const std::wstring& uploader_executable) { -#pragma pack(push,1) - struct LocalFileHeader - { - uint32_t Magic; // 0 - uint8_t VersionToExtract[2]; // 4 - uint16_t Flags; // 6 - uint16_t Method; // 8 - uint16_t ModTime; // 10 - uint16_t ModDate; // 12 - uint32_t CRC32; // 14 - uint32_t CompressedSize; // 18 - uint32_t UncompressedSize; // 22 - uint16_t NameLength; // 26 - uint16_t ExtraLength; // 28 - }; - - struct CentralDirectoryEntry - { - uint32_t Magic; - uint8_t VersionMadeBy[2]; - uint8_t VersionToExtract[2]; - uint16_t Flags; - uint16_t Method; - uint16_t ModTime; - uint16_t ModDate; - uint32_t CRC32; - uint32_t CompressedSize; - uint32_t UncompressedSize; - uint16_t NameLength; - uint16_t ExtraLength; - uint16_t CommentLength; - uint16_t StartingDiskNumber; - uint16_t InternalAttributes; - uint32_t ExternalAttributes; - uint32_t LocalHeaderOffset; - }; - - struct EndOfCentralDirectory - { - uint32_t Magic; - uint16_t DiskNumber; - uint16_t FirstDisk; - uint16_t NumEntries; - uint16_t NumEntriesOnAllDisks; - uint32_t DirectorySize; - uint32_t DirectoryOffset; - uint16_t ZipCommentLength; - }; -#pragma pack(pop) + static CrashReporter reporter(reports_directory, uploader_executable); } -struct TarFile +void CrashReporterHookThread() { - HANDLE File; - const char *Filename; - int ZipOffset; - uint32_t UncompressedSize; - uint32_t CompressedSize; - uint32_t CRC32; - bool Deflated; -}; + CrashReporter::hook_thread(); +} -struct MiniDumpThreadData +void InvokeCrashReporter() { - HANDLE File; - WRITEDUMP pMiniDumpWriteDump; - MINIDUMP_EXCEPTION_INFORMATION *Exceptor; -}; + CrashReporter::invoke(); +} -// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- +#ifdef _MSC_VER -// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- +#pragma warning(disable: 4535) // warning C4535: calling _set_se_translator() requires /EHa -static void AddFile (HANDLE file, const char *filename); -static void CloseTarFiles (); -static HANDLE MakeZip (); -static void AddZipFile (HANDLE ziphandle, TarFile *whichfile, short dosdate, short dostime); -static HANDLE CreateTempFile (); +std::wstring CrashReporter::reports_directory; +std::wstring CrashReporter::uploader_exe; +CrashReporter::MiniDumpWriteDumpPointer CrashReporter::func_MiniDumpWriteDump = 0; +std::recursive_mutex CrashReporter::mutex; -static void DumpBytes (HANDLE file, uint8_t *address); -static void AddStackInfo (HANDLE file, void *dumpaddress, DWORD code, CONTEXT *ctxt); -static void StackWalk (HANDLE file, void *dumpaddress, DWORD *topOfStack, DWORD *jump, CONTEXT *ctxt); -static void AddToolHelp (HANDLE file); - -static HANDLE WriteTextReport (); -static DWORD WINAPI WriteMiniDumpInAnotherThread (LPVOID lpParam); - -static INT_PTR CALLBACK DetailsDlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); -static void SetEditControl (HWND control, HWND sizedisplay, int filenum); - -static BOOL UploadReport (HANDLE report, bool gziped); - -// EXTERNAL DATA DECLARATIONS ---------------------------------------------- - -extern HINSTANCE g_hInst; - -// PUBLIC DATA DEFINITIONS ------------------------------------------------- - -EXCEPTION_POINTERS CrashPointers; - -// PRIVATE DATA DEFINITIONS ------------------------------------------------ - -static HRESULT (__stdcall *pEnableThemeDialogTexture) (HWND hwnd, DWORD dwFlags); - -static TarFile TarFiles[MAX_FILES]; - -static int NumFiles; - -static HANDLE DbgProcess; -static DWORD DbgProcessID; -static DWORD DbgThreadID; -static DWORD CrashCode; -static PVOID CrashAddress; -static bool NeedDbgHelp; -static char CrashSummary[256]; - -static WNDPROC StdStaticProc; - -// CODE -------------------------------------------------------------------- - -//========================================================================== -// -// SafeReadMemory -// -// Returns true if the memory was succussfully read and false if not. -// -//========================================================================== - -static bool SafeReadMemory (const void *base, void *buffer, size_t len) +CrashReporter::CrashReporter(const std::wstring& new_reports_directory, const std::wstring& new_uploader_executable) { -#ifdef __GNUC__ - // GCC version: Test the memory beforehand and hope it doesn't become - // unreadable while we read it. GCC is the only Windows compiler (that - // I know of) that can't do SEH. + reports_directory = new_reports_directory; + if (!reports_directory.empty() && (reports_directory.back() != L'/' && reports_directory.back() != L'\\')) + reports_directory.push_back(L'\\'); + uploader_exe = new_uploader_executable; + load_dbg_help(); + SetUnhandledExceptionFilter(&CrashReporter::on_win32_unhandled_exception); + enforce_filter(true); // Prevent anyone else from changing the unhandled exception filter + hook_thread(); +} - if (IsBadReadPtr (base, len)) - { - return false; - } - memcpy (buffer, base, len); - return true; -#else - // Non-GCC version: Use SEH to catch any bad reads immediately when - // they happen instead of trying to catch them beforehand. +CrashReporter::~CrashReporter() +{ +} - bool success = false; +void CrashReporter::invoke() +{ +#ifdef _DEBUG + DebugBreak(); // Bring up the debugger if it is running +#endif + Sleep(1000); // Give any possibly logging functionality a little time before we die + RaiseException(EXCEPTION_NONCONTINUABLE_EXCEPTION, EXCEPTION_NONCONTINUABLE_EXCEPTION, 0, 0); +} +void CrashReporter::generate_report() +{ __try { - memcpy (buffer, base, len); - success = true; + RaiseException(EXCEPTION_BREAKPOINT, EXCEPTION_BREAKPOINT, 0, 0); } - __except(EXCEPTION_EXECUTE_HANDLER) + __except (generate_report_filter(GetExceptionCode(), GetExceptionInformation())) { } - return success; -#endif -} - -//========================================================================== -// -// GetTopOfStack -// -// Given a pointer to some address on the stack, returns a pointer to -// the top of the stack. -// -//========================================================================== - -DWORD *GetTopOfStack (void *top) -{ - MEMORY_BASIC_INFORMATION memInfo; - - if (VirtualQuery (top, &memInfo, sizeof(memInfo))) - { - return (DWORD *)((uint8_t *)memInfo.BaseAddress + memInfo.RegionSize); - } - else - { - return NULL; - } } -//========================================================================== -// -// WriteMyMiniDump -// -// Writes a minidump if dbghelp.dll is present and adds it to the tarball. -// Otherwise, it just returns INVALID_HANDLE_VALUE. -// -//========================================================================== - -static HANDLE WriteMyMiniDump (void) +void CrashReporter::create_dump(DumpParams* dump_params, bool launch_uploader) { - MINIDUMP_EXCEPTION_INFORMATION exceptor = { DbgThreadID, &CrashPointers, FALSE }; - WCHAR dbghelpPath[MAX_PATH+12], *bs; - WRITEDUMP pMiniDumpWriteDump; + SYSTEMTIME systime; + GetLocalTime(&systime); + HANDLE file = INVALID_HANDLE_VALUE; - BOOL good = FALSE; - HMODULE dbghelp = NULL; - - // Make sure dbghelp.dll and MiniDumpWriteDump are available - // Try loading from the application directory first, then from the search path. - GetModuleFileNameW (NULL, dbghelpPath, MAX_PATH); - dbghelpPath[MAX_PATH] = 0; - bs = wcsrchr (dbghelpPath, '\\'); - if (bs != NULL) + WCHAR minidump_filename[1024]; + int counter; + for (counter = 1; counter < 1000; counter++) { - wcscpy (bs + 1, L"dbghelp.dll"); - dbghelp = LoadLibraryW (dbghelpPath); - } - if (dbghelp == NULL) - { - dbghelp = LoadLibraryA ("dbghelp.dll"); - if (dbghelp == NULL) - { - NeedDbgHelp = true; - return INVALID_HANDLE_VALUE; - } - } - pMiniDumpWriteDump = (WRITEDUMP)GetProcAddress (dbghelp, "MiniDumpWriteDump"); - if (pMiniDumpWriteDump != NULL) - { - file = CreateTempFile (); + swprintf_s(minidump_filename, L"%s%04d-%02d-%02d %02d.%02d.%02d (%d).dmp", reports_directory.c_str(), systime.wYear, systime.wMonth, systime.wDay, systime.wHour, systime.wMinute, systime.wSecond, counter); + file = CreateFile(minidump_filename, GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0); if (file != INVALID_HANDLE_VALUE) - { - if (CrashPointers.ExceptionRecord->ExceptionCode != EXCEPTION_STACK_OVERFLOW) - { - good = pMiniDumpWriteDump (DbgProcess, DbgProcessID, file, -#if 1 - MiniDumpNormal -#else - MiniDumpWithDataSegs|MiniDumpWithIndirectlyReferencedMemory|MiniDumpWithPrivateReadWriteMemory -#endif - , &exceptor, NULL, NULL); - } - else - { - MiniDumpThreadData dumpdata = { file, pMiniDumpWriteDump, &exceptor }; - DWORD id; - HANDLE thread = CreateThread (NULL, 0, WriteMiniDumpInAnotherThread, &dumpdata, 0, &id); - if (thread != nullptr) - { - WaitForSingleObject(thread, INFINITE); - if (GetExitCodeThread(thread, &id)) - { - good = id; - } - } - } - } + break; } - else - { - NeedDbgHelp = true; - } - return good ? file : INVALID_HANDLE_VALUE; -} + if (file == INVALID_HANDLE_VALUE) + return; -//========================================================================== -// -// WriteMiniDumpInAnotherThread -// -// When a stack overflow occurs, there isn't enough room left on the stack -// for MiniDumpWriteDump to do its thing, so we create a new thread with -// a new stack to do the work. -// -//========================================================================== + MINIDUMP_EXCEPTION_INFORMATION info; + info.ThreadId = dump_params->thread_id; + info.ExceptionPointers = dump_params->exception_pointers; + info.ClientPointers = 0; + MINIDUMP_TYPE type = static_cast( + MiniDumpWithHandleData | + MiniDumpWithProcessThreadData | + MiniDumpWithFullMemoryInfo | + MiniDumpWithThreadInfo); + func_MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, type, &info, 0, 0); + CloseHandle(file); -static DWORD WINAPI WriteMiniDumpInAnotherThread (LPVOID lpParam) -{ - MiniDumpThreadData *dumpdata = (MiniDumpThreadData *)lpParam; - return dumpdata->pMiniDumpWriteDump (DbgProcess, DbgProcessID, - dumpdata->File, MiniDumpNormal, dumpdata->Exceptor, NULL, NULL); -} + WCHAR log_filename[1024]; + swprintf_s(log_filename, L"%s%04d-%02d-%02d %02d.%02d.%02d (%d).log", reports_directory.c_str(), systime.wYear, systime.wMonth, systime.wDay, systime.wHour, systime.wMinute, systime.wSecond, counter); -//========================================================================== -// -// Writef -// -// Like printf, but for Win32 file HANDLEs. -// -//========================================================================== - -void Writef (HANDLE file, const char *format, ...) -{ - char buffer[1024]; - va_list args; - DWORD len; - - va_start (args, format); - len = myvsnprintf (buffer, sizeof buffer, format, args); - va_end (args); - WriteFile (file, buffer, len, &len, NULL); -} - -//========================================================================== -// -// WriteLogFileStreamer -// -// The callback function to stream a Rich Edit's contents to a file. -// -//========================================================================== - - -//========================================================================== -// -// WriteLogFile -// -// Writes the contents of a Rich Edit control to a file. -// -//========================================================================== - -HANDLE WriteLogFile() -{ - HANDLE file; - - file = CreateTempFile(); - if (file != INVALID_HANDLE_VALUE) - { - auto writeFile = [&](const void* data, uint32_t size, uint32_t& written) -> bool + file = CreateFile(log_filename, GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + if (file == INVALID_HANDLE_VALUE) + return; + auto writeFile = [&](const void* data, uint32_t size, uint32_t& written) -> bool { DWORD tmp = 0; BOOL result = WriteFile(file, data, size, &tmp, nullptr); written = tmp; return result == TRUE; }; - mainwindow.GetLog(writeFile); + mainwindow.GetLog(writeFile); + CloseHandle(file); + + if (launch_uploader && !uploader_exe.empty()) + { + WCHAR commandline[3 * 1024]; + swprintf_s(commandline, L"modulename -showcrashreport \"%s\" \"%s\"", minidump_filename, log_filename); + + STARTUPINFO startup_info = {}; + PROCESS_INFORMATION process_info = {}; + startup_info.cb = sizeof(STARTUPINFO); + if (CreateProcess(uploader_exe.c_str(), commandline, 0, 0, FALSE, CREATE_SUSPENDED, 0, 0, &startup_info, &process_info)) + { + // We need to allow the process to become the foreground window. Otherwise the error dialog while show up behind other apps + AllowSetForegroundWindow(process_info.dwProcessId); + ResumeThread(process_info.hThread); + + CloseHandle(process_info.hThread); + CloseHandle(process_info.hProcess); + + Sleep(1000); // Give process one second to show the foreground window before we exit + } } - return file; } -//========================================================================== -// -// CreateCrashLog -// -// Creates all the files needed for a crash report. -// -//========================================================================== - -void CreateCrashLog (const char *custominfo, DWORD customsize) +int CrashReporter::generate_report_filter(unsigned int code, struct _EXCEPTION_POINTERS* ep) { - // Do not collect information more than once. - if (NumFiles != 0) - { + DumpParams dump_params; + dump_params.hprocess = GetCurrentProcess(); + dump_params.hthread = GetCurrentThread(); + dump_params.thread_id = GetCurrentThreadId(); + dump_params.exception_pointers = ep; + dump_params.exception_code = code; + create_dump(&dump_params, false); + return EXCEPTION_EXECUTE_HANDLER; +} + +void CrashReporter::load_dbg_help() +{ + module_dbghlp = LoadLibrary(L"dbghelp.dll"); + if (module_dbghlp == 0) return; - } - - DbgThreadID = GetCurrentThreadId(); - DbgProcessID = GetCurrentProcessId(); - DbgProcess = GetCurrentProcess(); - - CrashCode = CrashPointers.ExceptionRecord->ExceptionCode; - CrashAddress = CrashPointers.ExceptionRecord->ExceptionAddress; - - AddFile (WriteTextReport(), "report.txt"); - AddFile (WriteMyMiniDump(), "minidump.mdmp"); - - // Copy app-specific information out of the crashing process's memory - // and into a new temporary file. - if (customsize != 0) - { - HANDLE file = CreateTempFile(); - DWORD wrote; - - if (file != INVALID_HANDLE_VALUE) - { - uint8_t buffer[512]; - DWORD left; - - for (;; customsize -= left, custominfo += left) - { - left = customsize > 512 ? 512 : customsize; - if (left == 0) - { - break; - } - if (SafeReadMemory (custominfo, buffer, left)) - { - WriteFile (file, buffer, left, &wrote, NULL); - } - else - { - Writef (file, "Failed reading remaining %lu bytes\r\n", customsize); - break; - } - } - AddFile (file, "local.txt"); - } - } - AddFile (WriteLogFile(), "log.rtf"); - CloseHandle (DbgProcess); + func_MiniDumpWriteDump = reinterpret_cast(GetProcAddress(module_dbghlp, "MiniDumpWriteDump")); } -//========================================================================== -// -// WriteTextReport -// -// This is the classic plain-text report ZDoom has generated since it -// added crash gathering abilities in 2002. -// -//========================================================================== - -HANDLE WriteTextReport () +void CrashReporter::hook_thread() { - HANDLE file; - - static const struct - { - DWORD Code; const char *Text; - } - exceptions[] = - { - { EXCEPTION_ACCESS_VIOLATION, "Access Violation" }, - { EXCEPTION_ARRAY_BOUNDS_EXCEEDED, "Array Bounds Exceeded" }, - { EXCEPTION_BREAKPOINT, "Breakpoint" }, - { EXCEPTION_DATATYPE_MISALIGNMENT, "Data Type Misalignment" }, - { EXCEPTION_FLT_DENORMAL_OPERAND, "Floating Point Denormal Operand." }, - { EXCEPTION_FLT_DIVIDE_BY_ZERO, "Floating Point Divide By Zero" }, - { EXCEPTION_FLT_INEXACT_RESULT, "Floating Point Inexact Result" }, - { EXCEPTION_FLT_INVALID_OPERATION, "Floating Point Invalid Operation" }, - { EXCEPTION_FLT_OVERFLOW, "Floating Point Overflow" }, - { EXCEPTION_FLT_STACK_CHECK, "Floating Point Stack Check" }, - { EXCEPTION_FLT_UNDERFLOW, "Floating Point Underflow" }, - { EXCEPTION_ILLEGAL_INSTRUCTION, "Illegal Instruction" }, - { EXCEPTION_IN_PAGE_ERROR, "In Page Error" }, - { EXCEPTION_INT_DIVIDE_BY_ZERO, "Integer Divide By Zero" }, - { EXCEPTION_INT_OVERFLOW, "Integer Overflow" }, - { EXCEPTION_INVALID_DISPOSITION, "Invalid Disposition" }, - { EXCEPTION_NONCONTINUABLE_EXCEPTION, "Noncontinuable Exception" }, - { EXCEPTION_PRIV_INSTRUCTION, "Priviledged Instruction" }, - { EXCEPTION_SINGLE_STEP, "Single Step" }, - { EXCEPTION_STACK_OVERFLOW, "Stack Overflow" } - }; - static const char eflagsBits[][2] = - { - { 'C','F' }, // Carry Flag - { 'x','1' }, // Always one - { 'P','F' }, // Parity Flag - { 'x','0' }, // Always zero - { 'A','F' }, // Auxilliary Carry Flag - { 'x','0' }, // Always zero - { 'Z','F' }, // Zero Flag - { 'S','F' }, // Sign Flag - { 'T','F' }, // Trap Flag - { 'I','F' }, // Interrupt Enable Flag - { 'D','F' }, // Direction Flag - { 'O','F' }, // Overflow Flag - { 'x','x' }, // IOPL low bit - { 'x','x' }, // IOPL high bit - { 'N','T' }, // Nested Task - { 'x','0' }, // Always zero - { 'R','F' }, // Resume Flag - { 'V','M' }, // Virtual-8086 Mode - { 'A','C' }, // Alignment Check - { 'V','I' }, // Virtual Interrupt Flag - { 'V','P' } // Virtual Interrupt Pending - }; - - - int i, j; - - file = CreateTempFile (); - if (file == INVALID_HANDLE_VALUE) - { - return file; - } - - OSVERSIONINFO verinfo = { sizeof(verinfo) }; - GetVersionEx (&verinfo); - - for (i = 0; (size_t)i < sizeof(exceptions)/sizeof(exceptions[0]); ++i) - { - if (exceptions[i].Code == CrashPointers.ExceptionRecord->ExceptionCode) - { - break; - } - } - j = mysnprintf (CrashSummary, countof(CrashSummary), "Code: %08lX", CrashPointers.ExceptionRecord->ExceptionCode); - if ((size_t)i < sizeof(exceptions)/sizeof(exceptions[0])) - { - j += mysnprintf (CrashSummary + j, countof(CrashSummary) - j, " (%s", exceptions[i].Text); - if (CrashPointers.ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) - { - // Pre-NT kernels do not seem to provide this information. - if (verinfo.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS) - { - j += mysnprintf (CrashSummary + j, countof(CrashSummary) - j, - " - tried to %s address %p", - CrashPointers.ExceptionRecord->ExceptionInformation[0] ? "write" : "read", - (void *)CrashPointers.ExceptionRecord->ExceptionInformation[1]); - } - } - CrashSummary[j++] = ')'; - } - j += mysnprintf (CrashSummary + j, countof(CrashSummary) - j, "\r\nAddress: %p", CrashPointers.ExceptionRecord->ExceptionAddress); - Writef (file, "%s\r\nFlags: %08X\r\n\r\n", CrashSummary, CrashPointers.ExceptionRecord->ExceptionFlags); - - Writef (file, "Windows %s %d.%d Build %d %s\r\n\r\n", - verinfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ? "9x" : "NT", - verinfo.dwMajorVersion, verinfo.dwMinorVersion, verinfo.dwBuildNumber, verinfo.szCSDVersion); - - CONTEXT *ctxt = CrashPointers.ContextRecord; - - if (ctxt->ContextFlags & CONTEXT_SEGMENTS) - { - Writef (file, "GS=%04x FS=%04x ES=%04x DS=%04x\r\n", - ctxt->SegGs, ctxt->SegFs, ctxt->SegEs, ctxt->SegDs); - } - - if (ctxt->ContextFlags & CONTEXT_INTEGER) - { -#ifndef _M_X64 - Writef (file, "EAX=%08x EBX=%08x ECX=%08x EDX=%08x\r\nESI=%08x EDI=%08x\r\n", - ctxt->Eax, ctxt->Ebx, ctxt->Ecx, ctxt->Edx, ctxt->Esi, ctxt->Edi); -#else - Writef (file, "RAX=%016I64x RBX=%016I64x RCX=%016I64x\r\n" - "RDX=%016I64x RSI=%016I64x RDI=%016I64x\r\n" - "RBP=%016I64x R8=%016I64x R9=%016I64x\r\n" - "R10=%016I64x R11=%016I64x R12=%016I64x\r\n" - "R13=%016I64x R14=%016I64x R15=%016I64x\r\n", - ctxt->Rax, ctxt->Rbx, ctxt->Rcx, ctxt->Rdx, ctxt->Rsi, ctxt->Rdi, ctxt->Rbp, - ctxt->R8, ctxt->R9, ctxt->R10, ctxt->R11, ctxt->R12, ctxt->R13, ctxt->R14, ctxt->R15); -#endif - } - - if (ctxt->ContextFlags & CONTEXT_CONTROL) - { -#ifndef _M_X64 - Writef (file, "EBP=%08x EIP=%08x ESP=%08x CS=%04x SS=%04x\r\nEFlags=%08x\r\n", - ctxt->Ebp, ctxt->Eip, ctxt->Esp, ctxt->SegCs, ctxt->SegSs, ctxt->EFlags); -#else - Writef (file, "RIP=%016I64x RSP=%016I64x\r\nCS=%04x SS=%04x EFlags=%08x\r\n", - ctxt->Rip, ctxt->Rsp, ctxt->SegCs, ctxt->SegSs, ctxt->EFlags); -#endif - - DWORD dw; - - for (i = 0, dw = 1; (size_t)i < sizeof(eflagsBits)/sizeof(eflagsBits[0]); dw <<= 1, ++i) - { - if (eflagsBits[i][0] != 'x') - { - Writef (file, " %c%c%c", eflagsBits[i][0], eflagsBits[i][1], - ctxt->EFlags & dw ? '+' : '-'); - } - } - Writef (file, "\r\n"); - } - - if (ctxt->ContextFlags & CONTEXT_FLOATING_POINT) - { -#ifndef _M_X64 - Writef (file, - "\r\nFPU State:\r\n ControlWord=%04x StatusWord=%04x TagWord=%04x\r\n" - " ErrorOffset=%08x\r\n ErrorSelector=%08x\r\n DataOffset=%08x\r\n DataSelector=%08x\r\n" - // Cr0NpxState was renamed in recent Windows headers so better skip it here. Its meaning is unknown anyway. - //" Cr0NpxState=%08x\r\n" - "\r\n" - , - (uint16_t)ctxt->FloatSave.ControlWord, (uint16_t)ctxt->FloatSave.StatusWord, (uint16_t)ctxt->FloatSave.TagWord, - ctxt->FloatSave.ErrorOffset, ctxt->FloatSave.ErrorSelector, ctxt->FloatSave.DataOffset, - ctxt->FloatSave.DataSelector - //, ctxt->FloatSave.Cr0NpxState - ); - - for (i = 0; i < 8; ++i) - { - DWORD d0, d1; - memcpy(&d0, &ctxt->FloatSave.RegisterArea[20*i+4], sizeof(DWORD)); - memcpy(&d1, &ctxt->FloatSave.RegisterArea[20*i], sizeof(DWORD)); - Writef (file, "MM%d=%08x%08x\r\n", i, d0, d1); - } -#else - for (i = 0; i < 8; ++i) - { - Writef (file, "MM%d=%016I64x\r\n", i, ctxt->Legacy[i].Low); - } - for (i = 0; i < 16; ++i) - { - Writef (file, "XMM%d=%016I64x%016I64x\r\n", i, ctxt->FltSave.XmmRegisters[i].High, ctxt->FltSave.XmmRegisters[i].Low); - } -#endif - } - - AddToolHelp (file); - -#ifdef _M_IX86 - Writef (file, "\r\nBytes near EIP:"); -#else - Writef (file, "\r\nBytes near RIP:"); -#endif - DumpBytes (file, (uint8_t *)CrashPointers.ExceptionRecord->ExceptionAddress-16); - - if (ctxt->ContextFlags & CONTEXT_CONTROL) - { -#ifndef _M_X64 - AddStackInfo (file, (void *)(size_t)CrashPointers.ContextRecord->Esp, - CrashPointers.ExceptionRecord->ExceptionCode, ctxt); -#else - AddStackInfo (file, (void *)CrashPointers.ContextRecord->Rsp, - CrashPointers.ExceptionRecord->ExceptionCode, ctxt); -#endif - } - - return file; + set_terminate(&CrashReporter::on_terminate); + _set_abort_behavior(0, _CALL_REPORTFAULT | _WRITE_ABORT_MSG); + signal(SIGABRT, &CrashReporter::on_sigabort); + _set_se_translator(&CrashReporter::on_se_unhandled_exception); } -//========================================================================== -// -// AddToolHelp -// -// Adds the information supplied by the tool help functions to the text -// report. This includes the list of threads for this process and the -// loaded modules. -// -//========================================================================== - -static void AddToolHelp (HANDLE file) +void CrashReporter::on_terminate() { - HMODULE kernel = GetModuleHandleA ("kernel32.dll"); - if (kernel == NULL) - return; - - CREATESNAPSHOT pCreateToolhelp32Snapshot; - THREADWALK pThread32First; - THREADWALK pThread32Next; - MODULEWALK pModule32First; - MODULEWALK pModule32Next; - - pCreateToolhelp32Snapshot = (CREATESNAPSHOT)GetProcAddress (kernel, "CreateToolhelp32Snapshot"); - pThread32First = (THREADWALK)GetProcAddress (kernel, "Thread32First"); - pThread32Next = (THREADWALK)GetProcAddress (kernel, "Thread32Next"); - pModule32First = (MODULEWALK)GetProcAddress (kernel, "Module32FirstW"); - pModule32Next = (MODULEWALK)GetProcAddress (kernel, "Module32NextW"); - - if (!(pCreateToolhelp32Snapshot && pThread32First && pThread32Next && - pModule32First && pModule32Next)) - { - Writef (file, "\r\nTool Help unavailable\r\n"); - return; - } - - HANDLE snapshot = pCreateToolhelp32Snapshot (TH32CS_SNAPMODULE|TH32CS_SNAPTHREAD, 0); - if (snapshot == INVALID_HANDLE_VALUE) - { - Writef (file, "\r\nTool Help snapshot unavailable\r\n"); - return; - } - - THREADENTRY32 thread = { sizeof(thread) }; - - Writef (file, "\r\nRunning threads:\r\n"); - - if (pThread32First (snapshot, &thread)) - { - do - { - if (thread.th32OwnerProcessID == DbgProcessID) - { - Writef (file, "%08x", thread.th32ThreadID); - - if (thread.th32ThreadID == DbgThreadID) - { - Writef (file, " at %p*", CrashAddress); - } - Writef (file, "\r\n"); - } - } while (pThread32Next (snapshot, &thread)); - } - - MODULEENTRY32 module = { sizeof(module) }; - - Writef (file, "\r\nLoaded modules:\r\n"); - - if (pModule32First (snapshot, &module)) - { - do - { - auto amod = FString(module.szModule); - Writef (file, "%p - %p %c%s\r\n", - module.modBaseAddr, module.modBaseAddr + module.modBaseSize - 1, - module.modBaseAddr <= CrashPointers.ExceptionRecord->ExceptionAddress && - module.modBaseAddr + module.modBaseSize > CrashPointers.ExceptionRecord->ExceptionAddress - ? '*' : ' ', - amod.GetChars()); - } while (pModule32Next (snapshot, &module)); - } - - CloseHandle (snapshot); + invoke(); } -//========================================================================== -// -// AddStackInfo -// -// Writes a stack dump to the text report. -// -//========================================================================== - -static void AddStackInfo (HANDLE file, void *dumpaddress, DWORD code, CONTEXT *ctxt) +void CrashReporter::on_sigabort(int) { - DWORD *addr = (DWORD *)dumpaddress, *jump; - DWORD *topOfStack = GetTopOfStack (dumpaddress); - uint8_t peekb; -#ifdef _M_IX86 - DWORD peekd; -#else - uint64_t peekq; -#endif - - jump = topOfStack; - if (code == EXCEPTION_STACK_OVERFLOW) - { - // If the stack overflowed, only dump the first and last 16KB of it. - if (topOfStack - addr > 32768/4) - { - jump = addr + 16384/4; - } - } - - StackWalk (file, dumpaddress, topOfStack, jump, ctxt); - - Writef (file, "\r\nStack Contents:\r\n"); - DWORD *scan; - for (scan = addr; scan < topOfStack; scan += 4) - { - int i; - ptrdiff_t max; - - if (scan == jump) - { - scan = topOfStack - 16384/4; - Writef (file, "\r\n . . . Snip . . .\r\n\r\n"); - } - - Writef (file, "%p:", scan); -#ifdef _M_IX86 - if (topOfStack - scan < 4) - { - max = topOfStack - scan; - } - else - { - max = 4; - } - - for (i = 0; i < max; ++i) - { - if (!SafeReadMemory (&scan[i], &peekd, 4)) - { - break; - } - Writef (file, " %08x", peekd); - } - for (; i < 4; ++i) - { - Writef (file, " "); - } -#else - if ((uint64_t *)topOfStack - (uint64_t *)scan < 2) - { - max = (uint64_t *)topOfStack - (uint64_t *)scan; - } - else - { - max = 2; - } - - for (i = 0; i < max; ++i) - { - if (!SafeReadMemory (&scan[i], &peekq, 8)) - { - break; - } - Writef (file, " %016x", peekq); - } - if (i < 2) - { - Writef (file, " "); - } -#endif - Writef (file, " "); - for (i = 0; i < int(max*sizeof(void*)); ++i) - { - if (!SafeReadMemory ((uint8_t *)scan + i, &peekb, 1)) - { - break; - } - Writef (file, "%c", peekb >= 0x20 && peekb <= 0x7f ? peekb : '\xb7'); - } - Writef (file, "\r\n"); - } + invoke(); } -#ifdef _M_IX86 - -//========================================================================== -// -// StackWalk -// Win32 version -// -// Lists a possible call trace for the crashing thread to the text report. -// This is not very specific and just lists any pointers into the -// main executable region, whether they are part of the real trace or not. -// -//========================================================================== - -static void StackWalk (HANDLE file, void *dumpaddress, DWORD *topOfStack, DWORD *jump, CONTEXT *ctxt) +void CrashReporter::on_se_unhandled_exception(unsigned int exception_code, PEXCEPTION_POINTERS exception_pointers) { - DWORD *addr = (DWORD *)dumpaddress; + // Ignore those bloody breakpoints! + if (exception_code == EXCEPTION_BREAKPOINT) return; - uint8_t *pBaseOfImage = (uint8_t *)GetModuleHandle (0); - IMAGE_OPTIONAL_HEADER *pHeader = (IMAGE_OPTIONAL_HEADER *)(pBaseOfImage + - ((IMAGE_DOS_HEADER*)pBaseOfImage)->e_lfanew + - sizeof(IMAGE_NT_SIGNATURE) + sizeof(IMAGE_FILE_HEADER)); + DumpParams dump_params; + dump_params.hprocess = GetCurrentProcess(); + dump_params.hthread = GetCurrentThread(); + dump_params.thread_id = GetCurrentThreadId(); + dump_params.exception_pointers = exception_pointers; + dump_params.exception_code = exception_code; - DWORD_PTR codeStart = (DWORD_PTR)pBaseOfImage + pHeader->BaseOfCode; - DWORD_PTR codeEnd = codeStart + pHeader->SizeOfCode; + // Ensure we only get a dump of the first thread crashing - let other threads block here. + std::unique_lock mutex_lock(mutex); - Writef (file, "\r\nPossible call trace:\r\n %08x BOOM", CrashAddress); - for (DWORD *scan = addr; scan < topOfStack; ++scan) - { - if (scan == jump) - { - scan = topOfStack - 16384/4; - Writef (file, "\r\n\r\n . . . . Snip . . . .\r\n"); - } - - DWORD_PTR code; - - if (SafeReadMemory (scan, &code, sizeof(code)) && - code >= codeStart && code < codeEnd) - { - static const char regNames[][4] = - { - "eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi" - }; - - // Check if address is after a call statement. Print what was called if it is. - const uint8_t *bytep = (uint8_t *)code; - uint8_t peekb; - -#define chkbyte(x,m,v) (SafeReadMemory(x, &peekb, 1) && ((peekb & m) == v)) - - if (chkbyte(bytep - 5, 0xFF, 0xE8) || chkbyte(bytep - 5, 0xFF, 0xE9)) - { - DWORD peekd; - if (SafeReadMemory (bytep - 4, &peekd, 4)) - { - DWORD_PTR jumpaddr = peekd + code; - Writef (file, "\r\n %p %s %p", code - 5, - peekb == 0xE9 ? "jmp " : "call", jumpaddr); - if (chkbyte((LPCVOID)jumpaddr, 0xFF, 0xE9) && - SafeReadMemory ((LPCVOID)(jumpaddr + 1), &peekd, 4)) - { - Writef (file," => jmp %p", peekd + jumpaddr + 5); - } - } - else - { - Writef (file, "\r\n %p %s ????????", code - 5, - peekb == 0xE9 ? "jmp " : "call"); - } - } - else if (chkbyte(bytep - 2, 0xFF, 0xFF) && chkbyte(bytep - 1, 0xF7, 0xD0)) - { - Writef (file, "\r\n %p call %s", code - 2, regNames[peekb & 7]); - } - else - { - int i; - - for (i = 2; i < 7; ++i) - { - if (chkbyte(bytep - i, 0xFF, 0xFF) && chkbyte(bytep - i + 1, 070, 020)) - { - break; - } - } - if (i >= 7) - { - Writef (file, "\r\n %08x", code); - } - else - { - int mod, rm, basereg = -1, indexreg = -1; - int scale = 1, offset = 0; - - Writef (file, "\r\n %08x", bytep - i); - bytep -= i - 1; - SafeReadMemory (bytep, &peekb, 1); - mod = peekb >> 6; - rm = peekb & 7; - bytep++; - - if (mod == 0 && rm == 5) - { - mod = 2; - } - else if (rm == 4) - { - int index, base; - - SafeReadMemory (bytep, &peekb, 1); - scale = 1 << (peekb >> 6); - index = (peekb >> 3) & 7; - base = peekb & 7; - bytep++; - - if (index != 4) - { - indexreg = index; - } - if (base != 5) - { - basereg = base; - } - else - { - if (mod == 0) - { - mod = 2; - } - else - { - basereg = 5; - } - } - } - else - { - basereg = rm; - } - - if (mod == 1) - { - signed char miniofs; - SafeReadMemory (bytep++, &miniofs, 1); - offset = miniofs; - } - else - { - SafeReadMemory (bytep, &offset, 4); - bytep += 4; - } - if ((DWORD_PTR)bytep == code) - { - Writef (file, " call [", bytep - i + 1); - if (basereg >= 0) - { - Writef (file, "%s", regNames[basereg]); - } - if (indexreg >= 0) - { - Writef (file, "%s%s", basereg >= 0 ? "+" : "", regNames[indexreg]); - if (scale > 1) - { - Writef (file, "*%d", scale); - } - } - if (offset != 0) - { - if (indexreg < 0 && basereg < 0) - { - Writef (file, "%08x", offset); - } - else - { - char sign; - if (offset < 0) - { - sign = '-'; - offset = -offset; - } - else - { - sign = '+'; - } - Writef (file, "%c0x%x", sign, offset); - } - } - Writef (file, "]"); - } - } - } - } - } - Writef (file, "\r\n"); + // Create dump in separate thread: + DWORD threadId; + HANDLE hThread = CreateThread(0, 0, &CrashReporter::create_dump_main, &dump_params, 0, &threadId); + if (hThread != 0) + WaitForSingleObject(hThread, INFINITE); + TerminateProcess(GetCurrentProcess(), 255); } -#else - -//========================================================================== -// -// StackWalk -// Win64 version -// -// Walks the stack for the crashing thread and dumps the trace to the text -// report. Unlike the Win32 version, Win64 provides facilities for -// doing a 100% exact walk. -// -// See http://www.nynaeve.net/?p=113 for more information, and -// http://www.nynaeve.net/Code/StackWalk64.cpp in particular. -// -//========================================================================== - -static void StackWalk (HANDLE file, void *dumpaddress, DWORD *topOfStack, DWORD *jump, CONTEXT *ctxt) +LONG CrashReporter::on_win32_unhandled_exception(PEXCEPTION_POINTERS exception_pointers) { - RTLLOOKUPFUNCTIONENTRY RtlLookupFunctionEntry; - HMODULE kernel; - CONTEXT context; - KNONVOLATILE_CONTEXT_POINTERS nv_context; - PRUNTIME_FUNCTION function; - PVOID handler_data; - ULONG64 establisher_frame; - ULONG64 image_base; + DumpParams dump_params; + dump_params.hprocess = GetCurrentProcess(); + dump_params.hthread = GetCurrentThread(); + dump_params.thread_id = GetCurrentThreadId(); + dump_params.exception_pointers = exception_pointers; + dump_params.exception_code = 0; - Writef (file, "\r\nCall trace:\r\n rip=%p <- Here it dies.\r\n", CrashAddress); + // Ensure we only get a dump of the first thread crashing - let other threads block here. + std::unique_lock mutex_lock(mutex); - kernel = GetModuleHandleA("kernel32.dll"); - if (kernel == NULL || NULL == (RtlLookupFunctionEntry = - (RTLLOOKUPFUNCTIONENTRY)GetProcAddress(kernel, "RtlLookupFunctionEntry"))) - { - Writef (file, " Unavailable: Could not get address of RtlLookupFunctionEntry\r\n"); - return; - } - // Get the caller's context - context = *ctxt; - - // This unwind loop intentionally skips the first call frame, as it - // shall correspond to the call to StackTrace64, which we aren't - // interested in. - - for (ULONG frame = 0; ; ++frame) - { - // Try to look up unwind metadata for the current function. - function = RtlLookupFunctionEntry(context.Rip, &image_base, NULL); - memset(&nv_context, 0, sizeof(nv_context)); - if (function == NULL) - { - // If we don't have a RUNTIME_FUNCTION, then we've encountered - // a leaf function. Adjust the stack appropriately. - context.Rip = (ULONG64)(*(PULONG64)context.Rsp); - context.Rsp += 8; - Writef(file, " Leaf function\r\n\r\n"); - } - else - { - // Note that there is not a one-to-one correspondance between - // runtime functions and source functions. One source function - // may be broken into multiple runtime functions. This loop walks - // backward from the current runtime function for however many - // consecutive runtime functions precede it. There is a slight - // chance that this will walk across different source functions. - // (Or maybe not, depending on whether or not the compiler - // guarantees that there will be empty space between functions; - // it looks like VC++ might.) In practice, this seems to work - // quite well for identifying the exact address to search for in - // a map file to determine the function name. - - PRUNTIME_FUNCTION function2 = function; - ULONG64 base = image_base; - - while (function2 != NULL) - { - Writef(file, " Function range: %p -> %p\r\n", - (void *)(base + function2->BeginAddress), - (void *)(base + function2->EndAddress)); - function2 = RtlLookupFunctionEntry(base + function2->BeginAddress - 1, &base, NULL); - } - Writef(file, "\r\n"); - - // Use RtlVirtualUnwind to execute the unwind for us. - RtlVirtualUnwind(0/*UNW_FLAG_NHANDLER*/, image_base, context.Rip, - function, &context, &handler_data, &establisher_frame, - &nv_context); - } - // If we reach a RIP of zero, this means we've walked off the end of - // the call stack and are done. - if (context.Rip == 0) - { - break; - } - - // Display the context. Note that we don't bother showing the XMM - // context, although we have the nonvolatile portion of it. - Writef(file, " FRAME %02d:\r\n rip=%p rsp=%p rbp=%p\r\n", - frame, context.Rip, context.Rsp, context.Rbp); - Writef(file, " r12=%p r13=%p r14=%p\r\n" - " rdi=%p rsi=%p rbx=%p\r\n", - context.R12, context.R13, context.R14, - context.Rdi, context.Rsi, context.Rbx); - - static const char reg_names[16][4] = - { - "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", - "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15" - }; - - // If we have stack-based register stores, then display them here. - for (int i = 0; i < 16; ++i) - { - if (nv_context.IntegerContext[i]) - { - Writef(file, " -> '%s' saved on stack at %p (=> %p)\r\n", - reg_names[i], nv_context.IntegerContext[i], *nv_context.IntegerContext[i]); - } - } - } + // Create minidump in seperate thread: + DWORD threadId; + HANDLE hThread = CreateThread(0, 0, &CrashReporter::create_dump_main, &dump_params, 0, &threadId); + if (hThread != 0) + WaitForSingleObject(hThread, INFINITE); + TerminateProcess(GetCurrentProcess(), 255); + return EXCEPTION_EXECUTE_HANDLER; } -#endif - -//========================================================================== -// -// DumpBytes -// -// Writes the bytes around EIP to the text report. -// -//========================================================================== - -static void DumpBytes (HANDLE file, uint8_t *address) +DWORD WINAPI CrashReporter::create_dump_main(LPVOID thread_parameter) { - char line[68*3], *line_p = line; - DWORD len; - uint8_t peek; - - for (int i = 0; i < 16*3; ++i) - { - if ((i & 15) == 0) - { - line_p += mysnprintf (line_p, countof(line) - (line_p - line), "\r\n%p:", address); - } - if (SafeReadMemory (address, &peek, 1)) - { - line_p += mysnprintf (line_p, countof(line) - (line_p - line), " %02x", *address); - } - else - { - line_p += mysnprintf (line_p, countof(line) - (line_p - line), " --"); - } - address++; - } - *line_p++ = '\r'; - *line_p++ = '\n'; - WriteFile (file, line, DWORD(line_p - line), &len, NULL); + create_dump(reinterpret_cast(thread_parameter), true); + TerminateProcess(GetCurrentProcess(), 255); + return 0; } -//========================================================================== -// -// CreateTempFile -// -// Creates a new temporary with read/write access. The file will be deleted -// automatically when it is closed. -// -//========================================================================== - -static HANDLE CreateTempFile () +bool CrashReporter::enforce_filter(bool bEnforce) { - WCHAR temppath[MAX_PATH-13]; - WCHAR tempname[MAX_PATH]; - if (!GetTempPathW (countof(temppath), temppath)) +#if defined(_M_X64) || defined(_M_IX86) + DWORD ErrCode = 0; + + HMODULE hLib = GetModuleHandle(L"kernel32.dll"); + if (hLib == 0) { - temppath[0] = '.'; - temppath[1] = '\0'; + ErrCode = GetLastError(); + return false; } - if (!GetTempFileNameW (temppath, L"zdo", 0, tempname)) + + BYTE* pTarget = (BYTE*)GetProcAddress(hLib, "SetUnhandledExceptionFilter"); + if (pTarget == 0) { - return INVALID_HANDLE_VALUE; + ErrCode = GetLastError(); + return false; } - return CreateFileW (tempname, GENERIC_WRITE|GENERIC_READ, 0, NULL, CREATE_ALWAYS, - FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE|FILE_FLAG_SEQUENTIAL_SCAN, - NULL); -} -//========================================================================== -// -// AddFile -// -// Add another file to be written to the tar. -// -//========================================================================== - -static void AddFile (HANDLE file, const char *filename) -{ - if (NumFiles == MAX_FILES || file == INVALID_HANDLE_VALUE) + if (IsBadReadPtr(pTarget, sizeof(original_bytes))) { - return; + return false; } - TarFiles[NumFiles].File = file; - TarFiles[NumFiles].Filename = filename; - NumFiles++; -} -//========================================================================== -// -// CloseTarFiles -// -// Close all files that were previously registerd with AddFile(). They -// must still be open. -// -//========================================================================== - -static void CloseTarFiles () -{ - for (int i = 0; i < NumFiles; ++i) + if (bEnforce) { - CloseHandle (TarFiles[i].File); - } - NumFiles = 0; -} + // Save the original contents of SetUnhandledExceptionFilter + memcpy(original_bytes, pTarget, sizeof(original_bytes)); -//========================================================================== -// -// WriteBlock -// -// This is a wrapper around WriteFile. If stream is non-NULL, then the data -// is compressed before writing it to the file. outbuf must be 1024 bytes. -// -//========================================================================== - -static DWORD WriteBlock (HANDLE file, LPCVOID buffer, DWORD bytes, z_stream *stream, Bytef *outbuf) -{ - if (stream == NULL) - { - WriteFile (file, buffer, bytes, &bytes, NULL); - return bytes; + // Patch SetUnhandledExceptionFilter + if (!write_memory(pTarget, patch_bytes, sizeof(patch_bytes))) + return false; } else { - DWORD wrote = 0; - - stream->next_in = (Bytef *)buffer; - stream->avail_in = bytes; - - while (stream->avail_in != 0) - { - if (stream->avail_out == 0) - { - stream->next_out = outbuf; - stream->avail_out = 1024; - wrote += 1024; - WriteFile (file, outbuf, 1024, &bytes, NULL); - } - deflate (stream, Z_NO_FLUSH); - } - return wrote; + // Restore the original behavior of SetUnhandledExceptionFilter + if (!write_memory(pTarget, original_bytes, sizeof(original_bytes))) + return false; } -} - -//========================================================================== -// -// WriteZip -// -// Writes a .zip/pk3 file. A HANDLE to the file is returned. This file is -// delete-on-close. -// -// The archive contains all the files previously passed to AddFile(). They -// must still be open, because MakeZip() does not open them itself. -// -//========================================================================== - -static HANDLE MakeZip () -{ - zip::CentralDirectoryEntry central = { ZIP_CENTRALFILE, { 20, 0 }, { 20, 0 }, }; - zip::EndOfCentralDirectory dirend = { ZIP_ENDOFDIR, }; - short dosdate, dostime; - time_t now; - struct tm *nowtm; - int i, numfiles; - HANDLE file; - DWORD len; - uint32_t dirsize; - size_t namelen; - - if (NumFiles == 0) - { - return INVALID_HANDLE_VALUE; - } - - // Open the zip file. - file = CreateTempFile (); - if (file == INVALID_HANDLE_VALUE) - { - return file; - } - - time (&now); - nowtm = localtime (&now); - - if (nowtm == NULL || nowtm->tm_year < 80) - { - dosdate = dostime = 0; - } - else - { - dosdate = (nowtm->tm_year - 80) * 512 + (nowtm->tm_mon + 1) * 32 + nowtm->tm_mday; - dostime = nowtm->tm_hour * 2048 + nowtm->tm_min * 32 + nowtm->tm_sec / 2; - dosdate = LittleShort(dosdate); - dostime = LittleShort(dostime); - } - - // Write out the zip archive, one file at a time - for (i = 0; i < NumFiles; ++i) - { - AddZipFile (file, &TarFiles[i], dosdate, dostime); - } - - // Write the central directory - central.ModTime = dostime; - central.ModDate = dosdate; - - dirend.DirectoryOffset = LittleLong((uint32_t)SetFilePointer (file, 0, NULL, FILE_CURRENT)); - - for (i = 0, numfiles = 0, dirsize = 0; i < NumFiles; ++i) - { - // Skip empty files - if (TarFiles[i].UncompressedSize == 0) - { - continue; - } - numfiles++; - if (TarFiles[i].Deflated) - { - central.Flags = LittleShort((uint16_t)2); - central.Method = LittleShort((uint16_t)8); - } - else - { - central.Flags = 0; - central.Method = 0; - } - namelen = strlen(TarFiles[i].Filename); - central.InternalAttributes = 0; - if (namelen > 4 && stricmp(TarFiles[i].Filename - 4, ".txt") == 0) - { // Bit 0 set indicates this is probably a text file. But do any tools use it? - central.InternalAttributes = LittleShort((uint16_t)1); - } - central.CRC32 = LittleLong(TarFiles[i].CRC32); - central.CompressedSize = LittleLong(TarFiles[i].CompressedSize); - central.UncompressedSize = LittleLong(TarFiles[i].UncompressedSize); - central.NameLength = LittleShort((uint16_t)namelen); - central.LocalHeaderOffset = LittleLong(TarFiles[i].ZipOffset); - WriteFile (file, ¢ral, sizeof(central), &len, NULL); - WriteFile (file, TarFiles[i].Filename, (DWORD)namelen, &len, NULL); - dirsize += DWORD(sizeof(central) + namelen); - } - - // Write the directory terminator - dirend.NumEntriesOnAllDisks = dirend.NumEntries = LittleShort((uint16_t)numfiles); - dirend.DirectorySize = LittleLong(dirsize); - WriteFile (file, &dirend, sizeof(dirend), &len, NULL); - - return file; -} - -//========================================================================== -// -// AddZipFile -// -// Adds a file to the opened zip. -// -//========================================================================== - -static void AddZipFile (HANDLE ziphandle, TarFile *whichfile, short dosdate, short dostime) -{ - zip::LocalFileHeader local = { ZIP_LOCALFILE, }; - Bytef outbuf[1024], inbuf[1024]; - z_stream stream; - DWORD wrote, len, k; - int err; - bool gzip; - - whichfile->UncompressedSize = GetFileSize (whichfile->File, NULL); - whichfile->CompressedSize = 0; - whichfile->ZipOffset = 0; - whichfile->Deflated = false; - - // Skip empty files. - if (whichfile->UncompressedSize == 0) - { - return; - } - - stream.next_in = Z_NULL; - stream.avail_in = 0; - stream.zalloc = Z_NULL; - stream.zfree = Z_NULL; - err = deflateInit2 (&stream, Z_BEST_COMPRESSION, Z_DEFLATED, -MAX_WBITS, - 8, Z_DEFAULT_STRATEGY); - gzip = err == Z_OK; - - if (gzip) - { - local.Method = LittleShort((uint16_t)8); - whichfile->Deflated = true; - stream.next_out = outbuf; - stream.avail_out = sizeof(outbuf); - } - - // Write out the header and filename. - local.VersionToExtract[0] = 20; - local.Flags = gzip ? LittleShort((uint16_t)2) : 0; - local.ModTime = dostime; - local.ModDate = dosdate; - local.UncompressedSize = LittleLong(whichfile->UncompressedSize); - local.NameLength = LittleShort((uint16_t)strlen(whichfile->Filename)); - - whichfile->ZipOffset = SetFilePointer (ziphandle, 0, NULL, FILE_CURRENT); - WriteFile (ziphandle, &local, sizeof(local), &wrote, NULL); - WriteFile (ziphandle, whichfile->Filename, (DWORD)strlen(whichfile->Filename), &wrote, NULL); - - // Write the file itself and calculate its CRC. - SetFilePointer (whichfile->File, 0, NULL, FILE_BEGIN); - for (k = 0; k < whichfile->UncompressedSize; ) - { - len = whichfile->UncompressedSize - k; - if (len > 1024) - { - len = 1024; - } - k += len; - ReadFile (whichfile->File, &inbuf, len, &len, NULL); - whichfile->CRC32 = crc32 (whichfile->CRC32, inbuf, len); - whichfile->CompressedSize += WriteBlock (ziphandle, inbuf, len, gzip ? &stream : NULL, outbuf); - } - - // Flush the zlib stream buffer. - if (gzip) - { - for (bool done = false;;) - { - len = sizeof(outbuf) - stream.avail_out; - if (len != 0) - { - whichfile->CompressedSize += len; - WriteFile (ziphandle, outbuf, len, &wrote, NULL); - stream.next_out = outbuf; - stream.avail_out = sizeof(outbuf); - } - if (done) - { - break; - } - err = deflate (&stream, Z_FINISH); - done = stream.avail_out != 0 || err == Z_STREAM_END; - if (err != Z_STREAM_END && err != Z_OK) - { - break; - } - } - deflateEnd (&stream); - } - - // Fill in fields we didn't know when we wrote the local header. - SetFilePointer (ziphandle, whichfile->ZipOffset + 14, NULL, FILE_BEGIN); - k = LittleLong(whichfile->CRC32); - WriteFile (ziphandle, &k, 4, &wrote, NULL); - k = LittleLong(whichfile->CompressedSize); - WriteFile (ziphandle, &k, 4, &wrote, NULL); - - SetFilePointer (ziphandle, 0, NULL, FILE_END); -} - -//========================================================================== -// -// DrawTransparentBitmap -// -// The replacement for TransparentBlt that does not leak memory under -// Windows 98/ME. As seen in the MSKB. -// -//========================================================================== - -void DrawTransparentBitmap(HDC hdc, HBITMAP hBitmap, short xStart, - short yStart, COLORREF cTransparentColor) -{ - BITMAP bm; - COLORREF cColor; - HBITMAP bmAndBack, bmAndObject, bmAndMem, bmSave; - HBITMAP bmBackOld, bmObjectOld, bmMemOld, bmSaveOld; - HDC hdcMem, hdcBack, hdcObject, hdcTemp, hdcSave; - POINT ptSize; - - hdcTemp = CreateCompatibleDC(hdc); - SelectObject(hdcTemp, hBitmap); // Select the bitmap - - GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&bm); - ptSize.x = bm.bmWidth; // Get width of bitmap - ptSize.y = bm.bmHeight; // Get height of bitmap - DPtoLP(hdcTemp, &ptSize, 1); // Convert from device - - // to logical points - - // Create some DCs to hold temporary data. - hdcBack = CreateCompatibleDC(hdc); - hdcObject = CreateCompatibleDC(hdc); - hdcMem = CreateCompatibleDC(hdc); - hdcSave = CreateCompatibleDC(hdc); - - // Create a bitmap for each DC. DCs are required for a number of - // GDI functions. - - // Monochrome DC - bmAndBack = CreateBitmap(ptSize.x, ptSize.y, 1, 1, NULL); - - // Monochrome DC - bmAndObject = CreateBitmap(ptSize.x, ptSize.y, 1, 1, NULL); - - bmAndMem = CreateCompatibleBitmap(hdc, ptSize.x, ptSize.y); - bmSave = CreateCompatibleBitmap(hdc, ptSize.x, ptSize.y); - - // Each DC must select a bitmap object to store pixel data. - bmBackOld = (HBITMAP)SelectObject(hdcBack, bmAndBack); - bmObjectOld = (HBITMAP)SelectObject(hdcObject, bmAndObject); - bmMemOld = (HBITMAP)SelectObject(hdcMem, bmAndMem); - bmSaveOld = (HBITMAP)SelectObject(hdcSave, bmSave); - - // Set proper mapping mode. - SetMapMode(hdcTemp, GetMapMode(hdc)); - - // Save the bitmap sent here, because it will be overwritten. - BitBlt(hdcSave, 0, 0, ptSize.x, ptSize.y, hdcTemp, 0, 0, SRCCOPY); - - // Set the background color of the source DC to the color. - // contained in the parts of the bitmap that should be transparent - cColor = SetBkColor(hdcTemp, cTransparentColor); - - // Create the object mask for the bitmap by performing a BitBlt - // from the source bitmap to a monochrome bitmap. - BitBlt(hdcObject, 0, 0, ptSize.x, ptSize.y, hdcTemp, 0, 0, - SRCCOPY); - - // Set the background color of the source DC back to the original - // color. - SetBkColor(hdcTemp, cColor); - - // Create the inverse of the object mask. - BitBlt(hdcBack, 0, 0, ptSize.x, ptSize.y, hdcObject, 0, 0, - NOTSRCCOPY); - - // Copy the background of the main DC to the destination. - BitBlt(hdcMem, 0, 0, ptSize.x, ptSize.y, hdc, xStart, yStart, - SRCCOPY); - - // Mask out the places where the bitmap will be placed. - BitBlt(hdcMem, 0, 0, ptSize.x, ptSize.y, hdcObject, 0, 0, SRCAND); - - // Mask out the transparent colored pixels on the bitmap. - BitBlt(hdcTemp, 0, 0, ptSize.x, ptSize.y, hdcBack, 0, 0, SRCAND); - - // XOR the bitmap with the background on the destination DC. - BitBlt(hdcMem, 0, 0, ptSize.x, ptSize.y, hdcTemp, 0, 0, SRCPAINT); - - // Copy the destination to the screen. - BitBlt(hdc, xStart, yStart, ptSize.x, ptSize.y, hdcMem, 0, 0, - SRCCOPY); - - // Place the original bitmap back into the bitmap sent here. - BitBlt(hdcTemp, 0, 0, ptSize.x, ptSize.y, hdcSave, 0, 0, SRCCOPY); - - // Delete the memory bitmaps. - DeleteObject(SelectObject(hdcBack, bmBackOld)); - DeleteObject(SelectObject(hdcObject, bmObjectOld)); - DeleteObject(SelectObject(hdcMem, bmMemOld)); - DeleteObject(SelectObject(hdcSave, bmSaveOld)); - - // Delete the memory DCs. - DeleteDC(hdcMem); - DeleteDC(hdcBack); - DeleteDC(hdcObject); - DeleteDC(hdcSave); - DeleteDC(hdcTemp); -} - -//========================================================================== -// -// TransparentStaticProc -// -// A special window procedure for the static control displaying the dying -// Doom Guy. It draws the bitmap with magenta as transparent. I could have -// made it an owner-draw control instead, but then I wouldn't get any of -// the standard bitmap graphic handling (like automatic sizing). -// -//========================================================================== - -static LRESULT CALLBACK TransparentStaticProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - RECT rect; - PAINTSTRUCT paint; - HDC dc; - - if (uMsg == WM_PAINT) - { - if (GetUpdateRect (hWnd, &rect, FALSE)) - { - dc = BeginPaint (hWnd, &paint); - if (dc != NULL) - { - HBITMAP bitmap = (HBITMAP)SendMessage (hWnd, STM_GETIMAGE, IMAGE_BITMAP, 0); - if (bitmap != NULL) - { - DrawTransparentBitmap (dc, bitmap, 0, 0, RGB(255,0,255)); - } - EndPaint (hWnd, &paint); - } - } - return 0; - } - return CallWindowProc (StdStaticProc, hWnd, uMsg, wParam, lParam); -} - -static HMODULE WinHlp32; - -//========================================================================== -// -// OverviewDlgProc -// -// The DialogProc for the crash overview page. -// -//========================================================================== - -static INT_PTR CALLBACK OverviewDlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) -{ - CHARFORMAT charFormat; - HWND edit; - ENLINK *link; - - switch (message) - { - case WM_INITDIALOG: - { - if (pEnableThemeDialogTexture != NULL) - { - pEnableThemeDialogTexture(hDlg, ETDT_ENABLETAB); - } - - // Setup the header at the top of the page. - edit = GetDlgItem(hDlg, IDC_CRASHHEADER); - SetWindowTextW(edit, WGAMENAME" has encountered a problem and needs to close.\n" - "We are sorry for the inconvenience."); - - // Setup a bold version of the standard dialog font and make the header bold. - charFormat.cbSize = sizeof(charFormat); - SendMessageW(edit, EM_GETCHARFORMAT, SCF_DEFAULT, (LPARAM)&charFormat); - charFormat.dwEffects = CFE_BOLD; - SendMessageW(edit, EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&charFormat); - - // Setup the drawing routine for the dying guy's bitmap. - edit = GetDlgItem(hDlg, IDC_DEADGUYVIEWER); - StdStaticProc = (WNDPROC)(LONG_PTR)SetWindowLongPtr(edit, GWLP_WNDPROC, (WLONG_PTR)(LONG_PTR)TransparentStaticProc); - - // Fill in all the text just below the heading. - edit = GetDlgItem(hDlg, IDC_PLEASETELLUS); - SendMessageW(edit, EM_AUTOURLDETECT, TRUE, 0); - SetWindowTextW(edit, L"Please tell us about this problem.\n" - "The information will NOT be sent to Microsoft.\n\n" - "An error report has been created that you can submit to help improve " GAMENAME ". " - /*"You can either save it to disk and make a report in the bugs forum at " FORUM_URL ", " - "or you can send it directly without letting other people know about it."*/); - SendMessageW(edit, EM_SETSEL, 0, 81); - SendMessageW(edit, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&charFormat); - SendMessageW(edit, EM_SETEVENTMASK, 0, ENM_LINK); - - // Assign a default invalid file handle to the user's edit control. - edit = GetDlgItem(hDlg, IDC_CRASHINFO); - SetWindowLongPtrW(edit, GWLP_USERDATA, (LONG_PTR)INVALID_HANDLE_VALUE); - - // Fill in the summary text at the bottom of the page. - edit = GetDlgItem(hDlg, IDC_CRASHSUMMARY); - auto wsum = WideString(CrashSummary); - SetWindowTextW(edit, wsum.c_str()); - return TRUE; - } - - case WM_NOTIFY: - // When the link in the "please tell us" edit control is clicked, open - // the bugs forum in a browser window. ShellExecute is used to do this, - // so the default browser, whatever it may be, will open it. - link = (ENLINK *)lParam; - if (link->nmhdr.idFrom == IDC_PLEASETELLUS && link->nmhdr.code == EN_LINK) - { - if (link->msg == WM_LBUTTONDOWN) - { - //ShellExecuteA (NULL, "open", BUGS_FORUM_URL, NULL, NULL, 0); - SetWindowLongPtrW (hDlg, DWLP_MSGRESULT, 1); - return TRUE; - } - } - return FALSE; - break; - } - return FALSE; -} - -//========================================================================== -// -// CrashDlgProc -// -// The DialogProc for the main crash dialog. -// -//========================================================================== - -static INT_PTR CALLBACK CrashDlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) -{ - static WCHAR overview[] = L"Overview"; - static WCHAR details[] = L"Details"; - HWND edit; - TCITEM tcitem; - RECT tabrect, tcrect; - LPNMHDR nmhdr; - - switch (message) - { - case WM_INITDIALOG: - // Set up the tab control - tcitem.mask = TCIF_TEXT | TCIF_PARAM; - edit = GetDlgItem (hDlg, IDC_CRASHTAB); - - GetWindowRect (edit, &tcrect); - ScreenToClient (hDlg, (LPPOINT)&tcrect.left); - ScreenToClient (hDlg, (LPPOINT)&tcrect.right); - - // There are two tabs: Overview and Details. Each pane is created from a - // dialog template, and the resultant window is stored as the lParam for - // the corresponding tab. - tcitem.pszText = overview; - tcitem.lParam = (LPARAM)CreateDialogParamW (g_hInst, MAKEINTRESOURCE(IDD_CRASHOVERVIEW), hDlg, OverviewDlgProc, (LPARAM)edit); - TabCtrl_InsertItem (edit, 0, &tcitem); - TabCtrl_GetItemRect (edit, 0, &tabrect); - SetWindowPos ((HWND)tcitem.lParam, HWND_TOP, tcrect.left + 3, tcrect.top + tabrect.bottom + 3, - tcrect.right - tcrect.left - 8, tcrect.bottom - tcrect.top - tabrect.bottom - 8, 0); - - tcitem.pszText = details; - tcitem.lParam = (LPARAM)CreateDialogParamW (g_hInst, MAKEINTRESOURCE(IDD_CRASHDETAILS), hDlg, DetailsDlgProc, (LPARAM)edit); - TabCtrl_InsertItem (edit, 1, &tcitem); - SetWindowPos ((HWND)tcitem.lParam, HWND_TOP, tcrect.left + 3, tcrect.top + tabrect.bottom + 3, - tcrect.right - tcrect.left - 8, tcrect.bottom - tcrect.top - tabrect.bottom - 8, 0); - break; - - case WM_NOTIFY: - nmhdr = (LPNMHDR)lParam; - if (nmhdr->idFrom == IDC_CRASHTAB) - { - int i = TabCtrl_GetCurSel (nmhdr->hwndFrom); - tcitem.mask = TCIF_PARAM; - TabCtrl_GetItem (nmhdr->hwndFrom, i, &tcitem); - edit = (HWND)tcitem.lParam; - - // TCN_SELCHANGING is sent just before the selected tab changes. - // TCN_SELCHANGE is sent just after the selected tab changes. - // So, when TCN_SELCHANGING is received hide the pane for the - // selected tab, and when TCN_SELCHANGE is received, show the pane - // for the selected tab. - if (nmhdr->code == TCN_SELCHANGING) - { - ShowWindow (edit, SW_HIDE); - SetWindowLongPtr (hDlg, DWLP_MSGRESULT, FALSE); - return TRUE; - } - else if (nmhdr->code == TCN_SELCHANGE) - { - ShowWindow (edit, SW_SHOW); - return TRUE; - } - else - { - return FALSE; - } - } - break; - - case WM_COMMAND: - if (HIWORD(wParam) == BN_CLICKED) - { - EndDialog (hDlg, LOWORD(wParam)); - } - break; - } - return FALSE; -} - -//========================================================================== -// -// DetailsDlgProc -// -// The dialog procedure for when the user wants to examine the files -// in the report. -// -//========================================================================== - -static INT_PTR CALLBACK DetailsDlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) -{ - HWND ctrl; - int i, j; - - switch (message) - { - case WM_INITDIALOG: - if (pEnableThemeDialogTexture != NULL) - { - pEnableThemeDialogTexture (hDlg, ETDT_ENABLETAB); - } - - // Set up the file contents display: No undos. The control's - // userdata stores the index of the file currently displayed. - ctrl = GetDlgItem (hDlg, IDC_CRASHFILECONTENTS); - SendMessageW (ctrl, EM_SETUNDOLIMIT, 0, 0); - SetWindowLongPtrW (ctrl, GWLP_USERDATA, -1); - SetEditControl (ctrl, GetDlgItem(hDlg, IDC_CRASHFILESIZE), 0); - SendMessageW (ctrl, LB_SETCURSEL, 0, 0); - break; - - case WM_SHOWWINDOW: - // When showing this pane, refresh the list of packaged files, and - // update the contents display as necessary. - if (wParam == TRUE) - { - ctrl = GetDlgItem (hDlg, IDC_CRASHFILES); - j = (int)SendMessageW (ctrl, LB_GETCURSEL, 0, 0); - SendMessageW (ctrl, LB_RESETCONTENT, 0, 0); - for (i = 0; i < NumFiles; ++i) - { - SendMessageA (ctrl, LB_ADDSTRING, 0, (LPARAM)TarFiles[i].Filename); - } - if (j == LB_ERR || j >= i) j = 0; - SendMessageW (ctrl, LB_SETCURSEL, j, 0); - - ctrl = GetDlgItem (hDlg, IDC_CRASHFILECONTENTS); - if (j > 2) SetWindowLongPtr (ctrl, GWLP_USERDATA, -1); - SetEditControl (ctrl, GetDlgItem(hDlg, IDC_CRASHFILESIZE), j); - } - break; - - case WM_COMMAND: - // Selecting a different file makes the contents display update. - if (HIWORD(wParam) == LBN_SELCHANGE) - { - i = (int)SendMessage ((HWND)lParam, LB_GETCURSEL, 0, 0); - if (i != LB_ERR) - { - // Update the contents control for this file - SetEditControl (GetDlgItem (hDlg, IDC_CRASHFILECONTENTS), - GetDlgItem (hDlg, IDC_CRASHFILESIZE), i); - } - } - break; - } - return FALSE; -} - -//========================================================================== -// -// StreamEditText -// -// The callback function to stream a text file into a rich edit control. -// -//========================================================================== - -static DWORD CALLBACK StreamEditText (DWORD_PTR cookie, LPBYTE buffer, LONG cb, LONG *pcb) -{ - DWORD wrote; - ReadFile ((HANDLE)cookie, buffer, cb, &wrote, NULL); - *pcb = wrote; - return wrote == 0; -} - -//========================================================================== -// -// StreamEditBinary -// -// The callback function to stream a binary file into a rich edit control. -// The binary file is converted to a RTF hex dump so that the different -// columns can be color-coded. -// -//========================================================================== - -struct BinStreamInfo -{ - int Stage; - HANDLE File; - DWORD Pointer; -}; - -static DWORD CALLBACK StreamEditBinary (DWORD_PTR cookie, LPBYTE buffer, LONG cb, LONG *pcb) -{ - BinStreamInfo *info = (BinStreamInfo *)cookie; - uint8_t buf16[16]; - DWORD read, i; - char *buff_p = (char *)buffer; - char *buff_end = (char *)buffer + cb; - -repeat: - switch (info->Stage) - { - case 0: // Write prologue - buff_p += mysnprintf (buff_p, buff_end - buff_p, "{\\rtf1\\ansi\\deff0" - "{\\colortbl ;\\red0\\green0\\blue80;\\red0\\green0\\blue0;\\red80\\green0\\blue80;}" - "\\viewkind4\\pard"); - info->Stage++; - break; - - case 1: // Write body - while (cb - ((LPBYTE)buff_p - buffer) > 150) - { - ReadFile (info->File, buf16, 16, &read, NULL); - if (read == 0 || info->Pointer >= 65536) - { - info->Stage = read == 0 ? 2 : 3; - goto repeat; - } - char *linestart = buff_p; - buff_p += mysnprintf (buff_p, buff_end - buff_p, "\\cf1 %08lx:\\cf2 ", info->Pointer); - info->Pointer += read; - - for (i = 0; i < read;) - { - if (i <= read - 4) - { - DWORD d; - memcpy(&d, &buf16[i], sizeof(d)); - buff_p += mysnprintf (buff_p, buff_end - buff_p, " %08lx", d); - i += 4; - } - else - { - buff_p += mysnprintf (buff_p, buff_end - buff_p, " %02x", buf16[i]); - i += 1; - } - } - while (buff_p - linestart < 57) - { - *buff_p++ = ' '; - } - buff_p += mysnprintf (buff_p, buff_end - buff_p, "\\cf3 "); - for (i = 0; i < read; ++i) - { - uint8_t code = buf16[i]; - if (code < 0x20 || code > 0x7f) code = 0xB7; - if (code == '\\' || code == '{' || code == '}') *buff_p++ = '\\'; - *buff_p++ = code; - } - buff_p += mysnprintf (buff_p, buff_end - buff_p, "\\par\r\n"); - } - break; - - case 2: // Write epilogue - buff_p += mysnprintf (buff_p, buff_end - buff_p, "\\cf0 }"); - info->Stage = 4; - break; - - case 3: // Write epilogue for truncated file - buff_p += mysnprintf (buff_p, buff_end - buff_p, "--- Rest of file truncated ---\\cf0 }"); - info->Stage = 4; - break; - - case 4: // We're done - return TRUE; - } - - *pcb = (LONG)((LPBYTE)buff_p - buffer); - return FALSE; -} - -//========================================================================== -// -// SetEditControl -// -// Fills the edit control with the contents of the chosen file. -// If the filename ends with ".txt" the file is assumed to be text. -// Otherwise it is treated as binary, and you get a hex dump. -// -//========================================================================== - -static void SetEditControl (HWND edit, HWND sizedisplay, int filenum) -{ - char sizebuf[32]; - EDITSTREAM stream; - DWORD size; - POINT pt = { 0, 0 }; - const char *rtf = NULL; - HGDIOBJ font; - - // Don't refresh the control if it's already showing the file we want. - if (GetWindowLongPtr (edit, GWLP_USERDATA) == filenum) - { - return; - } - - size = GetFileSize (TarFiles[filenum].File, NULL); - if (size < 1024) - { - mysnprintf (sizebuf, countof(sizebuf), "(%lu bytes)", size); - } - else - { - mysnprintf (sizebuf, countof(sizebuf), "(%lu KB)", size/1024); - } - SetWindowTextA (sizedisplay, sizebuf); - - SetWindowLongPtr (edit, GWLP_USERDATA, filenum); - - SetFilePointer (TarFiles[filenum].File, 0, NULL, FILE_BEGIN); - SendMessage (edit, EM_SETSCROLLPOS, 0, (LPARAM)&pt); - - // Set the font now, in case log.rtf was previously viewed, because - // that file changes it. - font = GetStockObject (ANSI_FIXED_FONT); - if (font != INVALID_HANDLE_VALUE) - { - SendMessage (edit, WM_SETFONT, (WPARAM)font, FALSE); - } - - // Text files are streamed in as-is. - // Binary files are streamed in as color-coded hex dumps. - stream.dwError = 0; - if (strstr (TarFiles[filenum].Filename, ".txt") != NULL || - (rtf = strstr (TarFiles[filenum].Filename, ".rtf")) != NULL) - { - CHARFORMAT beBlack; - - beBlack.cbSize = sizeof(beBlack); - beBlack.dwMask = CFM_COLOR; - beBlack.dwEffects = 0; - beBlack.crTextColor = RGB(0,0,0); - SendMessage (edit, EM_SETCHARFORMAT, 0, (LPARAM)&beBlack); - stream.dwCookie = (DWORD_PTR)TarFiles[filenum].File; - stream.pfnCallback = StreamEditText; - SendMessage (edit, EM_STREAMIN, rtf ? SF_RTF : SF_TEXT | SF_USECODEPAGE | (1252 << 16), (LPARAM)&stream); - } - else - { - BinStreamInfo info = { 0, TarFiles[filenum].File, 0 }; - stream.dwCookie = (DWORD_PTR)&info; - stream.pfnCallback = StreamEditBinary; - SendMessage (edit, EM_EXLIMITTEXT, 0, GetFileSize(TarFiles[filenum].File, NULL)*7); - SendMessage (edit, EM_STREAMIN, SF_RTF, (LPARAM)&stream); - } - SendMessage (edit, EM_SETSEL, (WPARAM)-1, 0); -} - -//========================================================================== -// -// SaveReport -// -// Makes a permanent copy of the report tarball. -// -//========================================================================== - -static void SaveReport (HANDLE file) -{ - OPENFILENAME ofn = { -#ifdef OPENFILENAME_SIZE_VERSION_400 - OPENFILENAME_SIZE_VERSION_400 -#else - sizeof(ofn) #endif - , }; - WCHAR filename[256]; - ofn.lpstrFilter = L"Zip file (*.zip)\0*.zip\0"; - wcscpy (filename, L"CrashReport.zip"); - ofn.lpstrFile = filename; - ofn.nMaxFile = countof(filename); - - while (GetSaveFileNameW (&ofn)) - { - HANDLE ofile = CreateFileW (ofn.lpstrFile, GENERIC_WRITE, 0, NULL, - CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_SEQUENTIAL_SCAN, - NULL); - if (ofile == INVALID_HANDLE_VALUE) - { - if (MessageBoxA (NULL, "Could not open the crash report file", - "Save As failed", MB_RETRYCANCEL) == IDRETRY) - { - continue; - } - return; - } - else - { - DWORD fileLen = GetFileSize (file, NULL), fileLeft; - char xferbuf[1024]; - - SetFilePointer (file, 0, NULL, FILE_BEGIN); - fileLeft = fileLen; - while (fileLeft != 0) - { - DWORD grab = fileLeft > sizeof(xferbuf) ? sizeof(xferbuf) : fileLeft; - DWORD didread; - - ReadFile (file, xferbuf, grab, &didread, NULL); - WriteFile (ofile, xferbuf, didread, &grab, NULL); - fileLeft -= didread; - } - CloseHandle (ofile); - return; - } - } + return true; } -//========================================================================== -// -// DisplayCrashLog -// -// Displays the crash information and possibly submits it. CreateCrashLog() -// must have been called previously. -// -//========================================================================== - -void DisplayCrashLog () +bool CrashReporter::write_memory(BYTE* pTarget, const BYTE* pSource, DWORD Size) { - HANDLE file; + DWORD ErrCode = 0; - if (NumFiles == 0) - { - char ohPoo[] = - GAMENAME" crashed but was unable to produce\n" - "detailed information about the crash.\n" - "\nThis is all that is available:\n\nCode=XXXXXXXX\nAddr=XXXXXXXX"; - mysnprintf (ohPoo + countof(ohPoo) - 23, 23, "%08lX\nAddr=%p", CrashCode, CrashAddress); - MessageBoxA (NULL, ohPoo, GAMENAME" Very Fatal Error", MB_OK|MB_ICONSTOP); - if (WinHlp32 != NULL) - { - FreeLibrary (WinHlp32); - } - } - else - { - HMODULE uxtheme = LoadLibraryA ("uxtheme.dll"); - if (uxtheme != NULL) - { - pEnableThemeDialogTexture = (HRESULT (__stdcall *)(HWND,DWORD))GetProcAddress (uxtheme, "EnableThemeDialogTexture"); - } - INT_PTR result = DialogBox (g_hInst, MAKEINTRESOURCE(IDD_CRASHDIALOG), NULL, (DLGPROC)CrashDlgProc); + if (pTarget == 0) + return false; + if (pSource == 0) + return false; + if (Size == 0) + return false; + if (IsBadReadPtr(pSource, Size)) + return false; - if (result == IDC_SAVEREPORT) - { - file = MakeZip (); - SaveReport (file); - CloseHandle (file); - } - if (uxtheme != NULL) - { - FreeLibrary (uxtheme); - } + // Modify protection attributes of the target memory page + + DWORD OldProtect = 0; + + if (!VirtualProtect(pTarget, Size, PAGE_EXECUTE_READWRITE, &OldProtect)) + { + ErrCode = GetLastError(); + return false; } - CloseTarFiles (); + + // Write memory + + memcpy(pTarget, pSource, Size); + + // Restore memory protection attributes of the target memory page + + DWORD Temp = 0; + + if (!VirtualProtect(pTarget, Size, OldProtect, &Temp)) + { + ErrCode = GetLastError(); + return false; + } + + // Success + return true; } + +#if defined(_M_X64) +const BYTE CrashReporter::patch_bytes[6] = { 0x48, 0x31, 0xC0, 0xC2, 0x00, 0x00 }; +#elif defined(_M_IX86) +const BYTE CrashReporter::patch_bytes[5] = { 0x33, 0xC0, 0xC2, 0x04, 0x00 }; +#endif + +#endif diff --git a/src/common/platform/win32/i_crash_arm.cpp b/src/common/platform/win32/i_crash_arm.cpp deleted file mode 100644 index 2a61ab42c..000000000 --- a/src/common/platform/win32/i_crash_arm.cpp +++ /dev/null @@ -1,7 +0,0 @@ -// Licensed to the Public Domain by Rachael Alexandersion (c) 2021 -// This file is nothing more than a stub. Please replace with a proper copyright notice if this function is implemented for ARM. Or just use the regular i_crash.cpp - -void DisplayCrashLog() -{ - // stub -} diff --git a/src/common/platform/win32/i_main.cpp b/src/common/platform/win32/i_main.cpp index 6977055b7..200cd541b 100644 --- a/src/common/platform/win32/i_main.cpp +++ b/src/common/platform/win32/i_main.cpp @@ -32,14 +32,13 @@ ** */ -// HEADER FILES ------------------------------------------------------------ - #define WIN32_LEAN_AND_MEAN #include #include #include #include -#include +#include +#include #include #include @@ -80,8 +79,6 @@ #include "i_mainwindow.h" -// MACROS ------------------------------------------------------------------ - // The main window's title. #ifdef _M_X64 #define X64 " 64-bit" @@ -91,53 +88,25 @@ #define X64 "" #endif -// TYPES ------------------------------------------------------------------- +void InitCrashReporter(const std::wstring& reports_directory, const std::wstring& uploader_executable); +FString GetKnownFolder(int shell_folder, REFKNOWNFOLDERID known_folder, bool create); -// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- - -LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); -void CreateCrashLog (const char *custominfo, DWORD customsize); -void DisplayCrashLog (); void DestroyCustomCursor(); int GameMain(); -// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- - -// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- - -// EXTERNAL DATA DECLARATIONS ---------------------------------------------- - -extern EXCEPTION_POINTERS CrashPointers; extern UINT TimerPeriod; -// PUBLIC DATA DEFINITIONS ------------------------------------------------- - // The command line arguments. FArgs *Args; HINSTANCE g_hInst; -HANDLE MainThread; -DWORD MainThreadID; HANDLE StdOut; bool FancyStdOut, AttachedStdOut; -// CODE -------------------------------------------------------------------- - - -//========================================================================== -// -// I_SetIWADInfo -// -//========================================================================== - void I_SetIWADInfo() { } -//========================================================================== -// -// DoMain -// //========================================================================== int DoMain (HINSTANCE hInstance) @@ -167,7 +136,7 @@ int DoMain (HINSTANCE hInstance) // handle instead of creating a console window. StdOut = GetStdHandle(STD_OUTPUT_HANDLE); - if (StdOut != NULL) + if (StdOut != nullptr) { // It seems that running from a shell always creates a std output // for us, even if it doesn't go anywhere. (Running from Explorer @@ -177,7 +146,7 @@ int DoMain (HINSTANCE hInstance) BY_HANDLE_FILE_INFORMATION info; if (!GetFileInformationByHandle(StdOut, &info)) { - StdOut = NULL; + StdOut = nullptr; } } if (StdOut == nullptr) @@ -185,7 +154,7 @@ int DoMain (HINSTANCE hInstance) if (AttachConsole(ATTACH_PARENT_PROCESS)) { StdOut = GetStdHandle(STD_OUTPUT_HANDLE); - DWORD foo; WriteFile(StdOut, "\n", 1, &foo, NULL); + DWORD foo; WriteFile(StdOut, "\n", 1, &foo, nullptr); AttachedStdOut = true; } if (StdOut == nullptr && AllocConsole()) @@ -217,7 +186,7 @@ int DoMain (HINSTANCE hInstance) // Figure out what directory the program resides in. WCHAR progbuff[1024]; - if (GetModuleFileNameW(nullptr, progbuff, sizeof progbuff) == 0) + if (GetModuleFileNameW(nullptr, progbuff, 1024) == 0) { MessageBoxA(nullptr, "Fatal", "Could not determine program location.", MB_ICONEXCLAMATION|MB_OK); exit(-1); @@ -242,7 +211,7 @@ int DoMain (HINSTANCE hInstance) // element. DEVMODE is not one of those structures. memset (&displaysettings, 0, sizeof(displaysettings)); displaysettings.dmSize = sizeof(displaysettings); - EnumDisplaySettings (NULL, ENUM_CURRENT_SETTINGS, &displaysettings); + EnumDisplaySettings (nullptr, ENUM_CURRENT_SETTINGS, &displaysettings); x = (displaysettings.dmPelsWidth - width) / 2; y = (displaysettings.dmPelsHeight - height) / 2; @@ -260,17 +229,17 @@ int DoMain (HINSTANCE hInstance) WinWidth = cRect.right; WinHeight = cRect.bottom; - CoInitialize (NULL); - atexit ([](){ CoUninitialize(); }); // beware of calling convention. + if (SUCCEEDED(CoInitialize(nullptr))) + atexit ([](){ CoUninitialize(); }); // beware of calling convention. int ret = GameMain (); if (mainwindow.CheckForRestart()) { - HMODULE hModule = GetModuleHandleW(NULL); + HMODULE hModule = GetModuleHandleW(nullptr); WCHAR path[MAX_PATH]; GetModuleFileNameW(hModule, path, MAX_PATH); - ShellExecuteW(NULL, L"open", path, GetCommandLineW(), NULL, SW_SHOWNORMAL); + ShellExecuteW(nullptr, L"open", path, GetCommandLineW(), nullptr, SW_SHOWNORMAL); } DestroyCustomCursor(); @@ -287,7 +256,7 @@ int DoMain (HINSTANCE hInstance) if (StdOut != nullptr) WriteFile(StdOut, "Press any key to exit...", 24, &bytes, nullptr); FlushConsoleInputBuffer(stdinput); SetConsoleMode(stdinput, 0); - ReadConsole(stdinput, &bytes, 1, &bytes, NULL); + ReadConsole(stdinput, &bytes, 1, &bytes, nullptr); } else if (StdOut == nullptr) { @@ -319,231 +288,13 @@ void I_ShowFatalError(const char *msg) } } -// Here is how the error logging system works. -// -// To catch exceptions that occur in secondary threads, CatchAllExceptions is -// set as the UnhandledExceptionFilter for this process. It records the state -// of the thread at the time of the crash using CreateCrashLog and then queues -// an APC on the primary thread. When the APC executes, it raises a software -// exception that gets caught by the __try/__except block in WinMain. -// I_GetEvent calls SleepEx to put the primary thread in a waitable state -// periodically so that the APC has a chance to execute. -// -// Exceptions on the primary thread are caught by the __try/__except block in -// WinMain. Not only does it record the crash information, it also shuts -// everything down and displays a dialog with the information present. If a -// console log is being produced, the information will also be appended to it. -// -// If a debugger is running, CatchAllExceptions never executes, so secondary -// thread exceptions will always be caught by the debugger. For the primary -// thread, IsDebuggerPresent is called to determine if a debugger is present. -// Note that this function is not present on Windows 95, so we cannot -// statically link to it. -// -// To make this work with MinGW, you will need to use inline assembly -// because GCC offers no native support for Windows' SEH. - -//========================================================================== -// -// SleepForever -// //========================================================================== -void SleepForever () -{ - Sleep (INFINITE); -} - -//========================================================================== -// -// ExitMessedUp -// -// An exception occurred while exiting, so don't do any standard processing. -// Just die. -// -//========================================================================== - -LONG WINAPI ExitMessedUp (LPEXCEPTION_POINTERS foo) -{ - ExitProcess (1000); -} - -//========================================================================== -// -// ExitFatally -// -//========================================================================== - -void CALLBACK ExitFatally (ULONG_PTR dummy) -{ - SetUnhandledExceptionFilter (ExitMessedUp); - I_ShutdownGraphics (); - mainwindow.RestoreConView (); - DisplayCrashLog (); - exit(-1); -} - -#ifndef _M_ARM64 -//========================================================================== -// -// CatchAllExceptions -// -//========================================================================== - -namespace -{ - CONTEXT MainThreadContext; -} - -LONG WINAPI CatchAllExceptions (LPEXCEPTION_POINTERS info) -{ -#ifdef _DEBUG - if (info->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT) - { - return EXCEPTION_CONTINUE_SEARCH; - } -#endif - - static bool caughtsomething = false; - - if (caughtsomething) return EXCEPTION_EXECUTE_HANDLER; - caughtsomething = true; - - char *custominfo = (char *)HeapAlloc (GetProcessHeap(), 0, 16384); - - CrashPointers = *info; - if (sysCallbacks.CrashInfo && custominfo) sysCallbacks.CrashInfo(custominfo, 16384, "\r\n"); - CreateCrashLog (custominfo, (DWORD)strlen(custominfo)); - - // If the main thread crashed, then make it clean up after itself. - // Otherwise, put the crashing thread to sleep and signal the main thread to clean up. - if (GetCurrentThreadId() == MainThreadID) - { -#ifdef _M_X64 - *info->ContextRecord = MainThreadContext; -#else - info->ContextRecord->Eip = (DWORD_PTR)ExitFatally; -#endif // _M_X64 - } - else - { -#ifndef _M_X64 - info->ContextRecord->Eip = (DWORD_PTR)SleepForever; -#else - info->ContextRecord->Rip = (DWORD_PTR)SleepForever; -#endif - QueueUserAPC (ExitFatally, MainThread, 0); - } - return EXCEPTION_CONTINUE_EXECUTION; -} -#else // !_M_ARM64 -// stub this function for ARM64 -LONG WINAPI CatchAllExceptions (LPEXCEPTION_POINTERS info) -{ - return EXCEPTION_CONTINUE_EXECUTION; -} -#endif // !_M_ARM64 - -//========================================================================== -// -// infiniterecursion -// -// Debugging routine for testing the crash logger. -// -//========================================================================== - -#ifdef _DEBUG -static void infiniterecursion(int foo) -{ - if (foo) - { - infiniterecursion(foo); - } -} -#endif - -// Setting this to 'true' allows getting the standard notification for a crash -// which offers the very important feature to open a debugger and see the crash in context right away. -CUSTOM_CVAR(Bool, disablecrashlog, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) -{ - SetUnhandledExceptionFilter(!*self ? CatchAllExceptions : nullptr); -} - -//========================================================================== -// -// WinMain -// -//========================================================================== - -int WINAPI wWinMain (HINSTANCE hInstance, HINSTANCE nothing, LPWSTR cmdline, int nCmdShow) +int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE nothing, LPWSTR cmdline, int nCmdShow) { g_hInst = hInstance; - InitCommonControls (); // Load some needed controls and be pretty under XP - - // We need to load riched20.dll so that we can create the control. - if (NULL == LoadLibraryA ("riched20.dll")) - { - // This should only happen on basic Windows 95 installations, but since we - // don't support Windows 95, we have no obligation to provide assistance in - // getting it installed. - MessageBoxA(NULL, "Could not load riched20.dll", GAMENAME " Error", MB_OK | MB_ICONSTOP); - return 0; - } - -#if !defined(__GNUC__) && defined(_DEBUG) - if (__argc == 2 && __wargv != nullptr && wcscmp (__wargv[1], L"TestCrash") == 0) - { - __try - { - *(int *)0 = 0; - } - __except(CrashPointers = *GetExceptionInformation(), - CreateCrashLog ("TestCrash", 9), EXCEPTION_EXECUTE_HANDLER) - { - } - DisplayCrashLog (); - return 0; - } - if (__argc == 2 && __wargv != nullptr && wcscmp (__wargv[1], L"TestStackCrash") == 0) - { - __try - { - infiniterecursion(1); - } - __except(CrashPointers = *GetExceptionInformation(), - CreateCrashLog ("TestStackCrash", 14), EXCEPTION_EXECUTE_HANDLER) - { - } - DisplayCrashLog (); - return 0; - } -#endif - - MainThread = INVALID_HANDLE_VALUE; - DuplicateHandle (GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &MainThread, - 0, FALSE, DUPLICATE_SAME_ACCESS); - MainThreadID = GetCurrentThreadId(); - -#ifndef _DEBUG - if (MainThread != INVALID_HANDLE_VALUE) - { -#ifndef _M_ARM64 - SetUnhandledExceptionFilter (CatchAllExceptions); -#endif - -#ifdef _M_X64 - static bool setJumpResult = false; - RtlCaptureContext(&MainThreadContext); - if (setJumpResult) - { - ExitFatally(0); - return 0; - } - setJumpResult = true; -#endif // _M_X64 - } -#endif + InitCommonControls(); #if defined(_DEBUG) && defined(_MSC_VER) // Uncomment this line to make the Visual C++ CRT check the heap before @@ -558,14 +309,24 @@ int WINAPI wWinMain (HINSTANCE hInstance, HINSTANCE nothing, LPWSTR cmdline, int //_crtBreakAlloc = 227524; #endif - int ret = DoMain (hInstance); + // Setup crash reporting, unless it is the crash reporter launching us in response to a crash + if (wcsstr(cmdline, L"-showcrashreport") == nullptr) + { + WCHAR exeFilename[1024] = {}; + if (GetModuleFileName(0, exeFilename, 1023) != 0) + { + FString reportsDirectory = GetKnownFolder(CSIDL_LOCAL_APPDATA, FOLDERID_LocalAppData, true); + reportsDirectory += "/" GAMENAMELOWERCASE; + reportsDirectory += "/crashreports"; + CreatePath(reportsDirectory.GetChars()); - CloseHandle (MainThread); - MainThread = INVALID_HANDLE_VALUE; - return ret; + InitCrashReporter(reportsDirectory.WideString(), exeFilename); + } + } + + return DoMain(hInstance); } -// each platform has its own specific version of this function. void I_SetWindowTitle(const char* caption) { mainwindow.SetWindowTitle(caption); diff --git a/src/common/widgets/errorwindow.cpp b/src/common/widgets/errorwindow.cpp index 625f278f7..d74864bb6 100644 --- a/src/common/widgets/errorwindow.cpp +++ b/src/common/widgets/errorwindow.cpp @@ -6,14 +6,16 @@ #include #include #include +#include +#include -bool ErrorWindow::ExecModal(const std::string& text, const std::string& log) +bool ErrorWindow::ExecModal(const std::string& text, const std::string& log, std::vector minidump) { Size screenSize = GetScreenSize(); double windowWidth = 1200.0; double windowHeight = 700.0; - auto window = std::make_unique(); + auto window = std::make_unique(std::move(minidump)); window->SetText(text, log); window->SetFrameGeometry((screenSize.width - windowWidth) * 0.5, (screenSize.height - windowHeight) * 0.5, windowWidth, windowHeight); window->Show(); @@ -23,7 +25,7 @@ bool ErrorWindow::ExecModal(const std::string& text, const std::string& log) return window->Restart; } -ErrorWindow::ErrorWindow() : Widget(nullptr, WidgetType::Window) +ErrorWindow::ErrorWindow(std::vector initminidump) : Widget(nullptr, WidgetType::Window), minidump(std::move(initminidump)) { FStringf caption("Fatal Error - " GAMENAME " %s (%s)", GetVersionString(), GetGitTime()); SetWindowTitle(caption.GetChars()); @@ -34,13 +36,21 @@ ErrorWindow::ErrorWindow() : Widget(nullptr, WidgetType::Window) LogView = new LogViewer(this); ClipboardButton = new PushButton(this); - RestartButton = new PushButton(this); - ClipboardButton->OnClick = [=]() { OnClipboardButtonClicked(); }; - RestartButton->OnClick = [=]() { OnRestartButtonClicked(); }; - ClipboardButton->SetText("Copy to clipboard"); - RestartButton->SetText("Restart"); + + if (minidump.empty()) + { + RestartButton = new PushButton(this); + RestartButton->OnClick = [=]() { OnRestartButtonClicked(); }; + RestartButton->SetText("Restart"); + } + else + { + SaveReportButton = new PushButton(this); + SaveReportButton->OnClick = [=]() { OnSaveReportButtonClicked(); }; + SaveReportButton->SetText("Save Report"); + } LogView->SetFocus(); } @@ -83,6 +93,37 @@ void ErrorWindow::OnRestartButtonClicked() DisplayWindow::ExitLoop(); } +void ErrorWindow::OnSaveReportButtonClicked() +{ + auto dialog = SaveFileDialog::Create(this); + dialog->AddFilter("Crash Report Zip Files", "*.zip"); + dialog->AddFilter("All Files", "*.*"); + dialog->SetFilename("CrashReport.zip"); + dialog->SetDefaultExtension("zip"); + if (dialog->Show()) + { + std::string filename = dialog->Filename(); + + mz_zip_archive zip = {}; + if (mz_zip_writer_init_heap(&zip, 0, 16 * 1024 * 1024)) + { + mz_zip_writer_add_mem(&zip, "minidump.dmp", minidump.data(), minidump.size(), MZ_DEFAULT_COMPRESSION); + mz_zip_writer_add_mem(&zip, "log.txt", clipboardtext.data(), clipboardtext.size(), MZ_DEFAULT_COMPRESSION); + } + void* buffer = nullptr; + size_t buffersize = 0; + mz_zip_writer_finalize_heap_archive(&zip, &buffer, &buffersize); + mz_zip_writer_end(&zip); + + std::unique_ptr f(FileWriter::Open(filename.c_str())); + if (f) + { + f->Write(buffer, buffersize); + f->Close(); + } + } +} + void ErrorWindow::OnClose() { Restart = false; @@ -96,7 +137,10 @@ void ErrorWindow::OnGeometryChanged() double y = GetHeight() - 15.0 - ClipboardButton->GetPreferredHeight(); ClipboardButton->SetFrameGeometry(20.0, y, 170.0, ClipboardButton->GetPreferredHeight()); - RestartButton->SetFrameGeometry(GetWidth() - 20.0 - 100.0, y, 100.0, RestartButton->GetPreferredHeight()); + if (RestartButton) + RestartButton->SetFrameGeometry(GetWidth() - 20.0 - 100.0, y, 100.0, RestartButton->GetPreferredHeight()); + else if (SaveReportButton) + SaveReportButton->SetFrameGeometry(GetWidth() - 20.0 - 100.0, y, 100.0, SaveReportButton->GetPreferredHeight()); y -= 20.0; LogView->SetFrameGeometry(Rect::xywh(0.0, 0.0, w, y)); diff --git a/src/common/widgets/errorwindow.h b/src/common/widgets/errorwindow.h index fe5b21029..1cb9b236d 100644 --- a/src/common/widgets/errorwindow.h +++ b/src/common/widgets/errorwindow.h @@ -10,9 +10,9 @@ class Scrollbar; class ErrorWindow : public Widget { public: - static bool ExecModal(const std::string& text, const std::string& log); + static bool ExecModal(const std::string& text, const std::string& log, std::vector minidump = {}); - ErrorWindow(); + ErrorWindow(std::vector minidump); bool Restart = false; @@ -25,11 +25,14 @@ private: void OnClipboardButtonClicked(); void OnRestartButtonClicked(); + void OnSaveReportButtonClicked(); LogViewer* LogView = nullptr; PushButton* ClipboardButton = nullptr; PushButton* RestartButton = nullptr; + PushButton* SaveReportButton = nullptr; + std::vector minidump; std::string clipboardtext; }; diff --git a/src/d_main.cpp b/src/d_main.cpp index 42ce5935b..df6770fe3 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -29,6 +29,8 @@ #ifdef _WIN32 #include +#define WIN32_MEAN_AND_LEAN +#include #endif #if defined(__unix__) || defined(__APPLE__) @@ -117,6 +119,7 @@ #include "screenjob.h" #include "startscreen.h" #include "shiftstate.h" +#include "common/widgets/errorwindow.h" #ifdef __unix__ #include "i_system.h" // for SHARE_DIR @@ -3717,6 +3720,49 @@ static int D_DoomMain_Internal (void) LoadHexFont(wad); // load hex font early so we have it during startup. InitWidgetResources(wad); + if (Args->CheckParm("-showcrashreport")) + { + FString minidumpFilename = Args->GetArg(2); + FString logFilename = Args->GetArg(3); + + FString logText; + { + FileReader fr; + if (fr.OpenFile(logFilename.GetChars())) + { + std::vector data(fr.GetLength() + 1); + if (fr.Read(data.data(), data.size() - 1) == (FileReader::Size)data.size() - 1) + { + logText = data.data(); + } + } + } + std::vector minidump; + { + FileReader fr; + if (fr.OpenFile(minidumpFilename.GetChars())) + { + minidump.resize(fr.GetLength()); + if (fr.Read(minidump.data(), minidump.size()) != (FileReader::Size)minidump.size()) + { + minidump.clear(); + } + } + } + + FString text; + text.Format("%s fatally crashed!", GAMENAME); + ErrorWindow::ExecModal(text.GetChars(), logText.GetChars(), std::move(minidump)); + + // Crash reporter only uses -showcrashreport on Windows at the moment and there seems to be no abstraction available + #ifdef WIN32 + DeleteFile(logFilename.WideString().c_str()); + DeleteFile(minidumpFilename.WideString().c_str()); + #endif + + return 0; + } + C_InitConsole(80*8, 25*8, false); I_DetectOS();