blob: 1671424942c9fae12fc560907c3d1d6ad357c1a1 [file] [log] [blame]
Torne (Richard Coles)a1401312014-03-18 10:20:56 +00001// Copyright (c) 2014 The Chromium Authors. All rights reserved.
Torne (Richard Coles)58218062012-11-14 11:43:16 +00002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <windows.h>
6#include <CommCtrl.h>
7#include <commdlg.h>
8#include <time.h>
9#include <windowsx.h>
10#include <atlbase.h>
11#include <atlsecurity.h>
12#include <algorithm>
13#include <sstream>
14
15#include "sandbox/win/sandbox_poc/main_ui_window.h"
16#include "base/logging.h"
17#include "sandbox/win/sandbox_poc/resource.h"
18#include "sandbox/win/src/acl.h"
19#include "sandbox/win/src/sandbox.h"
20#include "sandbox/win/src/win_utils.h"
21
22HWND MainUIWindow::list_view_ = NULL;
23
24const wchar_t MainUIWindow::kDefaultDll_[] = L"\\POCDLL.dll";
25const wchar_t MainUIWindow::kDefaultEntryPoint_[] = L"Run";
26const wchar_t MainUIWindow::kDefaultLogFile_[] = L"";
27
28MainUIWindow::MainUIWindow()
29 : instance_handle_(NULL),
30 spawn_target_(L""),
31 dll_path_(L""),
32 entry_point_(L""),
33 broker_(NULL) {
34}
35
36MainUIWindow::~MainUIWindow() {
37}
38
39unsigned int MainUIWindow::CreateMainWindowAndLoop(
40 HINSTANCE instance,
41 wchar_t* command_line,
42 int show_command,
43 sandbox::BrokerServices* broker) {
44 DCHECK(instance);
45 DCHECK(command_line);
46 DCHECK(broker);
47
48 instance_handle_ = instance;
49 spawn_target_ = command_line;
50 broker_ = broker;
51
52 // We'll use spawn_target_ later for creating a child process, but
53 // CreateProcess doesn't like double quotes, so we remove them along with
54 // tabs and spaces from the start and end of the string
55 const wchar_t *trim_removal = L" \r\t\"";
56 spawn_target_.erase(0, spawn_target_.find_first_not_of(trim_removal));
57 spawn_target_.erase(spawn_target_.find_last_not_of(trim_removal) + 1);
58
59 WNDCLASSEX window_class = {0};
60 window_class.cbSize = sizeof(WNDCLASSEX);
61 window_class.style = CS_HREDRAW | CS_VREDRAW;
62 window_class.lpfnWndProc = MainUIWindow::WndProc;
63 window_class.cbClsExtra = 0;
64 window_class.cbWndExtra = 0;
65 window_class.hInstance = instance;
66 window_class.hIcon =
67 ::LoadIcon(instance, MAKEINTRESOURCE(IDI_SANDBOX));
68 window_class.hCursor = ::LoadCursor(NULL, IDC_ARROW);
69 window_class.hbrBackground = GetStockBrush(WHITE_BRUSH);
70 window_class.lpszMenuName = MAKEINTRESOURCE(IDR_MENU_MAIN_UI);
71 window_class.lpszClassName = L"sandbox_ui_1";
72 window_class.hIconSm = NULL;
73
74 INITCOMMONCONTROLSEX controls = {
75 sizeof(INITCOMMONCONTROLSEX),
76 ICC_STANDARD_CLASSES | ICC_LISTVIEW_CLASSES
77 };
78 ::InitCommonControlsEx(&controls);
79
80 if (!::RegisterClassEx(&window_class))
81 return ::GetLastError();
82
83 // Create a main window of size 600x400
84 HWND window = ::CreateWindowW(window_class.lpszClassName,
85 L"", // window name
86 WS_OVERLAPPEDWINDOW,
87 CW_USEDEFAULT, // x
88 CW_USEDEFAULT, // y
89 600, // width
90 400, // height
91 NULL, // parent
92 NULL, // NULL = use class menu
93 instance,
94 0); // lpParam
95
96 if (NULL == window)
97 return ::GetLastError();
98
99 ::SetWindowLongPtr(window,
100 GWLP_USERDATA,
101 reinterpret_cast<LONG_PTR>(this));
102
103 ::SetWindowText(window, L"Sandbox Proof of Concept");
104
105 ::ShowWindow(window, show_command);
106
107 MSG message;
108 // Now lets start the message pump retrieving messages for any window that
109 // belongs to the current thread
110 while (::GetMessage(&message, NULL, 0, 0)) {
111 ::TranslateMessage(&message);
112 ::DispatchMessage(&message);
113 }
114
115 return 0;
116}
117
118LRESULT CALLBACK MainUIWindow::WndProc(HWND window,
119 UINT message_id,
120 WPARAM wparam,
121 LPARAM lparam) {
122 MainUIWindow* host = FromWindow(window);
123
124 #define HANDLE_MSG(hwnd, message, fn) \
125 case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))
126
127 switch (message_id) {
128 case WM_CREATE:
129 // 'host' is not yet available when we get the WM_CREATE message
130 return HANDLE_WM_CREATE(window, wparam, lparam, OnCreate);
131 case WM_DESTROY:
132 return HANDLE_WM_DESTROY(window, wparam, lparam, host->OnDestroy);
133 case WM_SIZE:
134 return HANDLE_WM_SIZE(window, wparam, lparam, host->OnSize);
135 case WM_COMMAND: {
136 // Look at which menu item was clicked on (or which accelerator)
137 int id = LOWORD(wparam);
138 switch (id) {
139 case ID_FILE_EXIT:
140 host->OnFileExit();
141 break;
142 case ID_COMMANDS_SPAWNTARGET:
143 host->OnCommandsLaunch(window);
144 break;
145 default:
146 // Some other menu item or accelerator
147 break;
148 }
149
150 return ERROR_SUCCESS;
151 }
152
153 default:
154 // Some other WM_message, let it pass to DefWndProc
155 break;
156 }
157
158 return DefWindowProc(window, message_id, wparam, lparam);
159}
160
161INT_PTR CALLBACK MainUIWindow::SpawnTargetWndProc(HWND dialog,
162 UINT message_id,
163 WPARAM wparam,
164 LPARAM lparam) {
165 UNREFERENCED_PARAMETER(lparam);
166
167 // Grab a reference to the main UI window (from the window handle)
168 MainUIWindow* host = FromWindow(GetParent(dialog));
169 DCHECK(host);
170
171 switch (message_id) {
172 case WM_INITDIALOG: {
173 // Initialize the window text for DLL name edit box
174 HWND edit_box_dll_name = ::GetDlgItem(dialog, IDC_DLL_NAME);
175 wchar_t current_dir[MAX_PATH];
176 if (GetCurrentDirectory(MAX_PATH, current_dir)) {
Torne (Richard Coles)5d1f7b12014-02-21 12:16:55 +0000177 base::string16 dll_path = base::string16(current_dir) +
178 base::string16(kDefaultDll_);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000179 ::SetWindowText(edit_box_dll_name, dll_path.c_str());
180 }
181
182 // Initialize the window text for Entry Point edit box
183 HWND edit_box_entry_point = ::GetDlgItem(dialog, IDC_ENTRY_POINT);
184 ::SetWindowText(edit_box_entry_point, kDefaultEntryPoint_);
185
186 // Initialize the window text for Log File edit box
187 HWND edit_box_log_file = ::GetDlgItem(dialog, IDC_LOG_FILE);
188 ::SetWindowText(edit_box_log_file, kDefaultLogFile_);
189
190 return static_cast<INT_PTR>(TRUE);
191 }
192 case WM_COMMAND:
193 // If the user presses the OK button (Launch)
194 if (LOWORD(wparam) == IDOK) {
195 if (host->OnLaunchDll(dialog)) {
196 if (host->SpawnTarget()) {
197 ::EndDialog(dialog, LOWORD(wparam));
198 }
199 }
200 return static_cast<INT_PTR>(TRUE);
201 } else if (LOWORD(wparam) == IDCANCEL) {
202 // If the user presses the Cancel button
203 ::EndDialog(dialog, LOWORD(wparam));
204 return static_cast<INT_PTR>(TRUE);
205 } else if (LOWORD(wparam) == IDC_BROWSE_DLL) {
206 // If the user presses the Browse button to look for a DLL
Torne (Richard Coles)5d1f7b12014-02-21 12:16:55 +0000207 base::string16 dll_path = host->OnShowBrowseForDllDlg(dialog);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000208 if (dll_path.length() > 0) {
209 // Initialize the window text for Log File edit box
210 HWND edit_box_dll_path = ::GetDlgItem(dialog, IDC_DLL_NAME);
211 ::SetWindowText(edit_box_dll_path, dll_path.c_str());
212 }
213 return static_cast<INT_PTR>(TRUE);
214 } else if (LOWORD(wparam) == IDC_BROWSE_LOG) {
215 // If the user presses the Browse button to look for a log file
Torne (Richard Coles)5d1f7b12014-02-21 12:16:55 +0000216 base::string16 log_path = host->OnShowBrowseForLogFileDlg(dialog);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000217 if (log_path.length() > 0) {
218 // Initialize the window text for Log File edit box
219 HWND edit_box_log_file = ::GetDlgItem(dialog, IDC_LOG_FILE);
220 ::SetWindowText(edit_box_log_file, log_path.c_str());
221 }
222 return static_cast<INT_PTR>(TRUE);
223 }
224
225 break;
226 }
227
228 return static_cast<INT_PTR>(FALSE);
229}
230
231MainUIWindow* MainUIWindow::FromWindow(HWND main_window) {
232 // We store a 'this' pointer using SetWindowLong in CreateMainWindowAndLoop
233 // so that we can retrieve it with this function later. This prevents us
234 // from having to define all the message handling functions (that we refer to
235 // in the window proc) as static
236 ::GetWindowLongPtr(main_window, GWLP_USERDATA);
237 return reinterpret_cast<MainUIWindow*>(
238 ::GetWindowLongPtr(main_window, GWLP_USERDATA));
239}
240
241BOOL MainUIWindow::OnCreate(HWND parent_window, LPCREATESTRUCT) {
242 // Create the listview that will the main app UI
243 list_view_ = ::CreateWindow(WC_LISTVIEW, // Class name
244 L"", // Window name
245 WS_CHILD | WS_VISIBLE | LVS_REPORT |
246 LVS_NOCOLUMNHEADER | WS_BORDER,
247 0, // x
248 0, // y
249 0, // width
250 0, // height
251 parent_window, // parent
252 NULL, // menu
253 ::GetModuleHandle(NULL),
254 0); // lpParam
255
256 DCHECK(list_view_);
257 if (!list_view_)
258 return FALSE;
259
260 LVCOLUMN list_view_column = {0};
261 list_view_column.mask = LVCF_FMT | LVCF_WIDTH ;
262 list_view_column.fmt = LVCFMT_LEFT;
263 list_view_column.cx = 10000; // Maximum size of an entry in the list view.
264 ListView_InsertColumn(list_view_, 0, &list_view_column);
265
266 // Set list view to show green font on black background
267 ListView_SetBkColor(list_view_, CLR_NONE);
268 ListView_SetTextColor(list_view_, RGB(0x0, 0x0, 0x0));
269 ListView_SetTextBkColor(list_view_, CLR_NONE);
270
271 return TRUE;
272}
273
274void MainUIWindow::OnDestroy(HWND window) {
275 UNREFERENCED_PARAMETER(window);
276
277 // Post a quit message because our application is over when the
278 // user closes this window.
279 ::PostQuitMessage(0);
280}
281
282void MainUIWindow::OnSize(HWND window, UINT state, int cx, int cy) {
283 UNREFERENCED_PARAMETER(window);
284 UNREFERENCED_PARAMETER(state);
285
286 // If we have a valid inner child, resize it to cover the entire
287 // client area of the main UI window.
288 if (list_view_) {
289 ::MoveWindow(list_view_,
290 0, // x
291 0, // y
292 cx, // width
293 cy, // height
294 TRUE); // repaint
295 }
296}
297
298void MainUIWindow::OnPaint(HWND window) {
299 PAINTSTRUCT paintstruct;
300 ::BeginPaint(window, &paintstruct);
301 // add painting code here if required
302 ::EndPaint(window, &paintstruct);
303}
304
305void MainUIWindow::OnFileExit() {
306 ::PostQuitMessage(0);
307}
308
309void MainUIWindow::OnCommandsLaunch(HWND window) {
310 // User wants to see the Select DLL dialog box
311 ::DialogBox(instance_handle_,
312 MAKEINTRESOURCE(IDD_LAUNCH_DLL),
313 window,
314 SpawnTargetWndProc);
315}
316
317bool MainUIWindow::OnLaunchDll(HWND dialog) {
318 HWND edit_box_dll_name = ::GetDlgItem(dialog, IDC_DLL_NAME);
319 HWND edit_box_entry_point = ::GetDlgItem(dialog, IDC_ENTRY_POINT);
320 HWND edit_log_file = ::GetDlgItem(dialog, IDC_LOG_FILE);
321
322 wchar_t dll_path[MAX_PATH];
323 wchar_t entry_point[MAX_PATH];
324 wchar_t log_file[MAX_PATH];
325
326 int dll_name_len = ::GetWindowText(edit_box_dll_name, dll_path, MAX_PATH);
327 int entry_point_len = ::GetWindowText(edit_box_entry_point,
328 entry_point, MAX_PATH);
329 // Log file is optional (can be blank)
330 ::GetWindowText(edit_log_file, log_file, MAX_PATH);
331
332 if (0 >= dll_name_len) {
333 ::MessageBox(dialog,
334 L"Please specify a DLL for the target to load",
335 L"No DLL specified",
336 MB_ICONERROR);
337 return false;
338 }
339
340 if (GetFileAttributes(dll_path) == INVALID_FILE_ATTRIBUTES) {
341 ::MessageBox(dialog,
342 L"DLL specified was not found",
343 L"DLL not found",
344 MB_ICONERROR);
345 return false;
346 }
347
348 if (0 >= entry_point_len) {
349 ::MessageBox(dialog,
350 L"Please specify an entry point for the DLL",
351 L"No entry point specified",
352 MB_ICONERROR);
353 return false;
354 }
355
356 // store these values in the member variables for use in SpawnTarget
Torne (Richard Coles)5d1f7b12014-02-21 12:16:55 +0000357 log_file_ = base::string16(L"\"") + log_file + base::string16(L"\"");
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000358 dll_path_ = dll_path;
359 entry_point_ = entry_point;
360
361 return true;
362}
363
364DWORD WINAPI MainUIWindow::ListenPipeThunk(void *param) {
365 return reinterpret_cast<MainUIWindow*>(param)->ListenPipe();
366}
367
368DWORD WINAPI MainUIWindow::WaitForTargetThunk(void *param) {
369 return reinterpret_cast<MainUIWindow*>(param)->WaitForTarget();
370}
371
372// Thread waiting for the target application to die. It displays
373// a message in the list view when it happens.
374DWORD MainUIWindow::WaitForTarget() {
375 WaitForSingleObject(target_.hProcess, INFINITE);
376
377 DWORD exit_code = 0;
378 if (!GetExitCodeProcess(target_.hProcess, &exit_code)) {
379 exit_code = 0xFFFF; // Default exit code
380 }
381
382 ::CloseHandle(target_.hProcess);
383 ::CloseHandle(target_.hThread);
384
385 AddDebugMessage(L"Targed exited with return code %d", exit_code);
386 return 0;
387}
388
389// Thread waiting for messages on the log pipe. It displays the messages
390// in the listview.
391DWORD MainUIWindow::ListenPipe() {
392 HANDLE logfile_handle = NULL;
393 ATL::CString file_to_open = log_file_.c_str();
394 file_to_open.Remove(L'\"');
395 if (file_to_open.GetLength()) {
396 logfile_handle = ::CreateFile(file_to_open.GetBuffer(),
397 GENERIC_WRITE,
398 FILE_SHARE_READ | FILE_SHARE_WRITE,
399 NULL, // Default security attributes
400 CREATE_ALWAYS,
401 FILE_ATTRIBUTE_NORMAL,
402 NULL); // No template
403 if (INVALID_HANDLE_VALUE == logfile_handle) {
404 AddDebugMessage(L"Failed to open \"%ls\" for logging. Error %d",
405 file_to_open.GetBuffer(), ::GetLastError());
406 logfile_handle = NULL;
407 }
408 }
409
410 const int kSizeBuffer = 1024;
411 BYTE read_buffer[kSizeBuffer] = {0};
412 ATL::CStringA read_buffer_global;
413 ATL::CStringA string_to_print;
414
415 DWORD last_error = 0;
416 while(last_error == ERROR_SUCCESS || last_error == ERROR_PIPE_LISTENING ||
417 last_error == ERROR_NO_DATA)
418 {
419 DWORD read_data_length;
420 if (::ReadFile(pipe_handle_,
421 read_buffer,
422 kSizeBuffer - 1, // Max read size
423 &read_data_length,
424 NULL)) { // Not overlapped
425 if (logfile_handle) {
426 DWORD write_data_length;
427 ::WriteFile(logfile_handle,
428 read_buffer,
429 read_data_length,
430 &write_data_length,
431 FALSE); // Not overlapped
432 }
433
434 // Append the new buffer to the current buffer
435 read_buffer[read_data_length] = NULL;
436 read_buffer_global += reinterpret_cast<char *>(read_buffer);
437 read_buffer_global.Remove(10); // Remove the CRs
438
439 // If we completed a new line, output it
440 int endline = read_buffer_global.Find(13); // search for LF
441 while (-1 != endline) {
442 string_to_print = read_buffer_global;
443 string_to_print.Delete(endline, string_to_print.GetLength());
444 read_buffer_global.Delete(0, endline);
445
446 // print the line (with the ending LF)
447 OutputDebugStringA(string_to_print.GetBuffer());
448
449 // Remove the ending LF
450 read_buffer_global.Delete(0, 1);
451
452 // Add the line to the log
453 AddDebugMessage(L"%S", string_to_print.GetBuffer());
454
455 endline = read_buffer_global.Find(13);
456 }
457 last_error = ERROR_SUCCESS;
458 } else {
459 last_error = GetLastError();
460 Sleep(100);
461 }
462 }
463
464 if (read_buffer_global.GetLength()) {
465 AddDebugMessage(L"%S", read_buffer_global.GetBuffer());
466 }
467
468 CloseHandle(pipe_handle_);
469
470 if (logfile_handle) {
471 CloseHandle(logfile_handle);
472 }
473
474 return 0;
475}
476
477bool MainUIWindow::SpawnTarget() {
478 // Generate the pipe name
479 GUID random_id;
480 CoCreateGuid(&random_id);
481
482 wchar_t log_pipe[MAX_PATH] = {0};
483 wnsprintf(log_pipe, MAX_PATH - 1,
484 L"\\\\.\\pipe\\sbox_pipe_log_%lu_%lu_%lu_%lu",
485 random_id.Data1,
486 random_id.Data2,
487 random_id.Data3,
488 random_id.Data4);
489
490 // We concatenate the four strings, add three spaces and a zero termination
491 // We use the resulting string as a param to CreateProcess (in SpawnTarget)
492 // Documented maximum for command line in CreateProcess is 32K (msdn)
493 size_t size_call = spawn_target_.length() + entry_point_.length() +
494 dll_path_.length() + wcslen(log_pipe) + 6;
495 if (32 * 1024 < (size_call * sizeof(wchar_t))) {
496 AddDebugMessage(L"The length of the arguments exceeded 32K. "
497 L"Aborting operation.");
498 return false;
499 }
500
501 wchar_t * arguments = new wchar_t[size_call];
502 wnsprintf(arguments, static_cast<int>(size_call), L"%ls %ls \"%ls\" %ls",
503 spawn_target_.c_str(), entry_point_.c_str(),
504 dll_path_.c_str(), log_pipe);
505
506 arguments[size_call - 1] = L'\0';
507
508 sandbox::TargetPolicy* policy = broker_->CreatePolicy();
509 policy->SetJobLevel(sandbox::JOB_LOCKDOWN, 0);
510 policy->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS,
511 sandbox::USER_LOCKDOWN);
512 policy->SetAlternateDesktop(true);
513 policy->SetDelayedIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW);
514
515 // Set the rule to allow the POC dll to be loaded by the target. Note that
516 // the rule allows 'all access' to the DLL, which could mean that the target
517 // could modify the DLL on disk.
518 policy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES,
519 sandbox::TargetPolicy::FILES_ALLOW_ANY, dll_path_.c_str());
520
521 sandbox::ResultCode result = broker_->SpawnTarget(spawn_target_.c_str(),
522 arguments, policy,
523 &target_);
524
525 policy->Release();
526 policy = NULL;
527
528 bool return_value = false;
529 if (sandbox::SBOX_ALL_OK != result) {
530 AddDebugMessage(
531 L"Failed to spawn target %ls w/args (%ls), sandbox error code: %d",
532 spawn_target_.c_str(), arguments, result);
533 return_value = false;
534 } else {
535
536 DWORD thread_id;
537 ::CreateThread(NULL, // Default security attributes
538 NULL, // Default stack size
539 &MainUIWindow::WaitForTargetThunk,
540 this,
541 0, // No flags
542 &thread_id);
543
544 pipe_handle_ = ::CreateNamedPipe(log_pipe,
545 PIPE_ACCESS_INBOUND | WRITE_DAC,
546 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
547 1, // Number of instances.
548 512, // Out buffer size.
549 512, // In buffer size.
550 NMPWAIT_USE_DEFAULT_WAIT,
551 NULL); // Default security descriptor
552
553 if (INVALID_HANDLE_VALUE == pipe_handle_)
554 AddDebugMessage(L"Failed to create pipe. Error %d", ::GetLastError());
555
Torne (Richard Coles)a1401312014-03-18 10:20:56 +0000556 if (!sandbox::AddKnownSidToObject(pipe_handle_, SE_KERNEL_OBJECT,
557 WinWorldSid, GRANT_ACCESS,
558 FILE_ALL_ACCESS))
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000559 AddDebugMessage(L"Failed to set security on pipe. Error %d",
560 ::GetLastError());
561
562 ::CreateThread(NULL, // Default security attributes
563 NULL, // Default stack size
564 &MainUIWindow::ListenPipeThunk,
565 this,
566 0, // No flags
567 &thread_id);
568
569 ::ResumeThread(target_.hThread);
570
571 AddDebugMessage(L"Successfully spawned target w/args (%ls)", arguments);
572 return_value = true;
573 }
574
575 delete[] arguments;
576 return return_value;
577}
578
Torne (Richard Coles)5d1f7b12014-02-21 12:16:55 +0000579base::string16 MainUIWindow::OnShowBrowseForDllDlg(HWND owner) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000580 wchar_t filename[MAX_PATH];
581 wcscpy_s(filename, MAX_PATH, L"");
582
583 OPENFILENAMEW file_info = {0};
584 file_info.lStructSize = sizeof(file_info);
585 file_info.hwndOwner = owner;
586 file_info.lpstrFile = filename;
587 file_info.nMaxFile = MAX_PATH;
588 file_info.lpstrFilter = L"DLL files (*.dll)\0*.dll\0All files\0*.*\0\0\0";
589
590 file_info.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
591
592 if (GetOpenFileName(&file_info)) {
593 return file_info.lpstrFile;
594 }
595
596 return L"";
597}
598
Torne (Richard Coles)5d1f7b12014-02-21 12:16:55 +0000599base::string16 MainUIWindow::OnShowBrowseForLogFileDlg(HWND owner) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000600 wchar_t filename[MAX_PATH];
601 wcscpy_s(filename, MAX_PATH, L"");
602
603 OPENFILENAMEW file_info = {0};
604 file_info.lStructSize = sizeof(file_info);
605 file_info.hwndOwner = owner;
606 file_info.lpstrFile = filename;
607 file_info.nMaxFile = MAX_PATH;
608 file_info.lpstrFilter = L"Log file (*.txt)\0*.txt\0All files\0*.*\0\0\0";
609
610 file_info.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;
611
612 if (GetSaveFileName(&file_info)) {
613 return file_info.lpstrFile;
614 }
615
616 return L"";
617}
618
619void MainUIWindow::AddDebugMessage(const wchar_t* format, ...) {
620 DCHECK(format);
621 if (!format)
622 return;
623
624 const int kMaxDebugBuffSize = 1024;
625
626 va_list arg_list;
627 _crt_va_start(arg_list, format);
628
629 wchar_t text[kMaxDebugBuffSize + 1];
630 vswprintf_s(text, kMaxDebugBuffSize, format, arg_list);
631 text[kMaxDebugBuffSize] = L'\0';
632
633 InsertLineInListView(text);
634}
635
636
637void MainUIWindow::InsertLineInListView(wchar_t* debug_message) {
638 DCHECK(debug_message);
639 if (!debug_message)
640 return;
641
642 // Prepend the time to the message
643 const int kSizeTime = 100;
644 size_t size_message_with_time = wcslen(debug_message) + kSizeTime;
645 wchar_t * message_time = new wchar_t[size_message_with_time];
646
647 time_t time_temp;
648 time_temp = time(NULL);
649
650 struct tm time = {0};
651 localtime_s(&time, &time_temp);
652
653 size_t return_code;
654 return_code = wcsftime(message_time, kSizeTime, L"[%H:%M:%S] ", &time);
655
656 wcscat_s(message_time, size_message_with_time, debug_message);
657
658 // We add the debug message to the top of the listview
659 LVITEM item;
660 item.iItem = ListView_GetItemCount(list_view_);
661 item.iSubItem = 0;
662 item.mask = LVIF_TEXT | LVIF_PARAM;
663 item.pszText = message_time;
664 item.lParam = 0;
665
666 ListView_InsertItem(list_view_, &item);
667
668 delete[] message_time;
669}