From 0f080d1f448a16e50b0f19425bf2865724bc6696 Mon Sep 17 00:00:00 2001 From: baldurk Date: Sun, 8 Jun 2014 11:00:41 +0100 Subject: [PATCH] Cleaned up renderdoccmd, split out necessary bits to OS-specific files --- renderdoc/core/crash_handler.h | 2 +- renderdoc/os/win32/win32_process.cpp | 2 +- renderdoccmd/Makefile | 2 +- renderdoccmd/renderdoccmd.cpp | 762 ++++-------------- renderdoccmd/renderdoccmd.vcxproj | 13 +- renderdoccmd/renderdoccmd.vcxproj.filters | 3 +- ...ux_specific.cpp => renderdoccmd_linux.cpp} | 59 +- renderdoccmd/renderdoccmd_win32.cpp | 494 ++++++++++++ 8 files changed, 713 insertions(+), 624 deletions(-) rename renderdoccmd/{linux_specific.cpp => renderdoccmd_linux.cpp} (60%) create mode 100644 renderdoccmd/renderdoccmd_win32.cpp diff --git a/renderdoc/core/crash_handler.h b/renderdoc/core/crash_handler.h index 1a8af94fe..76ceae533 100644 --- a/renderdoc/core/crash_handler.h +++ b/renderdoc/core/crash_handler.h @@ -92,7 +92,7 @@ class CrashHandler : public ICrashHandler wstring cmdline = L"\""; cmdline += radpath; - cmdline += L"/renderdoccmd.exe\" crashhandle"; + cmdline += L"/renderdoccmd.exe\" --crashhandle"; wchar_t *paramsAlloc = new wchar_t[512]; diff --git a/renderdoc/os/win32/win32_process.cpp b/renderdoc/os/win32/win32_process.cpp index c8c57d1df..4e5d5048d 100644 --- a/renderdoc/os/win32/win32_process.cpp +++ b/renderdoc/os/win32/win32_process.cpp @@ -249,7 +249,7 @@ uint32_t Process::InjectIntoProcess(uint32_t pid, const wchar_t *logfile, const string optstr = opts->ToString(); - _snwprintf_s(paramsAlloc, 255, 255, L"\"%ls\" -cap32for64 %d \"%ls\" \"%hs\"", renderdocPath, pid, logfile, optstr.c_str()); + _snwprintf_s(paramsAlloc, 255, 255, L"\"%ls\" --cap32for64 %d \"%ls\" \"%hs\"", renderdocPath, pid, logfile, optstr.c_str()); BOOL retValue = CreateProcessW(NULL, paramsAlloc, &pSec, &tSec, false, CREATE_SUSPENDED, NULL, NULL, &si, &pi); diff --git a/renderdoccmd/Makefile b/renderdoccmd/Makefile index c31957f03..2338a1b30 100644 --- a/renderdoccmd/Makefile +++ b/renderdoccmd/Makefile @@ -8,7 +8,7 @@ MACROS=-DLINUX \ CFLAGS=-c -Wall -Werror -fPIC $(MACROS) -I../renderdoc/ CPPFLAGS=-std=c++11 -g -Wno-unused -Wno-unknown-pragmas -Wno-reorder LDFLAGS=-L../renderdoc -lrenderdoc -lGL -OBJECTS=linux_specific.o +OBJECTS=renderdoccmd.o renderdoccmd_linux.o all: bin/renderdoccmd diff --git a/renderdoccmd/renderdoccmd.cpp b/renderdoccmd/renderdoccmd.cpp index 152bc822a..e45676a91 100644 --- a/renderdoccmd/renderdoccmd.cpp +++ b/renderdoccmd/renderdoccmd.cpp @@ -22,489 +22,30 @@ * THE SOFTWARE. ******************************************************************************/ - -#include -#include -#include -#include #include -#include -#include - -#include "resource.h" - -// breakpad -#include "common/windows/http_upload.h" -#include "client/windows/crash_generation/client_info.h" -#include "client/windows/crash_generation/crash_generation_server.h" - -#include "miniz.h" +#include using std::string; using std::wstring; -using std::vector; -using google_breakpad::ClientInfo; -using google_breakpad::CrashGenerationServer; -bool exitServer = false; +// defined in *_specific.cpp +void DisplayRendererPreview(ReplayRenderer *renderer); +wstring GetUsername(); -static HINSTANCE CrashHandlerInst = 0; -static HWND CrashHandlerWnd = 0; - -bool uploadReport = false; -bool uploadDump = false; -bool uploadLog = false; -string reproSteps = ""; - -wstring dump = L""; -vector customInfo; -wstring logpath = L""; - - -INT_PTR CALLBACK CrashHandlerProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +int renderdoccmd(int argc, wchar_t **argv) { - switch (message) - { - case WM_INITDIALOG: - { - HANDLE hIcon = LoadImage(CrashHandlerInst, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0); - - if(hIcon) - { - SendMessage(hDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); - SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon); - } - - SetDlgItemTextW(hDlg, IDC_WELCOMETEXT, - L"RenderDoc has encountered an unhandled exception or other similar unrecoverable error.\n\n" \ - L"If you had captured but not saved a logfile it should still be available in %TEMP% and will not be deleted," \ - L"you can try loading it again.\n\n" \ - L"A minidump has been created and the RenderDoc diagnostic log (NOT any capture logfile) is available if you would like " \ - L"to send them back to be analysed. The path for both is found below if you would like to inspect their contents and censor as appropriate.\n\n" \ - L"Neither contains any significant private information, the minidump has some internal states and local memory at the time of the " \ - L"crash & thread stacks, etc. The diagnostic log contains diagnostic messages like warnings and errors.\n\n" \ - L"The only other information sent is the version of RenderDoc, C# exception callstack, and any notes you include.\n\n" \ - L"Any repro steps or notes would be helpful to include with the report. If you'd like to be contacted about the bug " \ - L"e.g. for updates about its status just include your email & name. Thank you!\n\n" \ - L"Baldur (renderdoc@crytek.com)"); - - SetDlgItemTextW(hDlg, IDC_DUMPPATH, dump.c_str()); - SetDlgItemTextW(hDlg, IDC_LOGPATH, logpath.c_str()); - - CheckDlgButton(hDlg, IDC_SENDDUMP, BST_CHECKED); - CheckDlgButton(hDlg, IDC_SENDLOG, BST_CHECKED); - } - - case WM_SHOWWINDOW: - { - - { - RECT r; - GetClientRect(hDlg, &r); - - int xPos = (GetSystemMetrics(SM_CXSCREEN) - r.right)/2; - int yPos = (GetSystemMetrics(SM_CYSCREEN) - r.bottom)/2; - - SetWindowPos(hDlg, NULL, xPos, yPos, 0, 0, SWP_NOZORDER | SWP_NOSIZE); - } - - return (INT_PTR)TRUE; - } - - case WM_COMMAND: - { - int ID = LOWORD(wParam); - - if(ID == IDC_DONTSEND) - { - EndDialog(hDlg, 0); - return (INT_PTR)TRUE; - } - else if(ID == IDC_SEND) - { - uploadReport = true; - uploadDump = (IsDlgButtonChecked(hDlg, IDC_SENDDUMP) != 0); - uploadLog = (IsDlgButtonChecked(hDlg, IDC_SENDLOG) != 0); - - char notes[4097] = {0}; - - GetDlgItemTextA(hDlg, IDC_NAME, notes, 4096); - notes[4096] = 0; - - reproSteps = "Name: "; - reproSteps += notes; - reproSteps += "\n"; - - memset(notes, 0, 4096); - GetDlgItemTextA(hDlg, IDC_EMAIL, notes, 4096); - notes[4096] = 0; - - reproSteps += "Email: "; - reproSteps += notes; - reproSteps += "\n\n"; - - memset(notes, 0, 4096); - GetDlgItemTextA(hDlg, IDC_REPRO, notes, 4096); - notes[4096] = 0; - - reproSteps += notes; - - EndDialog(hDlg, 0); - return (INT_PTR)TRUE; - } - } - break; - - case WM_QUIT: - case WM_DESTROY: - case WM_CLOSE: - { - EndDialog(hDlg, 0); - return (INT_PTR)TRUE; - } - break; - } - return (INT_PTR)FALSE; -} - -static void _cdecl OnClientCrashed(void* context, const ClientInfo* client_info, const wstring* dump_path) -{ - if(dump_path) - { - dump = *dump_path; - - google_breakpad::CustomClientInfo custom = client_info->GetCustomInfo(); - - for(size_t i=0; i < custom.count; i++) - customInfo.push_back(custom.entries[i]); - } - - exitServer = true; -} - -static void _cdecl OnClientExited(void* context, const ClientInfo* client_info) -{ - exitServer = true; -} - -LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - if(msg == WM_CLOSE) { DestroyWindow(hwnd); return 0; } - if(msg == WM_DESTROY) { PostQuitMessage(0); return 0; } - return DefWindowProc(hwnd, msg, wParam, lParam); -} - -void DisplayRendererPreview(ReplayRenderer *renderer, HINSTANCE hInstance) -{ - if(renderer == NULL) return; - - HWND wnd = 0; - - wnd = CreateWindowEx(WS_EX_CLIENTEDGE, L"renderdoccmd", L"renderdoccmd", WS_OVERLAPPEDWINDOW, - CW_USEDEFAULT, CW_USEDEFAULT, 1280, 720, - NULL, NULL, hInstance, NULL); - - if(wnd == NULL) - { - return; - } - - ShowWindow(wnd, SW_SHOW); - UpdateWindow(wnd); - - rdctype::array texs; - ReplayRenderer_GetTextures(renderer, &texs); - - ReplayOutput *out = ReplayRenderer_CreateOutput(renderer, wnd); - - ReplayRenderer_SetFrameEvent(renderer, 0, 10000000); - - OutputConfig c; - c.m_Type = eOutputType_TexDisplay; - - ReplayOutput_SetOutputConfig(out, c); - - for(int32_t i=0; i < texs.count; i++) - { - wstring name(texs[i].name.elems, texs[i].name.elems+texs[i].name.count); - if(name.find(L"Swap") != wstring::npos) - { - TextureDisplay d; - d.texid = texs[i].ID; - d.mip = 0; - d.overlay = eTexOverlay_None; - d.CustomShader = ResourceId(); - d.HDRMul = -1.0f; - d.rangemin = 0.0f; - d.rangemax = 1.0f; - d.scale = 1.0f; - d.offx = 0.0f; - d.offy = 0.0f; - d.sliceFace = 0; - d.rawoutput = false; - d.Red = d.Green = d.Blue = true; - d.Alpha = false; - - ReplayOutput_SetTextureDisplay(out, d); - - break; - } - } - - MSG msg; - ZeroMemory(&msg, sizeof(msg)); - while(true) - { - // Check to see if any messages are waiting in the queue - while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) - { - // Translate the message and dispatch it to WindowProc() - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - // If the message is WM_QUIT, exit the while loop - if(msg.message == WM_QUIT) - break; - - ReplayRenderer_SetFrameEvent(renderer, 0, 10000000+rand()%1000); - - ReplayOutput_SetOutputConfig(out, c); - - ReplayOutput_Display(out); - - Sleep(40); - } -} - -int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd ) -{ - LPWSTR *argv; - int argc; - - argv = CommandLineToArgvW(GetCommandLine(), &argc); - - WNDCLASSEX wc; - wc.cbSize = sizeof(WNDCLASSEX); - wc.style = 0; - wc.lpfnWndProc = WndProc; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0; - wc.hInstance = hInstance; - wc.hIcon = LoadIcon(NULL, MAKEINTRESOURCE(IDI_ICON)); - wc.hCursor = LoadCursor(NULL, IDC_ARROW); - wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); - wc.lpszMenuName = NULL; - wc.lpszClassName = L"renderdoccmd"; - wc.hIconSm = LoadIcon(NULL, MAKEINTRESOURCE(IDI_ICON)); - - if(!RegisterClassEx(&wc)) - { - return 1; - } - - CrashGenerationServer *crashServer = NULL; - - if(argc == 2 && !_wcsicmp(argv[1], L"crashhandle")) - { - wchar_t tempPath[MAX_PATH] = {0}; - GetTempPathW(MAX_PATH-1, tempPath); - - Sleep(100); - - wstring dumpFolder = tempPath; - dumpFolder += L"RenderDocDumps"; - - CreateDirectoryW(dumpFolder.c_str(), NULL); - - crashServer = new CrashGenerationServer(L"\\\\.\\pipe\\RenderDocBreakpadServer", - NULL, NULL, NULL, OnClientCrashed, NULL, - OnClientExited, NULL, NULL, NULL, true, - &dumpFolder); - - if (!crashServer->Start()) { - delete crashServer; - crashServer = NULL; - return 1; - } - - CrashHandlerInst = hInstance; - - CrashHandlerWnd = CreateWindowEx(WS_EX_CLIENTEDGE, L"renderdoccmd", L"renderdoccmd", WS_OVERLAPPEDWINDOW, - CW_USEDEFAULT, CW_USEDEFAULT, 10, 10, - NULL, NULL, hInstance, NULL); - - HANDLE hIcon = LoadImage(CrashHandlerInst, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0); - - if(hIcon) - { - SendMessage(CrashHandlerWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); - SendMessage(CrashHandlerWnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon); - } - - ShowWindow(CrashHandlerWnd, SW_HIDE); - - HANDLE readyEvent = CreateEventA(NULL, TRUE, FALSE, "RENDERDOC_CRASHHANDLE"); - - if(readyEvent != NULL) - { - SetEvent(readyEvent); - - CloseHandle(readyEvent); - } - - MSG msg; - ZeroMemory(&msg, sizeof(msg)); - while(!exitServer) - { - // Check to see if any messages are waiting in the queue - while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) - { - // Translate the message and dispatch it to WindowProc() - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - // If the message is WM_QUIT, exit the while loop - if(msg.message == WM_QUIT) - break; - - Sleep(100); - } - - delete crashServer; - crashServer = NULL; - - if(!dump.empty()) - { - logpath = L""; - - string report = ""; - - for(size_t i=0; i < customInfo.size(); i++) - { - wstring name = customInfo[i].name; - wstring val = customInfo[i].value; - - if(name == L"logpath") - { - logpath = val; - } - else if(name == L"ptime") - { - // breakpad uptime, ignore. - } - else - { - report += string(name.begin(), name.end()) + ": " + string(val.begin(), val.end()) + "\n"; - } - } - - DialogBox(CrashHandlerInst, MAKEINTRESOURCE(IDD_CRASH_HANDLER), CrashHandlerWnd, (DLGPROC)CrashHandlerProc); - - report += "\n\nRepro steps/Notes:\n\n" + reproSteps; - - { - FILE *f = NULL; - _wfopen_s(&f, logpath.c_str(), L"r"); - if(f) - { - fseek(f, 0, SEEK_END); - long filesize = ftell(f); - fseek(f, 0, SEEK_SET); - - if(filesize > 10) - { - char *error_log = new char[filesize+1]; - memset(error_log, 0, filesize+1); - - fread(error_log, 1, filesize, f); - - char *managed_callstack = strstr(error_log, "--- Begin C# Exception Data ---"); - if(managed_callstack) - { - report += managed_callstack; - report += "\n\n"; - } - - delete[] error_log; - } - - fclose(f); - } - } - - if(uploadReport) - { - mz_zip_archive zip; - ZeroMemory(&zip, sizeof(zip)); - - wstring destzip = dumpFolder + L"\\report.zip"; - - DeleteFileW(destzip.c_str()); - - mz_zip_writer_init_wfile(&zip, destzip.c_str(), 0); - mz_zip_writer_add_mem(&zip, "report.txt", report.c_str(), report.length(), MZ_BEST_COMPRESSION); - - if(uploadDump && !dump.empty()) - mz_zip_writer_add_wfile(&zip, "minidump.dmp", dump.c_str(), NULL, 0, MZ_BEST_COMPRESSION); - - if(uploadLog && !logpath.empty()) - mz_zip_writer_add_wfile(&zip, "error.log", logpath.c_str(), NULL, 0, MZ_BEST_COMPRESSION); - - mz_zip_writer_finalize_archive(&zip); - mz_zip_writer_end(&zip); - - int timeout = 10000; - wstring body = L""; - int code = 0; - - std::map params; - - google_breakpad::HTTPUpload::SendRequest(L"http://renderdoc.org/bugsubmit", params, - dumpFolder + L"\\report.zip", L"report", &timeout, &body, &code); - - DeleteFileW(destzip.c_str()); - } - } - - if(!dump.empty()) - DeleteFileW(dump.c_str()); - - if(!logpath.empty()) - DeleteFileW(logpath.c_str()); - - return 0; - } - - HMODULE renderdoc = LoadLibrary(_T("renderdoc.dll")); - - if(!renderdoc) - { - OutputDebugString(_T("Couldn't load library!")); - return 1; - } - CaptureOptions opts; opts.AllowFullscreen = false; opts.AllowVSync = false; opts.DelayForDebugger = 5; opts.HookIntoChildren = true; - - if(argc == 2) + + if(argc >= 2) { - // if we were given an exe, inject into it - if(wcsstr(argv[1], L".exe") != NULL) + // fall through and print usage + if(!_wcsicmp(argv[1], L"--help") || !_wcsicmp(argv[1], L"-h")) { - uint32_t ident = RENDERDOC_ExecuteAndInject(argv[1], NULL, NULL, NULL, &opts, false); - - if(ident == 0) - printf("Failed to create & inject\n"); - else - printf("Created & injected as %d\n", ident); - - return ident; } // if we were given a logfile, load it and continually replay it. else if(wcsstr(argv[1], L".rdc") != NULL) @@ -514,166 +55,167 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLi auto status = RENDERDOC_CreateReplayRenderer(argv[1], &progress, &renderer); if(renderer && status == eReplayCreate_Success) - DisplayRendererPreview(renderer, hInstance); + DisplayRendererPreview(renderer); delete renderer; return 0; } - else if(wcsstr(argv[1], L"-replayhost") != NULL) + // replay a logfile + else if(!_wcsicmp(argv[1], L"--replay") || !_wcsicmp(argv[1], L"-r")) + { + if(argc >= 3) + { + float progress = 0.0f; + ReplayRenderer *renderer = NULL; + auto status = RENDERDOC_CreateReplayRenderer(argv[2], &progress, &renderer); + + if(renderer && status == eReplayCreate_Success) + DisplayRendererPreview(renderer); + + delete renderer; + return 0; + } + else + { + fprintf(stderr, "Not enough parameters to --replay"); + } + } +#ifdef WIN32 + // if we were given an executable on windows, inject into it + // can't do this on other platforms as there's no nice extension + // and we don't want to just launch any single parameter in case it's + // -h or -help or some other guess/typo + else if(wcsstr(argv[1], L".exe") != NULL) + { + uint32_t ident = RENDERDOC_ExecuteAndInject(argv[1], NULL, NULL, NULL, &opts, false); + + if(ident == 0) + fprintf(stderr, "Failed to create & inject\n"); + else + fprintf(stderr, "Created & injected as %d\n", ident); + + return ident; + } +#endif + // capture a program with default capture options + else if(!_wcsicmp(argv[1], L"--capture") || !_wcsicmp(argv[1], L"-c")) + { + if(argc >= 3) + { + uint32_t ident = RENDERDOC_ExecuteAndInject(argv[2], NULL, NULL, NULL, &opts, false); + + if(ident == 0) + fprintf(stderr, "Failed to create & inject to '%ls'\n", argv[2]); + else + fprintf(stderr, "Created & injected '%ls' as %d\n", argv[2], ident); + + return ident; + } + else + { + fprintf(stderr, "Not enough parameters to --capture"); + } + } + // inject into a running process with default capture options + else if(!_wcsicmp(argv[1], L"--inject") || !_wcsicmp(argv[1], L"-i")) + { + if(argc >= 3) + { + wchar_t *pid = argv[2]; + while(*pid == L'"' || iswspace(*pid)) pid++; + + uint32_t pidNum = (uint32_t)_wtoi(pid); + + uint32_t ident = RENDERDOC_InjectIntoProcess(pidNum, NULL, &opts, false); + + if(ident == 0) + printf("Failed to inject to %u\n", pid); + else + printf("Injected to %u as %d\n", pid, ident); + + return ident; + } + else + { + fprintf(stderr, "Not enough parameters to --inject"); + } + } + // spawn remote replay host + else if(!_wcsicmp(argv[1], L"--replayhost") || !_wcsicmp(argv[1], L"-rh")) { RENDERDOC_SpawnReplayHost(NULL); return 1; } - } - else if(argc == 3) - { - if(!_wcsicmp(argv[1], L"-inject")) + // replay a logfile over the network on a remote host + else if(!_wcsicmp(argv[1], L"--remotereplay") || !_wcsicmp(argv[1], L"-rr")) { - wchar_t *pid = argv[2]; - while(*pid == L'"' || iswspace(*pid)) pid++; - - DWORD pidNum = (DWORD)_wtoi(pid); - - uint32_t ident = RENDERDOC_InjectIntoProcess(pidNum, NULL, &opts, false); - - if(ident == 0) - printf("Failed to inject\n"); - else - printf("Injected as %d\n", ident); - - return ident; - } - else - { - uint32_t ident = RENDERDOC_ExecuteAndInject(argv[1], NULL, NULL, argv[2], &opts, false); - - if(ident == 0) - printf("Failed to create & inject\n"); - else - printf("Created & injected as %d\n", ident); - - return ident; - } - } - else if(argc == 4) - { - if(argc == 4 && wcsstr(argv[1], L"-replay") != NULL) - { - RemoteRenderer *remote = NULL; - auto status = RENDERDOC_CreateRemoteReplayConnection(argv[2], &remote); - - if(remote == NULL || status != eReplayCreate_Success) - return 1; - - float progress = 0.0f; - - ReplayRenderer *renderer = NULL; - status = RemoteRenderer_CreateProxyRenderer(remote, 0, argv[3], &progress, &renderer); - - if(renderer && status == eReplayCreate_Success) - DisplayRendererPreview(renderer, hInstance); - - RemoteRenderer_Shutdown(remote); - return 0; - } - } - else if(argc == 5) - { - if(!_wcsicmp(argv[1], L"-cap32for64")) - { - wchar_t *pid = argv[2]; - while(*pid == L'"' || iswspace(*pid)) pid++; - - DWORD pidNum = (DWORD)_wtoi(pid); - - wchar_t *log = argv[3]; - - CaptureOptions cmdopts; - - string optstring(&argv[4][0], &argv[4][0] + wcslen(argv[4])); - - cmdopts.FromString(optstring); - - return RENDERDOC_InjectIntoProcess(pidNum, log, &cmdopts, false); - } - else if(!_wcsicmp(argv[1], L"-remotecontrol")) - { - wchar_t *host = argv[2]; - wchar_t *ident = argv[3]; - while(*ident == L'"' || iswspace(*ident)) ident++; - bool force = argv[4][0] != '0'; - - DWORD identNum = (DWORD)_wtoi(ident); - - wchar_t username[256] = {0}; - DWORD usersize = 255; - GetEnvironmentVariableW(L"renderdoc_username", username, usersize); - - RemoteAccess *access = RENDERDOC_CreateRemoteAccessConnection(host, identNum, username, force); - - if(access == NULL) + if(argc >= 4) { - printf("Failed to connect\n"); + RemoteRenderer *remote = NULL; + auto status = RENDERDOC_CreateRemoteReplayConnection(argv[2], &remote); + + if(remote == NULL || status != eReplayCreate_Success) + return 1; + + float progress = 0.0f; + + ReplayRenderer *renderer = NULL; + status = RemoteRenderer_CreateProxyRenderer(remote, 0, argv[3], &progress, &renderer); + + if(renderer && status == eReplayCreate_Success) + DisplayRendererPreview(renderer); + + RemoteRenderer_Shutdown(remote); + return 0; } else { - printf("Target: %ls, API: %ls, Busy: %ls\n", RemoteAccess_GetTarget(access), RemoteAccess_GetAPI(access), RemoteAccess_GetBusyClient(access)); - - fflush(stdout); - - volatile bool run = true; - - while(run) - { - RemoteMessage msg; - RemoteAccess_ReceiveMessage(access, &msg); - - if(msg.Type == eRemoteMsg_Disconnected) - { - printf("Disconnected\n"); - RemoteAccess_Shutdown(access); - access = NULL; - break; - } - else if(msg.Type == eRemoteMsg_Busy) - { - printf("Busy: %ls\n", msg.Busy.ClientName.elems); - RemoteAccess_Shutdown(access); - access = NULL; - break; - } - else if(msg.Type == eRemoteMsg_Noop) - { - } - else if(msg.Type == eRemoteMsg_RegisterAPI) - { - printf("Updated - Target: %ls, API: %ls\n", RemoteAccess_GetTarget(access), RemoteAccess_GetAPI(access)); - } - else if(msg.Type == eRemoteMsg_NewCapture) - { - printf("Got capture - %d @ %llu, %d bytes of thumbnail\n", msg.NewCapture.ID, msg.NewCapture.timestamp, msg.NewCapture.thumbnail.count); - } - - fflush(stdout); - } - - if(access) - { - RemoteAccess_Shutdown(access); - access = NULL; - } + fprintf(stderr, "Not enough parameters to --remotereplay"); } + } + // not documented/useful for manual use on the cmd line, used internally + else if(!_wcsicmp(argv[1], L"--cap32for64")) + { + if(argc >= 5) + { + wchar_t *pid = argv[2]; + while(*pid == L'"' || iswspace(*pid)) pid++; - fflush(stdout); + uint32_t pidNum = (uint32_t)_wtoi(pid); - return 0; + wchar_t *log = argv[3]; + + CaptureOptions cmdopts; + + string optstring(&argv[4][0], &argv[4][0] + wcslen(argv[4])); + + cmdopts.FromString(optstring); + + return RENDERDOC_InjectIntoProcess(pidNum, log, &cmdopts, false); + } + else + { + fprintf(stderr, "Not enough parameters to --cap32for64"); + } } } + + fprintf(stderr, "renderdoccmd usage:\n\n"); + fprintf(stderr, " Launch a preview window that replays this logfile and\n"); + fprintf(stderr, " displays the backbuffer.\n"); +#ifdef WIN32 + fprintf(stderr, " Launch a capture of this program with default options.\n"); +#endif + fprintf(stderr, "\n"); + fprintf(stderr, " -h, --help Displays this help message.\n"); + fprintf(stderr, " -c, --capture PROGRAM Launches capture of the program with default options.\n"); + fprintf(stderr, " -i, --inject PID Injects into the specified PID to capture.\n"); + fprintf(stderr, " -r, --replay LOGFILE Launch a preview window that replays this logfile and\n"); + fprintf(stderr, " displays the backbuffer.\n"); + fprintf(stderr, " -rh, --replayhost Starts a replay host server that can be used to remotely\n"); + fprintf(stderr, " replay logfiles from another machine.\n"); + fprintf(stderr, " -rr, --remotereplay HOST LOGFILE Launch a replay of the logfile and display a preview\n"); + fprintf(stderr, " window. Use the remote host to replay all commands.\n"); - MessageBoxW(NULL, L"renderdoccmd Usage:\n\n" \ - L"renderdoccmd.exe \"full path to exe\" [\"path to capture logfile to save to\"]\n" \ - L"renderdoccmd.exe \"full path to logfile to replay\"\n" \ - L"renderdoccmd.exe -inject \"Process ID\"\n", - L"renderdoccmd", MB_OK); return 1; } diff --git a/renderdoccmd/renderdoccmd.vcxproj b/renderdoccmd/renderdoccmd.vcxproj index cf818da9c..501cdc4f1 100644 --- a/renderdoccmd/renderdoccmd.vcxproj +++ b/renderdoccmd/renderdoccmd.vcxproj @@ -93,8 +93,9 @@ Level3 Disabled WIN32;RENDERDOC_PLATFORM=win32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - ..\renderdoc\replay\ + ..\renderdoc\ MultiThreadedDLL + true Windows @@ -109,8 +110,9 @@ Level3 Disabled WIN32;RENDERDOC_PLATFORM=win32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - ..\renderdoc\replay\ + ..\renderdoc\ MultiThreadedDLL + true Windows @@ -127,7 +129,8 @@ true true WIN32;RENDERDOC_PLATFORM=win32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - ..\renderdoc\replay\ + ..\renderdoc\ + true Windows @@ -146,7 +149,8 @@ true true WIN32;RENDERDOC_PLATFORM=win32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - ..\renderdoc\replay\ + ..\renderdoc\ + true Windows @@ -159,6 +163,7 @@ + diff --git a/renderdoccmd/renderdoccmd.vcxproj.filters b/renderdoccmd/renderdoccmd.vcxproj.filters index fd2a8acab..8b3124ff2 100644 --- a/renderdoccmd/renderdoccmd.vcxproj.filters +++ b/renderdoccmd/renderdoccmd.vcxproj.filters @@ -3,6 +3,7 @@ + @@ -28,4 +29,4 @@ Resources - + \ No newline at end of file diff --git a/renderdoccmd/linux_specific.cpp b/renderdoccmd/renderdoccmd_linux.cpp similarity index 60% rename from renderdoccmd/linux_specific.cpp rename to renderdoccmd/renderdoccmd_linux.cpp index 525ca40a9..8d9475bfe 100644 --- a/renderdoccmd/linux_specific.cpp +++ b/renderdoccmd/renderdoccmd_linux.cpp @@ -22,20 +22,67 @@ * THE SOFTWARE. ******************************************************************************/ -#include +#include + +#include +#include +#include + #include +using std::wstring; + +wstring GetUsername() +{ +} + +void DisplayRendererPreview(ReplayRenderer *renderer) +{ + if(renderer == NULL) return; + + // TODO! +} + // symbol defined in libGL but not librenderdoc. // Forces link of libGL after renderdoc (otherwise all symbols would // be resolved and libGL wouldn't link, meaning dlsym(RTLD_NEXT) would fai extern "C" void glXWaitGL(); -int main() +int renderdoccmd(int argc, wchar_t **argv); + +int main(int argc, char *argv[]) { - RENDERDOC_SpawnReplayHost(NULL); + std::setlocale(LC_CTYPE, ""); - volatile bool never_run = false; - if(never_run) glXWaitGL(); + volatile bool never_run = false; if(never_run) glXWaitGL(); - return 0; + // do any linux-specific setup here + + // process any linux-specific arguments here + + wchar_t **wargv = new wchar_t*[argc]; + + iconv_t ic = iconv_open("WCHAR_T", "UTF-8"); + + for(int i=0; i < argc; i++) + { + size_t len = strlen(argv[i]); + wargv[i] = new wchar_t[len]; + + char *inbuf = argv[i]; + size_t insize = len+1; // include null terminator + char *outbuf = (char *)wargv[i]; + size_t outsize = len*sizeof(wchar_t); + iconv(ic, &inbuf, &insize, &outbuf, &outsize); + } + + iconv_close(ic); + + int ret = renderdoccmd(argc, wargv); + + for(int i=0; i < argc; i++) + delete[] wargv[i]; + delete[] wargv; + + return ret; } diff --git a/renderdoccmd/renderdoccmd_win32.cpp b/renderdoccmd/renderdoccmd_win32.cpp new file mode 100644 index 000000000..15da68c1c --- /dev/null +++ b/renderdoccmd/renderdoccmd_win32.cpp @@ -0,0 +1,494 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2014 Crytek + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include +#include +#include + +#include + +#include "resource.h" + +// breakpad +#include "common/windows/http_upload.h" +#include "client/windows/crash_generation/client_info.h" +#include "client/windows/crash_generation/crash_generation_server.h" + +#include "miniz.h" + +using std::string; +using std::wstring; +using std::vector; + +using google_breakpad::ClientInfo; +using google_breakpad::CrashGenerationServer; + +bool exitServer = false; + +static HINSTANCE CrashHandlerInst = 0; +static HWND CrashHandlerWnd = 0; + +bool uploadReport = false; +bool uploadDump = false; +bool uploadLog = false; +string reproSteps = ""; + +wstring dump = L""; +vector customInfo; +wstring logpath = L""; + +HINSTANCE hInstance = NULL; + +INT_PTR CALLBACK CrashHandlerProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_INITDIALOG: + { + HANDLE hIcon = LoadImage(CrashHandlerInst, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0); + + if(hIcon) + { + SendMessage(hDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); + SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon); + } + + SetDlgItemTextW(hDlg, IDC_WELCOMETEXT, + L"RenderDoc has encountered an unhandled exception or other similar unrecoverable error.\n\n" \ + L"If you had captured but not saved a logfile it should still be available in %TEMP% and will not be deleted," \ + L"you can try loading it again.\n\n" \ + L"A minidump has been created and the RenderDoc diagnostic log (NOT any capture logfile) is available if you would like " \ + L"to send them back to be analysed. The path for both is found below if you would like to inspect their contents and censor as appropriate.\n\n" \ + L"Neither contains any significant private information, the minidump has some internal states and local memory at the time of the " \ + L"crash & thread stacks, etc. The diagnostic log contains diagnostic messages like warnings and errors.\n\n" \ + L"The only other information sent is the version of RenderDoc, C# exception callstack, and any notes you include.\n\n" \ + L"Any repro steps or notes would be helpful to include with the report. If you'd like to be contacted about the bug " \ + L"e.g. for updates about its status just include your email & name. Thank you!\n\n" \ + L"Baldur (renderdoc@crytek.com)"); + + SetDlgItemTextW(hDlg, IDC_DUMPPATH, dump.c_str()); + SetDlgItemTextW(hDlg, IDC_LOGPATH, logpath.c_str()); + + CheckDlgButton(hDlg, IDC_SENDDUMP, BST_CHECKED); + CheckDlgButton(hDlg, IDC_SENDLOG, BST_CHECKED); + } + + case WM_SHOWWINDOW: + { + + { + RECT r; + GetClientRect(hDlg, &r); + + int xPos = (GetSystemMetrics(SM_CXSCREEN) - r.right)/2; + int yPos = (GetSystemMetrics(SM_CYSCREEN) - r.bottom)/2; + + SetWindowPos(hDlg, NULL, xPos, yPos, 0, 0, SWP_NOZORDER | SWP_NOSIZE); + } + + return (INT_PTR)TRUE; + } + + case WM_COMMAND: + { + int ID = LOWORD(wParam); + + if(ID == IDC_DONTSEND) + { + EndDialog(hDlg, 0); + return (INT_PTR)TRUE; + } + else if(ID == IDC_SEND) + { + uploadReport = true; + uploadDump = (IsDlgButtonChecked(hDlg, IDC_SENDDUMP) != 0); + uploadLog = (IsDlgButtonChecked(hDlg, IDC_SENDLOG) != 0); + + char notes[4097] = {0}; + + GetDlgItemTextA(hDlg, IDC_NAME, notes, 4096); + notes[4096] = 0; + + reproSteps = "Name: "; + reproSteps += notes; + reproSteps += "\n"; + + memset(notes, 0, 4096); + GetDlgItemTextA(hDlg, IDC_EMAIL, notes, 4096); + notes[4096] = 0; + + reproSteps += "Email: "; + reproSteps += notes; + reproSteps += "\n\n"; + + memset(notes, 0, 4096); + GetDlgItemTextA(hDlg, IDC_REPRO, notes, 4096); + notes[4096] = 0; + + reproSteps += notes; + + EndDialog(hDlg, 0); + return (INT_PTR)TRUE; + } + } + break; + + case WM_QUIT: + case WM_DESTROY: + case WM_CLOSE: + { + EndDialog(hDlg, 0); + return (INT_PTR)TRUE; + } + break; + } + return (INT_PTR)FALSE; +} + +static void _cdecl OnClientCrashed(void* context, const ClientInfo* client_info, const wstring* dump_path) +{ + if(dump_path) + { + dump = *dump_path; + + google_breakpad::CustomClientInfo custom = client_info->GetCustomInfo(); + + for(size_t i=0; i < custom.count; i++) + customInfo.push_back(custom.entries[i]); + } + + exitServer = true; +} + +static void _cdecl OnClientExited(void* context, const ClientInfo* client_info) +{ + exitServer = true; +} + +LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if(msg == WM_CLOSE) { DestroyWindow(hwnd); return 0; } + if(msg == WM_DESTROY) { PostQuitMessage(0); return 0; } + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +wstring GetUsername() +{ + wchar_t username[256] = {0}; + DWORD usersize = 255; + GetUserNameW(username, &usersize); + + return username; +} + +void DisplayRendererPreview(ReplayRenderer *renderer) +{ + if(renderer == NULL) return; + + HWND wnd = 0; + + wnd = CreateWindowEx(WS_EX_CLIENTEDGE, L"renderdoccmd", L"renderdoccmd", WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, 1280, 720, + NULL, NULL, hInstance, NULL); + + if(wnd == NULL) + { + return; + } + + ShowWindow(wnd, SW_SHOW); + UpdateWindow(wnd); + + rdctype::array texs; + ReplayRenderer_GetTextures(renderer, &texs); + + ReplayOutput *out = ReplayRenderer_CreateOutput(renderer, wnd); + + ReplayRenderer_SetFrameEvent(renderer, 0, 10000000); + + OutputConfig c; + c.m_Type = eOutputType_TexDisplay; + + ReplayOutput_SetOutputConfig(out, c); + + for(int32_t i=0; i < texs.count; i++) + { + wstring name(texs[i].name.elems, texs[i].name.elems+texs[i].name.count); + if(name.find(L"Swap") != wstring::npos) + { + TextureDisplay d; + d.texid = texs[i].ID; + d.mip = 0; + d.overlay = eTexOverlay_None; + d.CustomShader = ResourceId(); + d.HDRMul = -1.0f; + d.rangemin = 0.0f; + d.rangemax = 1.0f; + d.scale = 1.0f; + d.offx = 0.0f; + d.offy = 0.0f; + d.sliceFace = 0; + d.rawoutput = false; + d.Red = d.Green = d.Blue = true; + d.Alpha = false; + + ReplayOutput_SetTextureDisplay(out, d); + + break; + } + } + + MSG msg; + ZeroMemory(&msg, sizeof(msg)); + while(true) + { + // Check to see if any messages are waiting in the queue + while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + // Translate the message and dispatch it to WindowProc() + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + // If the message is WM_QUIT, exit the while loop + if(msg.message == WM_QUIT) + break; + + ReplayRenderer_SetFrameEvent(renderer, 0, 10000000+rand()%1000); + + ReplayOutput_SetOutputConfig(out, c); + + ReplayOutput_Display(out); + + Sleep(40); + } +} + +int renderdoccmd(int argc, wchar_t **argv); + +int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd ) +{ + LPWSTR *argv; + int argc; + + argv = CommandLineToArgvW(GetCommandLine(), &argc); + + hInstance = hInst; + + WNDCLASSEX wc; + wc.cbSize = sizeof(WNDCLASSEX); + wc.style = 0; + wc.lpfnWndProc = WndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = hInstance; + wc.hIcon = LoadIcon(NULL, MAKEINTRESOURCE(IDI_ICON)); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); + wc.lpszMenuName = NULL; + wc.lpszClassName = L"renderdoccmd"; + wc.hIconSm = LoadIcon(NULL, MAKEINTRESOURCE(IDI_ICON)); + + if(!RegisterClassEx(&wc)) + { + return 1; + } + + CrashGenerationServer *crashServer = NULL; + + // special WIN32 option for launching the crash handler + if(argc == 2 && !_wcsicmp(argv[1], L"--crashhandle")) + { + wchar_t tempPath[MAX_PATH] = {0}; + GetTempPathW(MAX_PATH-1, tempPath); + + Sleep(100); + + wstring dumpFolder = tempPath; + dumpFolder += L"RenderDocDumps"; + + CreateDirectoryW(dumpFolder.c_str(), NULL); + + crashServer = new CrashGenerationServer(L"\\\\.\\pipe\\RenderDocBreakpadServer", + NULL, NULL, NULL, OnClientCrashed, NULL, + OnClientExited, NULL, NULL, NULL, true, + &dumpFolder); + + if (!crashServer->Start()) { + delete crashServer; + crashServer = NULL; + return 1; + } + + CrashHandlerInst = hInstance; + + CrashHandlerWnd = CreateWindowEx(WS_EX_CLIENTEDGE, L"renderdoccmd", L"renderdoccmd", WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, 10, 10, + NULL, NULL, hInstance, NULL); + + HANDLE hIcon = LoadImage(CrashHandlerInst, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0); + + if(hIcon) + { + SendMessage(CrashHandlerWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); + SendMessage(CrashHandlerWnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon); + } + + ShowWindow(CrashHandlerWnd, SW_HIDE); + + HANDLE readyEvent = CreateEventA(NULL, TRUE, FALSE, "RENDERDOC_CRASHHANDLE"); + + if(readyEvent != NULL) + { + SetEvent(readyEvent); + + CloseHandle(readyEvent); + } + + MSG msg; + ZeroMemory(&msg, sizeof(msg)); + while(!exitServer) + { + // Check to see if any messages are waiting in the queue + while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + // Translate the message and dispatch it to WindowProc() + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + // If the message is WM_QUIT, exit the while loop + if(msg.message == WM_QUIT) + break; + + Sleep(100); + } + + delete crashServer; + crashServer = NULL; + + if(!dump.empty()) + { + logpath = L""; + + string report = ""; + + for(size_t i=0; i < customInfo.size(); i++) + { + wstring name = customInfo[i].name; + wstring val = customInfo[i].value; + + if(name == L"logpath") + { + logpath = val; + } + else if(name == L"ptime") + { + // breakpad uptime, ignore. + } + else + { + report += string(name.begin(), name.end()) + ": " + string(val.begin(), val.end()) + "\n"; + } + } + + DialogBox(CrashHandlerInst, MAKEINTRESOURCE(IDD_CRASH_HANDLER), CrashHandlerWnd, (DLGPROC)CrashHandlerProc); + + report += "\n\nRepro steps/Notes:\n\n" + reproSteps; + + { + FILE *f = NULL; + _wfopen_s(&f, logpath.c_str(), L"r"); + if(f) + { + fseek(f, 0, SEEK_END); + long filesize = ftell(f); + fseek(f, 0, SEEK_SET); + + if(filesize > 10) + { + char *error_log = new char[filesize+1]; + memset(error_log, 0, filesize+1); + + fread(error_log, 1, filesize, f); + + char *managed_callstack = strstr(error_log, "--- Begin C# Exception Data ---"); + if(managed_callstack) + { + report += managed_callstack; + report += "\n\n"; + } + + delete[] error_log; + } + + fclose(f); + } + } + + if(uploadReport) + { + mz_zip_archive zip; + ZeroMemory(&zip, sizeof(zip)); + + wstring destzip = dumpFolder + L"\\report.zip"; + + DeleteFileW(destzip.c_str()); + + mz_zip_writer_init_wfile(&zip, destzip.c_str(), 0); + mz_zip_writer_add_mem(&zip, "report.txt", report.c_str(), report.length(), MZ_BEST_COMPRESSION); + + if(uploadDump && !dump.empty()) + mz_zip_writer_add_wfile(&zip, "minidump.dmp", dump.c_str(), NULL, 0, MZ_BEST_COMPRESSION); + + if(uploadLog && !logpath.empty()) + mz_zip_writer_add_wfile(&zip, "error.log", logpath.c_str(), NULL, 0, MZ_BEST_COMPRESSION); + + mz_zip_writer_finalize_archive(&zip); + mz_zip_writer_end(&zip); + + int timeout = 10000; + wstring body = L""; + int code = 0; + + std::map params; + + google_breakpad::HTTPUpload::SendRequest(L"http://renderdoc.org/bugsubmit", params, + dumpFolder + L"\\report.zip", L"report", &timeout, &body, &code); + + DeleteFileW(destzip.c_str()); + } + } + + if(!dump.empty()) + DeleteFileW(dump.c_str()); + + if(!logpath.empty()) + DeleteFileW(logpath.c_str()); + + return 0; + } + + return renderdoccmd(argc, argv); +}