diff --git a/devel/w32dumper/aegisub-w32dumper.vcxproj b/devel/w32dumper/aegisub-w32dumper.vcxproj
new file mode 100644
index 000000000..f164d3644
--- /dev/null
+++ b/devel/w32dumper/aegisub-w32dumper.vcxproj
@@ -0,0 +1,91 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+
+ {343E27E9-303B-4489-BAD7-1DDCD1C9552A}
+ Win32Proj
+ aegisubw32dumper
+
+
+
+ Application
+ true
+ Unicode
+
+
+ Application
+ false
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+ false
+
+
+
+
+
+ Level3
+ Disabled
+ WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)
+
+
+ Windows
+ true
+ comctl32.lib;psapi.lib;dbghelp.lib;%(AdditionalDependencies)
+
+
+
+
+ Level3
+
+
+ MaxSpeed
+ true
+ true
+ WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)
+ MultiThreaded
+
+
+ Windows
+ true
+ true
+ true
+ comctl32.lib;psapi.lib;dbghelp.lib;%(AdditionalDependencies)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/devel/w32dumper/aegisub-w32dumper.vcxproj.filters b/devel/w32dumper/aegisub-w32dumper.vcxproj.filters
new file mode 100644
index 000000000..cd1886511
--- /dev/null
+++ b/devel/w32dumper/aegisub-w32dumper.vcxproj.filters
@@ -0,0 +1,32 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hpp;hxx;hm;inl;inc;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Source Files
+
+
+
+
+ Header Files
+
+
+
+
+ Resource Files
+
+
+
\ No newline at end of file
diff --git a/devel/w32dumper/resource.h b/devel/w32dumper/resource.h
new file mode 100644
index 000000000..ef44b025c
--- /dev/null
+++ b/devel/w32dumper/resource.h
@@ -0,0 +1,19 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by w32dumper.rc
+//
+#define IDD_W32DUMPER 101
+#define IDC_LOGLIST 1001
+#define IDC_SYSLINK1 1002
+#define IDC_DUMPFOLDERLINK 1002
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1003
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/devel/w32dumper/w32dumper.aps b/devel/w32dumper/w32dumper.aps
new file mode 100644
index 000000000..9c01377b5
Binary files /dev/null and b/devel/w32dumper/w32dumper.aps differ
diff --git a/devel/w32dumper/w32dumper.cpp b/devel/w32dumper/w32dumper.cpp
new file mode 100644
index 000000000..eb5f1abbd
--- /dev/null
+++ b/devel/w32dumper/w32dumper.cpp
@@ -0,0 +1,329 @@
+#define WIN32_LEAN_AND_MEAN
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "resource.h"
+
+
+std::wstring CanonicalFileName(std::wstring const &fn)
+{
+ DWORD bufsize = GetLongPathNameW(fn.c_str(), 0, 0);
+ wchar_t *fnbuf = (LPWSTR)malloc(sizeof(*fnbuf)*bufsize);
+ bufsize = GetLongPathNameW(fn.c_str(), fnbuf, bufsize);
+ auto canfn = std::wstring(fnbuf, fnbuf+bufsize);
+ free(fnbuf);
+ return canfn;
+}
+
+std::wstring GetDumpfileFolder()
+{
+ std::wstring dumpfile_folder;
+ wchar_t appdata_folder[MAX_PATH+1] = {0};
+ SHGetFolderPathW(0, CSIDL_APPDATA, 0, SHGFP_TYPE_CURRENT, appdata_folder);
+ dumpfile_folder = std::wstring(appdata_folder);
+ dumpfile_folder += L"\\Aegisub\\";
+ if (CreateDirectoryW(dumpfile_folder.c_str(), 0) == 0 && GetLastError() == ERROR_PATH_NOT_FOUND)
+ return std::wstring(); // nowhere to write, somehow there is no %appdata%
+ dumpfile_folder += L"dumps\\";
+ CreateDirectoryW(dumpfile_folder.c_str(), 0);
+ return dumpfile_folder;
+}
+
+std::wstring IntToWstring(int n)
+{
+ wchar_t buf[16];
+ swprintf_s(buf, L"%d", n);
+ return std::wstring(buf);
+}
+
+
+// DNM = Dumper Notify Message
+#define DNM_COMPLETED (WM_APP + 0)
+#define DNM_ERROR (WM_APP + 1)
+#define DNM_DUMPSTARTED (WM_APP + 2)
+#define DNM_DUMPFINISHED (WM_APP + 3)
+
+
+// Data being passed to the worker thread
+struct DumperThreadData {
+ HWND hwndDlg;
+ HINSTANCE hInstance;
+};
+
+// Miniclass to make sure the dialog gets sent a message when the thread ends,
+// regardless of the reason or manner
+struct EnsureDialogNotifiedOfThreadCompletion {
+ HWND hwnd;
+ EnsureDialogNotifiedOfThreadCompletion(HWND hwnd) : hwnd(hwnd) { }
+ ~EnsureDialogNotifiedOfThreadCompletion()
+ {
+ SendMessageW(hwnd, DNM_COMPLETED, 0, 0);
+ }
+ void error(int code, wchar_t const *message)
+ {
+ SendMessageW(hwnd, DNM_ERROR, (WPARAM)code, (LPARAM)message);
+ }
+};
+
+struct WindowsHandle {
+ HANDLE handle;
+ explicit WindowsHandle(HANDLE handle) : handle(handle) { }
+ ~WindowsHandle() { if (handle != 0) CloseHandle(handle); }
+ operator HANDLE() { return handle; }
+};
+
+// Thread that will actually find and make dumps of Aegisub processes
+void __cdecl dumper_thread(void *data)
+{
+ DumperThreadData *dtd = static_cast(data);
+
+ EnsureDialogNotifiedOfThreadCompletion completion_notify(dtd->hwndDlg);
+
+ // Find Aegisub's install dir based on where we are located
+ std::wstring aegisub_filename_prefix;
+ {
+ DWORD bufsize = MAX_PATH;
+ LPWSTR modfnbuf = (LPWSTR)malloc(sizeof(*modfnbuf)*bufsize);
+ DWORD modfnlen = GetModuleFileNameW(0, modfnbuf, bufsize);
+ if (modfnlen > bufsize)
+ {
+ bufsize = modfnlen;
+ modfnbuf = (LPWSTR)realloc(modfnbuf, sizeof(*modfnbuf)*bufsize);
+ modfnlen = GetModuleFileNameW(0, modfnbuf, bufsize);
+ }
+ aegisub_filename_prefix = CanonicalFileName(std::wstring(modfnbuf, modfnbuf+modfnlen));
+ free(modfnbuf);
+ }
+ {
+ // Chomp it at the last backslash and append "aegisub"
+ size_t backslash_pos = aegisub_filename_prefix.rfind(L'\\');
+ if (backslash_pos == std::wstring::npos)
+ {
+ completion_notify.error(2, L"Something is wrong with the installation path");
+ return;
+ }
+ aegisub_filename_prefix.erase(backslash_pos+1);
+ }
+
+ // Figure out where we should be writing dump files to
+ std::wstring dumpfile_folder = GetDumpfileFolder();
+ if (dumpfile_folder.empty())
+ {
+ completion_notify.error(3, L"Could not access folder for writing dump files to");
+ return;
+ }
+
+ // Get pids of all processes
+ std::vector process_ids;
+ {
+ size_t pidlist_size = 128;
+ size_t pidlist_count = 0;
+ do {
+ process_ids.resize(pidlist_size);
+ DWORD bytes_returned = 0;
+ if (EnumProcesses(&process_ids[0], sizeof(DWORD)*pidlist_size, &bytes_returned) == 0)
+ {
+ completion_notify.error(4, L"An error occurred trying to enumerate processes on the system");
+ return;
+ }
+ pidlist_count = bytes_returned / sizeof(DWORD);
+ } while (pidlist_count == pidlist_size);
+ process_ids.resize(pidlist_count);
+ }
+
+ // Build a string useful for making filenames more unique
+ std::wstring timestring;
+ {
+ time_t t = time(0);
+ tm curtime;
+ localtime_s(&curtime, &t);
+ wchar_t fmttime[20] = {0};
+ swprintf_s(fmttime, L"%4d%02d%02d-%02d%02d%02d",
+ curtime.tm_year+1900, curtime.tm_mon, curtime.tm_mday,
+ curtime.tm_hour, curtime.tm_min, curtime.tm_sec);
+ timestring = std::wstring(fmttime);
+ }
+
+ // Check each process for being interesting (i.e. probably an Aegisub process)
+ const DWORD mypid = GetCurrentProcessId();
+ for (auto ppid = process_ids.begin(); ppid != process_ids.end(); ++ppid)
+ {
+ if (*ppid == mypid)
+ continue;
+
+ WindowsHandle proc(OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, FALSE, *ppid));
+ if (proc == 0)
+ continue;
+
+ // Get process name
+ std::wstring procfn;
+ {
+ DWORD bufsize = MAX_PATH;
+ LPWSTR modfnbuf = (LPWSTR)malloc(sizeof(*modfnbuf)*bufsize);
+ DWORD modfnlen = GetModuleFileNameExW(proc, 0, modfnbuf, bufsize);
+ if (modfnlen == 0)
+ {
+ DWORD err = GetLastError();
+ continue;
+ }
+ if (modfnlen > bufsize)
+ {
+ bufsize = modfnlen;
+ modfnbuf = (LPWSTR)realloc(modfnbuf, sizeof(*modfnbuf)*bufsize);
+ modfnlen = GetModuleFileNameExW(proc, 0, modfnbuf, bufsize);
+ }
+ procfn = CanonicalFileName(std::wstring(modfnbuf, modfnbuf+modfnlen));
+ free(modfnbuf);
+ }
+
+ // Check it's relevant
+ if (procfn.find(aegisub_filename_prefix) != 0)
+ continue;
+
+ // Pick a filename to write
+ std::wstring procfn_basename;
+ {
+ // Chop off everything up to and including last backslash
+ size_t pos = procfn.rfind(L'\\');
+ if (pos != std::wstring::npos)
+ procfn_basename = procfn.substr(pos+1);
+ else
+ procfn_basename = procfn;
+ }
+
+ // Tell about our exploits
+ SendMessageW(dtd->hwndDlg, DNM_DUMPSTARTED, (WPARAM)*ppid, (LPARAM)procfn_basename.c_str());
+
+ std::wstring dumpfile_name =
+ dumpfile_folder +
+ procfn_basename + L'-' +
+ IntToWstring(*ppid) + L'-' +
+ timestring +
+ L".dmp";
+
+ WindowsHandle dumpfile(CreateFileW(dumpfile_name.c_str(), GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0));
+
+ MiniDumpWriteDump(
+ proc,
+ *ppid,
+ dumpfile,
+ MINIDUMP_TYPE(MiniDumpWithThreadInfo|MiniDumpIgnoreInaccessibleMemory|MiniDumpWithIndirectlyReferencedMemory),
+ 0, 0, 0);
+
+ SendMessageW(dtd->hwndDlg, DNM_DUMPFINISHED, 0, (LPARAM)dumpfile_name.c_str());
+ }
+}
+
+
+int numdumps = 0;
+int numerrors = 0;
+
+void AddStringToListbox(HWND hwndDlg, std::wstring const &str)
+{
+ SendDlgItemMessageW(hwndDlg, IDC_LOGLIST, LB_ADDSTRING, 0, (LPARAM)str.c_str());
+}
+
+INT_PTR CALLBACK dialog_msghandler(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case DNM_COMPLETED:
+ EnableWindow(GetDlgItem(hwndDlg, IDCLOSE), TRUE);
+ if (numerrors > 0)
+ AddStringToListbox(hwndDlg, L"Finished with errors.");
+ else if (numdumps > 0)
+ AddStringToListbox(hwndDlg, std::wstring(L"Completed ") + IntToWstring(numdumps) + (numdumps>1?L" minidumps.":L" minidump."));
+ else
+ AddStringToListbox(hwndDlg, L"Finished, found no processes to dump.");
+ break;
+
+ case DNM_ERROR:
+ numerrors += 1;
+ AddStringToListbox(hwndDlg, std::wstring(L"An error occurred: ") + (wchar_t const *)lParam);
+ break;
+
+ case DNM_DUMPSTARTED:
+ numdumps += 1;
+ AddStringToListbox(hwndDlg, std::wstring(L"Beginning dump of pid ") + IntToWstring(wParam) + L" (" + (wchar_t const *)lParam + L")");
+ break;
+
+ case DNM_DUMPFINISHED:
+ AddStringToListbox(hwndDlg, std::wstring(L" Finished dump: ") + (wchar_t const *)lParam);
+ break;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDCLOSE:
+ if (HIWORD(wParam) == BN_CLICKED)
+ PostQuitMessage(0);
+ break;
+ }
+ break;
+
+ case WM_NOTIFY:
+ {
+ NMHDR &nm = *(NMHDR*)lParam;
+ if (nm.idFrom == IDC_DUMPFOLDERLINK && (nm.code == NM_CLICK || nm.code == NM_RETURN))
+ {
+ std::wstring dumpfile_folder = GetDumpfileFolder();
+ ShellExecuteW(hwndDlg, L"open", dumpfile_folder.c_str(), 0, 0, SW_SHOWNORMAL);
+ }
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+
+#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' ""version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
+
+int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
+{
+ CoInitializeEx(0, COINIT_MULTITHREADED);
+ INITCOMMONCONTROLSEX iccx = {
+ sizeof(INITCOMMONCONTROLSEX),
+ ICC_LINK_CLASS|ICC_STANDARD_CLASSES
+ };
+ if (InitCommonControlsEx(&iccx) == FALSE)
+ ExitProcess(1);
+
+ HWND hwndDlg = CreateDialogW(hInstance, MAKEINTRESOURCE(IDD_W32DUMPER), 0, dialog_msghandler);
+ if (hwndDlg == 0)
+ ExitProcess(2);
+
+ DumperThreadData dtd = { hwndDlg, hInstance };
+ uintptr_t dumper_thread_handle = _beginthread(dumper_thread, 0, &dtd);
+ ShowWindow(hwndDlg, SW_SHOWNORMAL);
+
+ MSG msg;
+ BOOL gmret;
+ while ((gmret = GetMessageW(&msg, 0, 0, 0)) != 0)
+ {
+ if (gmret == -1)
+ {
+ ExitProcess(3);
+ }
+ else if (!IsDialogMessageW(hwndDlg, &msg))
+ {
+ TranslateMessage(&msg);
+ DispatchMessageW(&msg);
+ }
+ }
+
+ CoUninitialize();
+
+ return gmret;
+}
diff --git a/devel/w32dumper/w32dumper.rc b/devel/w32dumper/w32dumper.rc
new file mode 100644
index 000000000..ff1e67cbe
--- /dev/null
+++ b/devel/w32dumper/w32dumper.rc
@@ -0,0 +1,105 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_W32DUMPER DIALOGEX 0, 0, 317, 118
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CENTER | WS_CAPTION
+CAPTION "Aegisub crash logger"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ DEFPUSHBUTTON "Close",IDCLOSE,260,97,50,14,WS_DISABLED
+ LISTBOX IDC_LOGLIST,7,7,303,85,NOT LBS_NOTIFY | LBS_SORT | LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
+ CONTROL "Open folder containing dump files",IDC_DUMPFOLDERLINK,
+ "SysLink",WS_TABSTOP,7,99,195,12
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_W32DUMPER, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 310
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 111
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+#endif // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United Kingdom) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""afxres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+#endif // English (United Kingdom) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+