diff --git a/build/Aegisub/Aegisub.vcxproj b/build/Aegisub/Aegisub.vcxproj
index ebd159711..86b816e76 100644
--- a/build/Aegisub/Aegisub.vcxproj
+++ b/build/Aegisub/Aegisub.vcxproj
@@ -348,7 +348,10 @@
-
+
+ true
+
+
diff --git a/build/Aegisub/Aegisub.vcxproj.filters b/build/Aegisub/Aegisub.vcxproj.filters
index bb8fba4db..29ce46093 100644
--- a/build/Aegisub/Aegisub.vcxproj.filters
+++ b/build/Aegisub/Aegisub.vcxproj.filters
@@ -1238,6 +1238,9 @@
Utilities\Logging
+
+ Utilities\Logging
+
diff --git a/src/crash_writer_minidump.cpp b/src/crash_writer_minidump.cpp
new file mode 100644
index 000000000..da07e5a01
--- /dev/null
+++ b/src/crash_writer_minidump.cpp
@@ -0,0 +1,153 @@
+// Copyright (c) 2014, Thomas Goyne
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+#include "config.h"
+
+#include "crash_writer.h"
+
+#include "version.h"
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+extern EXCEPTION_POINTERS *wxGlobalSEInformation;
+
+namespace {
+wchar_t crash_dump_path[MAX_PATH];
+agi::fs::path crashlog_path;
+
+using MiniDumpWriteDump = BOOL(WINAPI *)(
+ HANDLE hProcess,
+ DWORD dwPid,
+ HANDLE hFile,
+ MINIDUMP_TYPE DumpType,
+ CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
+ CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
+ CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
+
+struct dump_thread_state {
+ std::mutex start_mutex;
+ std::condition_variable start_cv;
+
+ std::atomic exit = false;
+ EXCEPTION_POINTERS *ep = nullptr;
+ DWORD thread_id = 0;
+
+ // Must be last so everything else is initialized before it
+ std::thread thread;
+
+ dump_thread_state() : thread([&] { main(); }) { }
+
+ void main() {
+ auto module = LoadLibrary(L"dbghelp.dll");
+ if (!module) return;
+
+ auto fn = reinterpret_cast(GetProcAddress(module, "MiniDumpWriteDump"));
+ if (!fn) {
+ FreeLibrary(module);
+ return;
+ }
+
+ std::unique_lock lock(start_mutex);
+ start_cv.wait(lock, [&] { return ep || exit; });
+ if (ep)
+ write_dump(fn);
+ FreeLibrary(module);
+ }
+
+ void write_dump(MiniDumpWriteDump fn) {
+ auto file = CreateFile(crash_dump_path,
+ GENERIC_WRITE,
+ 0, // no sharing
+ nullptr,
+ CREATE_NEW,
+ FILE_ATTRIBUTE_NORMAL,
+ nullptr);
+ if (file == INVALID_HANDLE_VALUE) return;
+
+ MINIDUMP_EXCEPTION_INFORMATION info;
+ info.ThreadId = thread_id;
+ info.ExceptionPointers = ep;
+ info.ClientPointers = FALSE;
+
+ fn(GetCurrentProcess(), GetCurrentProcessId(), file, MiniDumpNormal, &info, nullptr, nullptr);
+
+ CloseHandle(file);
+ }
+};
+
+std::unique_ptr dump_thread;
+}
+
+namespace crash_writer {
+void Initialize(agi::fs::path const& path) {
+ crashlog_path = path / "crashlog.txt";
+
+ auto dump_path = path / "crashdumps";
+ agi::fs::CreateDirectory(dump_path);
+
+ const auto path_str = (dump_path / GetVersionNumber()).wstring();
+ wcscpy_s(crash_dump_path, path_str.c_str());
+ auto len = path_str.size();
+
+ const auto t = time(nullptr);
+ struct tm tm;
+ localtime_s(&tm, &t);
+
+ len += wcsftime(crash_dump_path + len, MAX_PATH - len, L"-%Y-%m-%d-%H-%M-%S-", &tm);
+ len += swprintf_s(crash_dump_path + len, MAX_PATH - len, L"%d", GetCurrentProcessId());
+ wcscpy_s(crash_dump_path + len, MAX_PATH - len, L".dmp");
+
+ if (!dump_thread)
+ dump_thread = agi::util::make_unique();
+}
+
+void Cleanup() {
+ dump_thread->exit = true;
+ dump_thread->start_cv.notify_all();
+ dump_thread->thread.join();
+ dump_thread.reset();
+}
+
+void Write() {
+ dump_thread->ep = wxGlobalSEInformation;
+ dump_thread->thread_id = GetCurrentThreadId();
+ dump_thread->start_cv.notify_all();
+ dump_thread->thread.join();
+ dump_thread.reset();
+}
+
+void Write(std::string const& error) {
+ boost::filesystem::ofstream file(crashlog_path, std::ios::app);
+ if (file.is_open()) {
+ file << agi::util::strftime("--- %y-%m-%d %H:%M:%S ------------------\n");
+ file << boost::format("VER - %s\n") % GetAegisubLongVersionString();
+ file << boost::format("EXC - Aegisub has crashed with unhandled exception \"%s\".\n") % error;
+ file << "----------------------------------------\n\n";
+ file.close();
+ }
+}
+}
diff --git a/src/main.cpp b/src/main.cpp
index 05a7672b9..3437f1d7b 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -311,6 +311,7 @@ int AegisubApp::OnExit() {
// Keep this last!
delete agi::log::log;
+ crash_writer::Cleanup();
return wxApp::OnExit();
}