1 /*
2  * PROJECT:     ReactOS Event Log Viewer
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Event Log Viewer main file.
5  * COPYRIGHT:   Copyright 2007 Marc Piulachs <marc.piulachs@codexchange.net>
6  *              Copyright 2008-2016 Eric Kohl <eric.kohl@reactos.org>
7  *              Copyright 2016-2022 Hermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
8  */
9 
10 #include "eventvwr.h"
11 #include "evtdetctl.h"
12 
13 #include <sddl.h> // For ConvertSidToStringSidW
14 #include <shellapi.h>
15 #include <shlwapi.h>
16 
17 #include <pseh/pseh2.h>
18 
19 // #include "resource.h"
20 
21 #define LVM_PROGRESS    (WM_APP + 1)    // Used by the subclassed ListView
22 
23 static const LPCWSTR EVENTVWR_WNDCLASS = L"EVENTVWR"; /* The main window class name */
24 static const LPCWSTR EVENTLOG_BASE_KEY = L"SYSTEM\\CurrentControlSet\\Services\\EventLog\\";
25 static const LPCWSTR EVNTVWR_PARAM_KEY = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Event Viewer";
26 
27 /* The 3 system logs that should always exist in the user's system */
28 static const LPCWSTR SystemLogs[] =
29 {
30     L"Application",
31     L"Security",
32     L"System"
33 };
34 
35 /* MessageFile message buffer size */
36 #define EVENT_MESSAGE_EVENTTEXT_BUFFER  (1024*10)                           // NOTE: Used by evtdetctl.c
37 #define EVENT_MESSAGE_FILE_BUFFER       (1024*10)
38 #define EVENT_DLL_SEPARATOR             L";"
39 #define EVENT_CATEGORY_MESSAGE_FILE     L"CategoryMessageFile"
40 #define EVENT_MESSAGE_FILE              L"EventMessageFile"
41 #define EVENT_PARAMETER_MESSAGE_FILE    L"ParameterMessageFile"
42 
43 #define MAX_LOADSTRING 255
44 
45 #define SPLIT_WIDTH 4
46 
47 /* Globals */
48 HINSTANCE hInst;                            /* Current instance */
49 WCHAR szTitle[MAX_LOADSTRING];              /* The title bar text */
50 WCHAR szTitleTemplate[MAX_LOADSTRING];      /* The logged-on title bar text */
51 WCHAR szStatusBarTemplate[MAX_LOADSTRING];  /* The status bar text */
52 WCHAR szLoadingWait[MAX_LOADSTRING];        /* The "Loading, please wait..." text */
53 WCHAR szEmptyList[MAX_LOADSTRING];          /* The "There are no items to show in this view" text */
54 WCHAR szSaveFilter[MAX_LOADSTRING];         /* Filter Mask for the save Dialog */
55 
56 INT  nVSplitPos;                            /* Vertical splitter (1) position */
57 INT  nHSplitPos;                            /* Horizontal splitter (2) position */
58 BYTE bSplit = 0;                            /* Splitter state:
59                                              * 0: No splitting;
60                                              * 1: Vertical splitting;
61                                              * 2: Horizontal splitting.
62                                              */
63 
64 HWND hwndMainWindow = NULL;                 /* Main window */
65 HWND hwndTreeView;                          /* TreeView control */
66 HWND hwndListView;                          /* ListView control */          // NOTE: Used by evtdetctl.c
67 HWND hwndEventDetails;                      /* Event details pane */
68 HWND hwndStatus;                            /* Status bar */
69 HWND hwndStatusProgress;                    /* Progress bar in the status bar */
70 HMENU hMainMenu;                            /* The application's main menu */
71 
72 HTREEITEM htiSystemLogs = NULL, htiAppLogs = NULL, htiUserLogs = NULL;
73 
74 LPWSTR lpComputerName = NULL;       /* NULL: local user computer (default) */
75 LPWSTR lpszzUserLogsToLoad = NULL;  /* The list of user logs to load at startup (multi-string) */
76 SIZE_T cbUserLogsSize = 0;
77 
78 HKEY hkMachine = NULL; // Registry handle to the HKEY_LOCAL_MACHINE key of the remote computer registry.
79 
80 /* Global event records cache for the current active event log filter */
81 DWORD g_TotalRecords = 0;
82 PEVENTLOGRECORD* g_RecordPtrs = NULL;
83 
84 /* Lists of event logs and event log filters */
85 LIST_ENTRY EventLogList;
86 LIST_ENTRY EventLogFilterList;
87 PEVENTLOGFILTER ActiveFilter = NULL;
88 
89 HANDLE hEnumEventsThread = NULL;
90 HANDLE hStopEnumEvent    = NULL;
91 
92 /*
93  * Setting EnumFilter to a valid pointer and raising the hStartEnumEvent event
94  * triggers the event-enumerator thread to perform a new enumeration.
95  */
96 PEVENTLOGFILTER EnumFilter = NULL;
97 HANDLE hStartStopEnumEvent = NULL;  // End-of-application event
98 HANDLE hStartEnumEvent     = NULL;  // Command event
99 
100 /* Default Open/Save-As dialog box */
101 OPENFILENAMEW sfn;
102 
103 
104 /* Event Viewer Application Settings */
105 typedef struct _SETTINGS
106 {
107     BOOL bShowDetailsPane;      /* Show (TRUE) or Hide (FALSE) the events details pane */
108     BOOL bShowGrid;             /* Show (TRUE) or Hide (FALSE) the events view grid */
109     BOOL bSaveSettings;         /* Save (TRUE) or do not save (FALSE) current settings on exit */
110     BOOL bNewestEventsFirst;    /* Sort the displayed events the newest ones first (TRUE) or last (FALSE) */
111     INT  nVSplitPos;            /* Vertical splitter position */
112     INT  nHSplitPos;            /* Horizontal splitter position */
113     WINDOWPLACEMENT wpPos;
114 } SETTINGS, *PSETTINGS;
115 
116 SETTINGS Settings;
117 
118 
119 /* Forward declarations of functions included in this code module */
120 
121 static DWORD WINAPI
122 StartStopEnumEventsThread(IN LPVOID lpParameter);
123 
124 VOID OpenUserEventLogFile(IN LPCWSTR lpszFileName);
125 
126 VOID BuildLogListAndFilterList(IN LPCWSTR lpComputerName);
127 VOID FreeLogList(VOID);
128 VOID FreeLogFilterList(VOID);
129 
130 ATOM MyRegisterClass(HINSTANCE);
131 BOOL InitInstance(HINSTANCE);
132 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
133 INT_PTR EventLogProperties(HINSTANCE, HWND, PEVENTLOGFILTER);
134 INT_PTR CALLBACK EventDetails(HWND, UINT, WPARAM, LPARAM);
135 
136 
137 /* MAIN FUNCTIONS *************************************************************/
138 
139 VOID
140 ShowWin32Error(IN DWORD dwError)
141 {
142     LPWSTR lpMessageBuffer;
143 
144     if (dwError == ERROR_SUCCESS)
145         return;
146 
147     if (!FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
148                         FORMAT_MESSAGE_FROM_SYSTEM |
149                         FORMAT_MESSAGE_IGNORE_INSERTS,
150                         NULL,
151                         dwError,
152                         LANG_USER_DEFAULT,
153                         (LPWSTR)&lpMessageBuffer,
154                         0, NULL))
155     {
156         return;
157     }
158 
159     MessageBoxW(hwndMainWindow, lpMessageBuffer, szTitle, MB_OK | MB_ICONERROR);
160     LocalFree(lpMessageBuffer);
161 }
162 
163 VOID
164 DisplayUsage(VOID)
165 {
166     LPWSTR lpBuffer;
167     LPCWSTR lpUsage;
168     INT iUsageLen = LoadStringW(hInst, IDS_USAGE, (LPWSTR)&lpUsage, 0);
169 
170     if (iUsageLen == 0)
171         return;
172 
173     lpBuffer = HeapAlloc(GetProcessHeap(), 0, (iUsageLen + 1) * sizeof(WCHAR));
174     if (!lpBuffer)
175         return;
176 
177     StringCchCopyNW(lpBuffer, iUsageLen + 1, lpUsage, iUsageLen);
178     MessageBoxW(NULL, lpBuffer, szTitle, MB_OK | MB_ICONINFORMATION);
179 
180     HeapFree(GetProcessHeap(), 0, lpBuffer);
181 }
182 
183 BOOL
184 ProcessCmdLine(IN LPWSTR lpCmdLine)
185 {
186     BOOL Success = FALSE;
187     INT i, argc;
188     LPWSTR* argv;
189 
190     /* Skip any leading whitespace */
191     if (lpCmdLine)
192     {
193         while (iswspace(*lpCmdLine))
194             ++lpCmdLine;
195     }
196 
197     /* No command line means no processing needed */
198     if (!lpCmdLine || !*lpCmdLine)
199         return TRUE;
200 
201     /* Build the arguments vector */
202     argv = CommandLineToArgvW(lpCmdLine, &argc);
203     if (!argv)
204         return FALSE;
205 
206     /* Parse the command line for options (skip the program name) */
207     for (i = 1; i < argc; ++i)
208     {
209         /* Check for new options */
210         if (argv[i][0] == L'-' || argv[i][0] == L'/')
211         {
212             if (argv[i][1] == L'?' && argv[i][2] == 0)
213             {
214                 /* Display help */
215                 DisplayUsage();
216                 goto Quit;
217             }
218             else
219             if (argv[i][2] == L':')
220             {
221                 switch (towupper(argv[i][1]))
222                 {
223                     case L'L':
224                     {
225                         LPWSTR lpNewBuffer;
226                         LPWSTR lpFileName = argv[i] + 3;
227                         SIZE_T cbFileName;
228 
229                         /* Check for a quoted file name */
230                         if (*lpFileName == L'\"')
231                         {
232                             /* Skip this quote, and the last one too if any */
233                             ++lpFileName;
234                             cbFileName = wcslen(lpFileName);
235                             if (cbFileName > 0 && lpFileName[cbFileName - 1] == L'\"')
236                                 lpFileName[cbFileName - 1] = UNICODE_NULL;
237                         }
238 
239                         /* Skip this one if we do not actually have a file name */
240                         if (!*lpFileName)
241                             continue;
242 
243                         cbFileName = (wcslen(lpFileName) + 1) * sizeof(WCHAR);
244 
245                         /* Reallocate the list of user logs to load */
246                         if (lpszzUserLogsToLoad)
247                         {
248                             lpNewBuffer = HeapReAlloc(GetProcessHeap(),
249                                                       HEAP_ZERO_MEMORY,
250                                                       lpszzUserLogsToLoad,
251                                                       /* Count the multi-string NULL-terminator */
252                                                       cbUserLogsSize + cbFileName + sizeof(WCHAR));
253                         }
254                         else
255                         {
256                             cbUserLogsSize = 0;
257                             lpNewBuffer = HeapAlloc(GetProcessHeap(),
258                                                     HEAP_ZERO_MEMORY,
259                                                     /* Count the multi-string NULL-terminator */
260                                                     cbUserLogsSize + cbFileName + sizeof(WCHAR));
261                         }
262 
263                         if (!lpNewBuffer)
264                         {
265                             ShowWin32Error(ERROR_NOT_ENOUGH_MEMORY);
266                             goto Quit;
267                         }
268 
269                         lpszzUserLogsToLoad = lpNewBuffer;
270                         lpNewBuffer = (LPWSTR)((ULONG_PTR)lpNewBuffer + cbUserLogsSize);
271                         cbUserLogsSize += cbFileName;
272 
273                         /* Save the file name */
274                         StringCbCopyW(lpNewBuffer, cbFileName, lpFileName);
275 
276                         continue;
277                     }
278 
279                     default:
280                         break;
281                 }
282             }
283 
284             /* Unknown argument: display help and bail out */
285             DisplayUsage();
286             goto Quit;
287         }
288         else
289         {
290             /*
291              * An argument that does not start with the switch character.
292              * If this is the first argument then this corresponds to the
293              * optional computer name. Otherwise this is a wrong argument.
294              */
295             if (i == 1)
296             {
297                 /* Store the computer name */
298                 LPWSTR lpTemp = argv[i];
299                 SIZE_T cbLength;
300 
301                 /* Strip any leading backslashes */
302                 while (*lpTemp == L'\\')
303                     ++lpTemp;
304 
305                 cbLength = (wcslen(lpTemp) + 1) * sizeof(WCHAR);
306                 lpComputerName = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbLength);
307                 if (lpComputerName)
308                 {
309                     StringCbCopyW(lpComputerName, cbLength, lpTemp);
310                 }
311                 /* else, fall back to local computer */
312             }
313             else
314             {
315                 /* Invalid syntax: display help and bail out */
316                 DisplayUsage();
317                 goto Quit;
318             }
319         }
320     }
321 
322     Success = TRUE;
323 
324 Quit:
325     /* In case of failure, free anything we have allocated */
326     if (!Success)
327     {
328         if (lpszzUserLogsToLoad)
329         {
330             cbUserLogsSize = 0;
331             HeapFree(GetProcessHeap(), 0, lpszzUserLogsToLoad);
332             lpszzUserLogsToLoad = NULL;
333         }
334         if (lpComputerName)
335         {
336             HeapFree(GetProcessHeap(), 0, lpComputerName);
337             lpComputerName = NULL;
338         }
339     }
340 
341     /* Free the arguments vector and exit */
342     LocalFree(argv);
343     return Success;
344 }
345 
346 BOOL
347 LoadSettings(int nDefCmdShow)
348 {
349     LONG Result;
350     HKEY hKeyEventVwr;
351     DWORD dwType, cbData;
352     WCHAR buffer[100];
353 
354     /* Load the default values */
355     Settings.bSaveSettings = TRUE;
356     Settings.bShowDetailsPane = TRUE;
357     Settings.bShowGrid = FALSE;
358     Settings.bNewestEventsFirst = TRUE;
359     Settings.nVSplitPos = 250; /* Splitter default positions */
360     Settings.nHSplitPos = 250;
361     ZeroMemory(&Settings.wpPos, sizeof(Settings.wpPos));
362     Settings.wpPos.length = sizeof(Settings.wpPos);
363     SetRect(&Settings.wpPos.rcNormalPosition,
364             CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT);
365     Settings.wpPos.showCmd = nDefCmdShow; // SW_SHOWNORMAL;
366 
367     /* Try to open the Event Viewer user key */
368     if (RegOpenKeyExW(HKEY_CURRENT_USER,
369                       EVNTVWR_PARAM_KEY,
370                       0,
371                       KEY_QUERY_VALUE,
372                       &hKeyEventVwr) != ERROR_SUCCESS)
373     {
374         return FALSE;
375     }
376 
377     // Result = RegQueryValueExW(hKeyEventVwr, L"Filter", NULL, &dwType, (LPBYTE)&szFilter, &cbData); // REG_SZ
378     // Result = RegQueryValueExW(hKeyEventVwr, L"Find", NULL, &dwType, (LPBYTE)&szFind, &cbData); // REG_SZ
379     // Result = RegQueryValueExW(hKeyEventVwr, L"Module", NULL, &dwType, (LPBYTE)&szModule, &cbData); // REG_SZ
380 
381     cbData = sizeof(buffer);
382     Result = RegQueryValueExW(hKeyEventVwr, L"SaveSettings", NULL, &dwType, (LPBYTE)buffer, &cbData);
383     if (Result == ERROR_SUCCESS)
384     {
385         if (dwType == REG_SZ)
386         {
387             buffer[cbData / sizeof(WCHAR) - 1] = UNICODE_NULL;
388             Settings.bSaveSettings = !!(DWORD)_wtoi(buffer);
389         }
390         else if (dwType == REG_DWORD && cbData == sizeof(DWORD))
391         {
392             Settings.bSaveSettings = !!*(PDWORD)buffer;
393         }
394     }
395 
396     cbData = sizeof(buffer);
397     Result = RegQueryValueExW(hKeyEventVwr, L"DetailsPane", NULL, &dwType, (LPBYTE)buffer, &cbData);
398     if (Result == ERROR_SUCCESS)
399     {
400         if (dwType == REG_SZ)
401         {
402             buffer[cbData / sizeof(WCHAR) - 1] = UNICODE_NULL;
403             Settings.bShowDetailsPane = !!(DWORD)_wtoi(buffer);
404         }
405         else if (dwType == REG_DWORD && cbData == sizeof(DWORD))
406         {
407             Settings.bShowDetailsPane = !!*(PDWORD)buffer;
408         }
409     }
410 
411     cbData = sizeof(buffer);
412     Result = RegQueryValueExW(hKeyEventVwr, L"ShowGrid", NULL, &dwType, (LPBYTE)buffer, &cbData);
413     if (Result == ERROR_SUCCESS)
414     {
415         if (dwType == REG_SZ)
416         {
417             buffer[cbData / sizeof(WCHAR) - 1] = UNICODE_NULL;
418             Settings.bShowGrid = !!(DWORD)_wtoi(buffer);
419         }
420         else if (dwType == REG_DWORD && cbData == sizeof(DWORD))
421         {
422             Settings.bShowGrid = !!*(PDWORD)buffer;
423         }
424     }
425 
426     cbData = sizeof(buffer);
427     Result = RegQueryValueExW(hKeyEventVwr, L"SortOrder", NULL, &dwType, (LPBYTE)buffer, &cbData);
428     if (Result == ERROR_SUCCESS)
429     {
430         if (dwType == REG_SZ)
431         {
432             buffer[cbData / sizeof(WCHAR) - 1] = UNICODE_NULL;
433             Settings.bNewestEventsFirst = !!(DWORD)_wtoi(buffer);
434         }
435         else if (dwType == REG_DWORD && cbData == sizeof(DWORD))
436         {
437             Settings.bNewestEventsFirst = !!*(PDWORD)buffer;
438         }
439     }
440 
441     /* Retrieve the splitter positions */
442     cbData = sizeof(buffer);
443     Result = RegQueryValueExW(hKeyEventVwr, L"VSplitPos", NULL, &dwType, (LPBYTE)buffer, &cbData);
444     if (Result == ERROR_SUCCESS)
445     {
446         if (dwType == REG_SZ)
447         {
448             buffer[cbData / sizeof(WCHAR) - 1] = UNICODE_NULL;
449             Settings.nVSplitPos = (DWORD)_wtoi(buffer);
450         }
451         else if (dwType == REG_DWORD && cbData == sizeof(DWORD))
452         {
453             Settings.nVSplitPos = *(PDWORD)buffer;
454         }
455     }
456 
457     cbData = sizeof(buffer);
458     Result = RegQueryValueExW(hKeyEventVwr, L"HSplitPos", NULL, &dwType, (LPBYTE)buffer, &cbData);
459     if (Result == ERROR_SUCCESS)
460     {
461         if (dwType == REG_SZ)
462         {
463             buffer[cbData / sizeof(WCHAR) - 1] = UNICODE_NULL;
464             Settings.nHSplitPos = (DWORD)_wtoi(buffer);
465         }
466         else if (dwType == REG_DWORD && cbData == sizeof(DWORD))
467         {
468             Settings.nHSplitPos = *(PDWORD)buffer;
469         }
470     }
471 
472     /* Retrieve the geometry of the main window */
473     cbData = sizeof(buffer);
474     Result = RegQueryValueExW(hKeyEventVwr, L"Window", NULL, &dwType, (LPBYTE)buffer, &cbData);
475     if ((Result == ERROR_SUCCESS) && (dwType == REG_SZ))
476         buffer[cbData / sizeof(WCHAR) - 1] = UNICODE_NULL;
477     else
478         buffer[0] = UNICODE_NULL;
479 
480     if (swscanf(buffer, L"%d %d %d %d %d",
481                 &Settings.wpPos.rcNormalPosition.left,
482                 &Settings.wpPos.rcNormalPosition.top,
483                 &Settings.wpPos.rcNormalPosition.right,
484                 &Settings.wpPos.rcNormalPosition.bottom,
485                 &Settings.wpPos.showCmd) != 5)
486     {
487         /* Parsing failed, use defaults */
488         SetRect(&Settings.wpPos.rcNormalPosition,
489                 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT);
490         Settings.wpPos.showCmd = nDefCmdShow; // SW_SHOWNORMAL;
491     }
492 
493     RegCloseKey(hKeyEventVwr);
494     return TRUE;
495 }
496 
497 BOOL
498 SaveSettings(VOID)
499 {
500     HKEY hKeyEventVwr;
501     DWORD dwSize;
502     WCHAR buffer[100];
503 
504     /* Try to create/open the Event Viewer user key */
505     if (RegCreateKeyExW(HKEY_CURRENT_USER,
506                         EVNTVWR_PARAM_KEY,
507                         0,
508                         NULL,
509                         REG_OPTION_NON_VOLATILE,
510                         KEY_SET_VALUE,
511                         NULL,
512                         &hKeyEventVwr,
513                         NULL) != ERROR_SUCCESS)
514     {
515         return FALSE;
516     }
517 
518     dwSize = sizeof(Settings.bSaveSettings);
519     RegSetValueExW(hKeyEventVwr, L"SaveSettings", 0, REG_DWORD, (LPBYTE)&Settings.bSaveSettings, dwSize);
520 
521     /* Do not save more settings if we are not asked to do so */
522     if (!Settings.bSaveSettings)
523         goto Quit;
524 
525     dwSize = sizeof(Settings.bShowDetailsPane);
526     RegSetValueExW(hKeyEventVwr, L"DetailsPane", 0, REG_DWORD, (LPBYTE)&Settings.bShowDetailsPane, dwSize);
527 
528     dwSize = sizeof(Settings.bShowGrid);
529     RegSetValueExW(hKeyEventVwr, L"ShowGrid", 0, REG_DWORD, (LPBYTE)&Settings.bShowGrid, dwSize);
530 
531     dwSize = sizeof(Settings.bNewestEventsFirst);
532     RegSetValueExW(hKeyEventVwr, L"SortOrder", 0, REG_DWORD, (LPBYTE)&Settings.bNewestEventsFirst, dwSize);
533 
534     Settings.nVSplitPos = nVSplitPos;
535     dwSize = sizeof(Settings.nVSplitPos);
536     RegSetValueExW(hKeyEventVwr, L"VSplitPos", 0, REG_DWORD, (LPBYTE)&Settings.nVSplitPos, dwSize);
537 
538     Settings.nHSplitPos = nHSplitPos;
539     dwSize = sizeof(Settings.nHSplitPos);
540     RegSetValueExW(hKeyEventVwr, L"HSplitPos", 0, REG_DWORD, (LPBYTE)&Settings.nHSplitPos, dwSize);
541 
542     StringCbPrintfW(buffer, sizeof(buffer),
543                     L"%d %d %d %d %d",
544                     Settings.wpPos.rcNormalPosition.left,
545                     Settings.wpPos.rcNormalPosition.top,
546                     Settings.wpPos.rcNormalPosition.right,
547                     Settings.wpPos.rcNormalPosition.bottom,
548                     Settings.wpPos.showCmd);
549 
550     dwSize = (DWORD)((wcslen(buffer) + 1) * sizeof(WCHAR));
551     RegSetValueExW(hKeyEventVwr, L"Window", 0, REG_SZ, (LPBYTE)buffer, dwSize);
552 
553 Quit:
554     RegCloseKey(hKeyEventVwr);
555     return TRUE;
556 }
557 
558 int APIENTRY
559 wWinMain(HINSTANCE hInstance,
560          HINSTANCE hPrevInstance,
561          LPWSTR    lpCmdLine,
562          int       nCmdShow)
563 {
564     HANDLE hThread;
565     INITCOMMONCONTROLSEX iccx;
566     HMODULE hRichEdit;
567     HACCEL hAccelTable;
568     MSG msg;
569 
570     UNREFERENCED_PARAMETER(hPrevInstance);
571     UNREFERENCED_PARAMETER(lpCmdLine);
572 
573     /* Whenever any of the common controls are used in your app,
574      * you must call InitCommonControlsEx() to register the classes
575      * for those controls. */
576     iccx.dwSize = sizeof(iccx);
577     iccx.dwICC = ICC_LISTVIEW_CLASSES;
578     InitCommonControlsEx(&iccx);
579 
580     /* Load the RichEdit DLL to add support for RichEdit controls */
581     hRichEdit = LoadLibraryW(L"riched20.dll");
582     if (!hRichEdit)
583         return -1;
584 
585     msg.wParam = (WPARAM)-1;
586 
587     /* Store the instance handle in the global variable */
588     hInst = hInstance;
589 
590     /* Initialize global strings */
591     LoadStringW(hInstance, IDS_APP_TITLE, szTitle, ARRAYSIZE(szTitle));
592     LoadStringW(hInstance, IDS_APP_TITLE_EX, szTitleTemplate, ARRAYSIZE(szTitleTemplate));
593     LoadStringW(hInstance, IDS_STATUS_MSG, szStatusBarTemplate, ARRAYSIZE(szStatusBarTemplate));
594     LoadStringW(hInstance, IDS_LOADING_WAIT, szLoadingWait, ARRAYSIZE(szLoadingWait));
595     LoadStringW(hInstance, IDS_NO_ITEMS, szEmptyList, ARRAYSIZE(szEmptyList));
596 
597     /*
598      * Process the command-line arguments. Note that we need the full
599      * command-line, with the program file included, and not just what
600      * WinMain() provides in its lpCmdLine parameter.
601      */
602     if (!ProcessCmdLine(GetCommandLineW()))
603         goto Quit;
604 
605     if (!MyRegisterClass(hInstance))
606         goto Quit;
607 
608     /* Load the settings */
609     LoadSettings(nCmdShow);
610 
611     /* Perform application initialization */
612     if (!InitInstance(hInstance))
613         goto Quit;
614 
615     hAccelTable = LoadAcceleratorsW(hInstance, MAKEINTRESOURCEW(IDA_EVENTVWR));
616 
617     /* Create the Start/Stop enumerator thread */
618     // Manual-reset event
619     hStartStopEnumEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
620     if (!hStartStopEnumEvent)
621         goto Cleanup;
622 
623     // Auto-reset event
624     hStartEnumEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
625     if (!hStartEnumEvent)
626         goto Cleanup;
627 
628     hThread = CreateThread(NULL, 0,
629                            StartStopEnumEventsThread,
630                            NULL, 0, NULL);
631     if (!hThread)
632         goto Cleanup;
633 
634     /* Retrieve the available event logs on this computer and create filters for them */
635     InitializeListHead(&EventLogList);
636     InitializeListHead(&EventLogFilterList);
637     BuildLogListAndFilterList(lpComputerName);
638 
639     /* Open the user-specified logs if any are present on the command-line */
640     if (lpszzUserLogsToLoad)
641     {
642         LPWSTR lpUserLog;
643         for (lpUserLog = lpszzUserLogsToLoad; *lpUserLog; lpUserLog += wcslen(lpUserLog) + 1)
644         {
645             OpenUserEventLogFile(lpUserLog);
646         }
647 
648         /* Now cleanup the list of user logs */
649         cbUserLogsSize = 0;
650         HeapFree(GetProcessHeap(), 0, lpszzUserLogsToLoad);
651         lpszzUserLogsToLoad = NULL;
652     }
653 
654     /* Main message loop */
655     while (GetMessageW(&msg, NULL, 0, 0))
656     {
657         if (!TranslateAcceleratorW(hwndMainWindow, hAccelTable, &msg))
658         {
659             TranslateMessage(&msg);
660             DispatchMessageW(&msg);
661         }
662     }
663 
664     /* Save the settings */
665     SaveSettings();
666 
667     /* Disconnect from computer */
668     if (hkMachine && hkMachine != HKEY_LOCAL_MACHINE)
669     {
670         /* We are connected to some other computer, close the old connection */
671         RegCloseKey(hkMachine);
672         hkMachine = NULL;
673     }
674 
675     /* Stop the enumerator thread */
676     SetEvent(hStartStopEnumEvent);
677     WaitForSingleObject(hThread, INFINITE);
678     CloseHandle(hThread);
679 
680     /* Free the filters list and the event logs list */
681     FreeLogFilterList();
682     FreeLogList();
683 
684 Cleanup:
685     /* Handle cleanup */
686     if (hStartEnumEvent)
687         CloseHandle(hStartEnumEvent);
688     if (hStartStopEnumEvent)
689         CloseHandle(hStartStopEnumEvent);
690 
691 Quit:
692     /* Final cleanup */
693     if (lpszzUserLogsToLoad)
694     {
695         cbUserLogsSize = 0;
696         HeapFree(GetProcessHeap(), 0, lpszzUserLogsToLoad);
697         lpszzUserLogsToLoad = NULL;
698     }
699     if (lpComputerName)
700     {
701         HeapFree(GetProcessHeap(), 0, lpComputerName);
702         lpComputerName = NULL;
703     }
704     FreeLibrary(hRichEdit);
705 
706     return (int)msg.wParam;
707 }
708 
709 
710 /* GENERIC HELPER FUNCTIONS ***************************************************/
711 
712 VOID
713 EventTimeToSystemTime(IN DWORD EventTime,
714                       OUT PSYSTEMTIME pSystemTime)
715 {
716     SYSTEMTIME st1970 = { 1970, 1, 0, 1, 0, 0, 0, 0 };
717     FILETIME ftLocal;
718     union
719     {
720         FILETIME ft;
721         ULONGLONG ll;
722     } u1970, uUCT;
723 
724     uUCT.ft.dwHighDateTime = 0;
725     uUCT.ft.dwLowDateTime = EventTime;
726     SystemTimeToFileTime(&st1970, &u1970.ft);
727     uUCT.ll = uUCT.ll * 10000000 + u1970.ll;
728     FileTimeToLocalFileTime(&uUCT.ft, &ftLocal);
729     FileTimeToSystemTime(&ftLocal, pSystemTime);
730 }
731 
732 /*
733  * This function takes in entry a path to a single DLL, in which
734  * the message string of ID dwMessageId has to be searched.
735  * The other parameters are similar to those of the FormatMessageW API.
736  */
737 LPWSTR
738 GetMessageStringFromDll(
739     IN LPCWSTR  lpMessageDll,
740     IN DWORD    dwFlags, // If we always use the same flags, just remove this param...
741     IN DWORD    dwMessageId,
742     IN DWORD    nSize,
743     IN va_list* Arguments OPTIONAL)
744 {
745     HMODULE hLibrary;
746     DWORD dwLength;
747     LPWSTR lpMsgBuf = NULL;
748 
749     hLibrary = LoadLibraryExW(lpMessageDll, NULL,
750                               /* LOAD_LIBRARY_AS_IMAGE_RESOURCE | */ LOAD_LIBRARY_AS_DATAFILE);
751     if (hLibrary == NULL)
752         return NULL;
753 
754     /* Sanitize dwFlags */
755     dwFlags &= ~FORMAT_MESSAGE_FROM_STRING;
756     dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;
757 
758     _SEH2_TRY
759     {
760         /*
761          * Retrieve the message string without appending extra newlines.
762          * Wrap in SEH to protect from invalid string parameters.
763          */
764         _SEH2_TRY
765         {
766             dwLength = FormatMessageW(dwFlags,
767                                    /* FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE |
768                                       FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, */
769                                       hLibrary,
770                                       dwMessageId,
771                                       LANG_USER_DEFAULT,
772                                       (LPWSTR)&lpMsgBuf,
773                                       nSize,
774                                       Arguments);
775         }
776         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
777         {
778             dwLength = 0;
779 
780             /*
781              * An exception occurred while calling FormatMessage, this is usually
782              * the sign that a parameter was invalid, either 'lpMsgBuf' was NULL
783              * but we did not pass the flag FORMAT_MESSAGE_ALLOCATE_BUFFER, or the
784              * array pointer 'Arguments' was NULL or did not contain enough elements,
785              * and we did not pass the flag FORMAT_MESSAGE_IGNORE_INSERTS, and the
786              * message string expected too many inserts.
787              * In this last case only, we can call again FormatMessage but ignore
788              * explicitly the inserts. The string that we will return to the user
789              * will not be pre-formatted.
790              */
791             if (((dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) || lpMsgBuf) &&
792                 !(dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS))
793             {
794                 /* Remove any possible harmful flags and always ignore inserts */
795                 dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY;
796                 dwFlags |=  FORMAT_MESSAGE_IGNORE_INSERTS;
797 
798                 /* If this call also throws an exception, we are really dead */
799                 dwLength = FormatMessageW(dwFlags,
800                                           hLibrary,
801                                           dwMessageId,
802                                           LANG_USER_DEFAULT,
803                                           (LPWSTR)&lpMsgBuf,
804                                           nSize,
805                                           NULL /* Arguments */);
806             }
807         }
808         _SEH2_END;
809     }
810     _SEH2_FINALLY
811     {
812         FreeLibrary(hLibrary);
813     }
814     _SEH2_END;
815 
816     if (dwLength == 0)
817     {
818         ASSERT(lpMsgBuf == NULL);
819         lpMsgBuf = NULL;
820     }
821     else
822     {
823         LPWSTR ptr;
824 
825         ASSERT(lpMsgBuf);
826 
827         /* Trim any trailing whitespace */
828         ptr = lpMsgBuf + dwLength - 1;
829         while (iswspace(*ptr))
830             *ptr-- = UNICODE_NULL;
831     }
832 
833     return lpMsgBuf;
834 }
835 
836 /*
837  * This function takes in entry a comma-separated list of DLLs, in which
838  * the message string of ID dwMessageId has to be searched.
839  * The other parameters are similar to those of the FormatMessageW API.
840  */
841 LPWSTR
842 GetMessageStringFromDllList(
843     IN LPCWSTR  lpMessageDllList,
844     IN DWORD    dwFlags, // If we always use the same flags, just remove this param...
845     IN DWORD    dwMessageId,
846     IN DWORD    nSize,
847     IN va_list* Arguments OPTIONAL)
848 {
849     BOOL Success = FALSE;
850     SIZE_T cbLength;
851     LPWSTR szMessageDllList;
852     LPWSTR szDll;
853     LPWSTR lpMsgBuf = NULL;
854 
855     /* Allocate a local buffer for the DLL list that can be tokenized */
856     // TODO: Optimize that!! Maybe we can cleverly use lpMessageDllList in read/write mode
857     // and cleverly temporarily replace the ';' by UNICODE_NULL, do our job, then reverse the change.
858     cbLength = (wcslen(lpMessageDllList) + 1) * sizeof(WCHAR);
859     szMessageDllList = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbLength);
860     if (!szMessageDllList)
861         return NULL;
862     CopyMemory(szMessageDllList, lpMessageDllList, cbLength);
863 
864     /* Loop through the list of message DLLs */
865     szDll = wcstok(szMessageDllList, EVENT_DLL_SEPARATOR);
866     while ((szDll != NULL) && !Success)
867     {
868         // Uses LANG_USER_DEFAULT
869         lpMsgBuf = GetMessageStringFromDll(szDll,
870                                            dwFlags,
871                                            dwMessageId,
872                                            nSize,
873                                            Arguments);
874         if (lpMsgBuf)
875         {
876             /* The ID was found and the message was formatted */
877             Success = TRUE;
878             break;
879         }
880 
881         /*
882          * The DLL could not be loaded, or the message could not be found,
883          * try the next DLL, if any.
884          */
885         szDll = wcstok(NULL, EVENT_DLL_SEPARATOR);
886     }
887 
888     HeapFree(GetProcessHeap(), 0, szMessageDllList);
889 
890     return lpMsgBuf;
891 }
892 
893 
894 typedef struct
895 {
896     LPWSTR pStartingAddress;    // Pointer to the beginning of a parameter string in pMessage
897     LPWSTR pEndingAddress;      // Pointer to the end of a parameter string in pMessage
898     DWORD  pParameterID;        // Parameter identifier found in pMessage
899     LPWSTR pParameter;          // Actual parameter string
900 } param_strings_format_data;
901 
902 DWORD
903 ApplyParameterStringsToMessage(
904     IN LPCWSTR  lpMessageDllList,
905     IN BOOL     bMessagePreFormatted,
906     IN CONST LPCWSTR pMessage,
907     OUT LPWSTR* pFinalMessage)
908 {
909     /*
910      * This code is heavily adapted from the MSDN example:
911      * https://msdn.microsoft.com/en-us/library/windows/desktop/bb427356.aspx
912      * with bugs removed.
913      */
914 
915     DWORD Status = ERROR_SUCCESS;
916     DWORD dwParamCount = 0; // Number of insertion strings found in pMessage
917     size_t cchBuffer = 0;   // Size of the buffer in characters
918     size_t cchParams = 0;   // Number of characters in all the parameter strings
919     size_t cch = 0;
920     DWORD i = 0;
921     param_strings_format_data* pParamData = NULL; // Array of pointers holding information about each parameter string in pMessage
922     LPWSTR pTempMessage = (LPWSTR)pMessage;
923     LPWSTR pTempFinalMessage = NULL;
924 
925     *pFinalMessage = NULL;
926 
927     /* Determine the number of parameter insertion strings in pMessage */
928     if (bMessagePreFormatted)
929     {
930         while ((pTempMessage = wcschr(pTempMessage, L'%')))
931         {
932             pTempMessage++;
933             if (iswdigit(*pTempMessage))
934             {
935                 dwParamCount++;
936                 while (iswdigit(*++pTempMessage)) ;
937             }
938         }
939     }
940     else
941     {
942         while ((pTempMessage = wcsstr(pTempMessage, L"%%")))
943         {
944             pTempMessage += 2;
945             if (iswdigit(*pTempMessage))
946             {
947                 dwParamCount++;
948                 while (iswdigit(*++pTempMessage)) ;
949             }
950         }
951     }
952 
953     /* If there are no parameter insertion strings in pMessage, just return */
954     if (dwParamCount == 0)
955     {
956         // *pFinalMessage = NULL;
957         goto Cleanup;
958     }
959 
960     /* Allocate the array of parameter string format data */
961     pParamData = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwParamCount * sizeof(param_strings_format_data));
962     if (!pParamData)
963     {
964         Status = ERROR_OUTOFMEMORY;
965         goto Cleanup;
966     }
967 
968     /*
969      * Retrieve each parameter in pMessage and the beginning and end of the
970      * insertion string, as well as the message identifier of the parameter.
971      */
972     pTempMessage = (LPWSTR)pMessage;
973     if (bMessagePreFormatted)
974     {
975         while ((pTempMessage = wcschr(pTempMessage, L'%')) && (i < dwParamCount))
976         {
977             pTempMessage++;
978             if (iswdigit(*pTempMessage))
979             {
980                 pParamData[i].pStartingAddress = pTempMessage-1;
981                 pParamData[i].pParameterID = (DWORD)_wtol(pTempMessage);
982 
983                 while (iswdigit(*++pTempMessage)) ;
984 
985                 pParamData[i].pEndingAddress = pTempMessage;
986                 i++;
987             }
988         }
989     }
990     else
991     {
992         while ((pTempMessage = wcsstr(pTempMessage, L"%%")) && (i < dwParamCount))
993         {
994             pTempMessage += 2;
995             if (iswdigit(*pTempMessage))
996             {
997                 pParamData[i].pStartingAddress = pTempMessage-2;
998                 pParamData[i].pParameterID = (DWORD)_wtol(pTempMessage);
999 
1000                 while (iswdigit(*++pTempMessage)) ;
1001 
1002                 pParamData[i].pEndingAddress = pTempMessage;
1003                 i++;
1004             }
1005         }
1006     }
1007 
1008     /* Retrieve each parameter string */
1009     for (i = 0; i < dwParamCount; i++)
1010     {
1011         // pParamData[i].pParameter = GetMessageString(pParamData[i].pParameterID, 0, NULL);
1012         pParamData[i].pParameter =
1013             GetMessageStringFromDllList(lpMessageDllList,
1014                                         FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE |
1015                                         FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK,
1016                                         pParamData[i].pParameterID,
1017                                         0, NULL);
1018         if (!pParamData[i].pParameter)
1019         {
1020             /* Skip the insertion string */
1021             continue;
1022         }
1023 
1024         cchParams += wcslen(pParamData[i].pParameter);
1025     }
1026 
1027     /*
1028      * Allocate the final message buffer, the size of which is based on the
1029      * length of the original message and the length of each parameter string.
1030      */
1031     cchBuffer = wcslen(pMessage) + cchParams + 1;
1032     *pFinalMessage = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cchBuffer * sizeof(WCHAR));
1033     if (!*pFinalMessage)
1034     {
1035         Status = ERROR_OUTOFMEMORY;
1036         goto Cleanup;
1037     }
1038 
1039     pTempFinalMessage = *pFinalMessage;
1040 
1041     /* Build the final message string */
1042     pTempMessage = (LPWSTR)pMessage;
1043     for (i = 0; i < dwParamCount; i++)
1044     {
1045         /* Append the segment from pMessage */
1046         cch = pParamData[i].pStartingAddress - pTempMessage;
1047         StringCchCopyNW(pTempFinalMessage, cchBuffer, pTempMessage, cch);
1048         pTempMessage = pParamData[i].pEndingAddress;
1049         cchBuffer -= cch;
1050         pTempFinalMessage += cch;
1051 
1052         /* Append the parameter string */
1053         if (pParamData[i].pParameter)
1054         {
1055             StringCchCopyW(pTempFinalMessage, cchBuffer, pParamData[i].pParameter);
1056             cch = wcslen(pParamData[i].pParameter); // pTempFinalMessage
1057         }
1058         else
1059         {
1060             /*
1061              * We failed to retrieve the parameter string before, so just
1062              * place back the original string placeholder.
1063              */
1064             cch = pParamData[i].pEndingAddress /* == pTempMessage */ - pParamData[i].pStartingAddress;
1065             StringCchCopyNW(pTempFinalMessage, cchBuffer, pParamData[i].pStartingAddress, cch);
1066             // cch = wcslen(pTempFinalMessage);
1067         }
1068         cchBuffer -= cch;
1069         pTempFinalMessage += cch;
1070     }
1071 
1072     /* Append the last segment from pMessage */
1073     StringCchCopyW(pTempFinalMessage, cchBuffer, pTempMessage);
1074 
1075 Cleanup:
1076 
1077     // if (Status != ERROR_SUCCESS)
1078         // *pFinalMessage = NULL;
1079 
1080     if (pParamData)
1081     {
1082         for (i = 0; i < dwParamCount; i++)
1083         {
1084             if (pParamData[i].pParameter)
1085                 LocalFree(pParamData[i].pParameter);
1086         }
1087 
1088         HeapFree(GetProcessHeap(), 0, pParamData);
1089     }
1090 
1091     return Status;
1092 }
1093 
1094 
1095 /*
1096  * The following functions were adapted from
1097  * shell32!dialogs/filedefext.cpp:``SH_...'' functions.
1098  */
1099 
1100 UINT
1101 FormatInteger(LONGLONG Num, LPWSTR pwszResult, UINT cchResultMax)
1102 {
1103     WCHAR wszNumber[24];
1104     WCHAR wszDecimalSep[8], wszThousandSep[8];
1105     NUMBERFMTW nf;
1106     WCHAR wszGrouping[12];
1107     INT cchGrouping;
1108     INT cchResult;
1109     INT i;
1110 
1111     // Print the number in uniform mode
1112     swprintf(wszNumber, L"%I64u", Num);
1113 
1114     // Get system strings for decimal and thousand separators.
1115     GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, wszDecimalSep, _countof(wszDecimalSep));
1116     GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, wszThousandSep, _countof(wszThousandSep));
1117 
1118     // Initialize format for printing the number in bytes
1119     ZeroMemory(&nf, sizeof(nf));
1120     nf.lpDecimalSep = wszDecimalSep;
1121     nf.lpThousandSep = wszThousandSep;
1122 
1123     // Get system string for groups separator
1124     cchGrouping = GetLocaleInfoW(LOCALE_USER_DEFAULT,
1125                                  LOCALE_SGROUPING,
1126                                  wszGrouping,
1127                                  _countof(wszGrouping));
1128 
1129     // Convert grouping specs from string to integer
1130     for (i = 0; i < cchGrouping; i++)
1131     {
1132         WCHAR wch = wszGrouping[i];
1133 
1134         if (wch >= L'0' && wch <= L'9')
1135             nf.Grouping = nf.Grouping * 10 + (wch - L'0');
1136         else if (wch != L';')
1137             break;
1138     }
1139 
1140     if ((nf.Grouping % 10) == 0)
1141         nf.Grouping /= 10;
1142     else
1143         nf.Grouping *= 10;
1144 
1145     // Format the number
1146     cchResult = GetNumberFormatW(LOCALE_USER_DEFAULT,
1147                                  0,
1148                                  wszNumber,
1149                                  &nf,
1150                                  pwszResult,
1151                                  cchResultMax);
1152 
1153     if (!cchResult)
1154         return 0;
1155 
1156     // GetNumberFormatW returns number of characters including UNICODE_NULL
1157     return cchResult - 1;
1158 }
1159 
1160 UINT
1161 FormatByteSize(LONGLONG cbSize, LPWSTR pwszResult, UINT cchResultMax)
1162 {
1163     UINT cchWritten, cchRemaining;
1164     LPWSTR pwszEnd;
1165     size_t cchStringRemaining;
1166 
1167     /* Write formated bytes count */
1168     cchWritten = FormatInteger(cbSize, pwszResult, cchResultMax);
1169     if (!cchWritten)
1170         return 0;
1171 
1172     /* Copy " bytes" to buffer */
1173     pwszEnd = pwszResult + cchWritten;
1174     cchRemaining = cchResultMax - cchWritten;
1175     StringCchCopyExW(pwszEnd, cchRemaining, L" ", &pwszEnd, &cchStringRemaining, 0);
1176     cchRemaining = (UINT)cchStringRemaining;
1177     cchWritten = LoadStringW(hInst, IDS_BYTES_FORMAT, pwszEnd, cchRemaining);
1178     cchRemaining -= cchWritten;
1179 
1180     return cchResultMax - cchRemaining;
1181 }
1182 
1183 LPWSTR
1184 FormatFileSizeWithBytes(const PULARGE_INTEGER lpQwSize, LPWSTR pwszResult, UINT cchResultMax)
1185 {
1186     UINT cchWritten, cchRemaining;
1187     LPWSTR pwszEnd;
1188     size_t cchCopyRemaining;
1189 
1190     /* Format bytes in KBs, MBs etc */
1191     if (StrFormatByteSizeW(lpQwSize->QuadPart, pwszResult, cchResultMax) == NULL)
1192         return NULL;
1193 
1194     /* If there is less bytes than 1KB, we have nothing to do */
1195     if (lpQwSize->QuadPart < 1024)
1196         return pwszResult;
1197 
1198     /* Concatenate " (" */
1199     cchWritten = (UINT)wcslen(pwszResult);
1200     pwszEnd = pwszResult + cchWritten;
1201     cchRemaining = cchResultMax - cchWritten;
1202     StringCchCopyExW(pwszEnd, cchRemaining, L" (", &pwszEnd, &cchCopyRemaining, 0);
1203     cchRemaining = (UINT)cchCopyRemaining;
1204 
1205     /* Write formated bytes count */
1206     cchWritten = FormatByteSize(lpQwSize->QuadPart, pwszEnd, cchRemaining);
1207     pwszEnd += cchWritten;
1208     cchRemaining -= cchWritten;
1209 
1210     /* Copy ")" to the buffer */
1211     StringCchCopyW(pwszEnd, cchRemaining, L")");
1212 
1213     return pwszResult;
1214 }
1215 
1216 /* Adapted from shell32!dialogs/filedefext.cpp:``CFileDefExt::GetFileTimeString'' */
1217 BOOL
1218 GetFileTimeString(LPFILETIME lpFileTime, LPWSTR pwszResult, UINT cchResult)
1219 {
1220     FILETIME ft;
1221     SYSTEMTIME st;
1222     int cchWritten;
1223     UINT cchRemaining = cchResult;
1224     size_t cchCopyRemaining;
1225     LPWSTR pwszEnd = pwszResult;
1226 
1227     if (!FileTimeToLocalFileTime(lpFileTime, &ft) || !FileTimeToSystemTime(&ft, &st))
1228         return FALSE;
1229 
1230     cchWritten = GetDateFormatW(LOCALE_USER_DEFAULT, DATE_LONGDATE, &st, NULL, pwszEnd, cchRemaining);
1231     if (cchWritten)
1232         --cchWritten; // GetDateFormatW returns count with terminating zero
1233     // else
1234         // ERR("GetDateFormatW failed\n");
1235 
1236     cchRemaining -= cchWritten;
1237     pwszEnd += cchWritten;
1238 
1239     StringCchCopyExW(pwszEnd, cchRemaining, L", ", &pwszEnd, &cchCopyRemaining, 0);
1240     cchRemaining = (UINT)cchCopyRemaining;
1241 
1242     cchWritten = GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL, pwszEnd, cchRemaining);
1243     if (cchWritten)
1244         --cchWritten; // GetTimeFormatW returns count with terminating zero
1245     // else
1246         // ERR("GetTimeFormatW failed\n");
1247 
1248     return TRUE;
1249 }
1250 
1251 
1252 HTREEITEM
1253 TreeViewAddItem(IN HWND hTreeView,
1254                 IN HTREEITEM hParent,
1255                 IN LPWSTR lpText,
1256                 IN INT Image,
1257                 IN INT SelectedImage,
1258                 IN LPARAM lParam)
1259 {
1260     TV_INSERTSTRUCTW Insert;
1261 
1262     ZeroMemory(&Insert, sizeof(Insert));
1263 
1264     Insert.item.mask = TVIF_TEXT | TVIF_PARAM | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
1265     Insert.hInsertAfter = TVI_LAST;
1266     Insert.hParent = hParent;
1267     Insert.item.pszText = lpText;
1268     Insert.item.iImage = Image;
1269     Insert.item.iSelectedImage = SelectedImage;
1270     Insert.item.lParam = lParam;
1271 
1272     Insert.item.mask |= TVIF_STATE;
1273     Insert.item.stateMask = TVIS_OVERLAYMASK;
1274     Insert.item.state = INDEXTOOVERLAYMASK(1);
1275 
1276     return TreeView_InsertItem(hTreeView, &Insert);
1277 }
1278 
1279 
1280 /* LOG HELPER FUNCTIONS *******************************************************/
1281 
1282 PEVENTLOG
1283 AllocEventLog(IN PCWSTR ComputerName OPTIONAL,
1284               IN PCWSTR LogName,
1285               IN BOOL Permanent)
1286 {
1287     PEVENTLOG EventLog;
1288     SIZE_T cchName;
1289 
1290     /* Allocate a new event log entry */
1291     EventLog = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*EventLog));
1292     if (!EventLog)
1293         return NULL;
1294 
1295     /* Allocate the computer name string (optional) and copy it */
1296     if (ComputerName)
1297     {
1298         cchName = wcslen(ComputerName) + 1;
1299         EventLog->ComputerName = HeapAlloc(GetProcessHeap(), 0, cchName * sizeof(WCHAR));
1300         if (EventLog->ComputerName)
1301             StringCchCopyW(EventLog->ComputerName, cchName, ComputerName);
1302     }
1303 
1304     /* Allocate the event log name string and copy it */
1305     cchName = wcslen(LogName) + 1;
1306     EventLog->LogName = HeapAlloc(GetProcessHeap(), 0, cchName * sizeof(WCHAR));
1307     if (!EventLog->LogName)
1308     {
1309         if (EventLog->ComputerName)
1310             HeapFree(GetProcessHeap(), 0, EventLog->ComputerName);
1311         HeapFree(GetProcessHeap(), 0, EventLog);
1312         return NULL;
1313     }
1314     StringCchCopyW(EventLog->LogName, cchName, LogName);
1315 
1316     EventLog->Permanent = Permanent;
1317 
1318     return EventLog;
1319 }
1320 
1321 VOID
1322 EventLog_Free(IN PEVENTLOG EventLog)
1323 {
1324     if (EventLog->LogName)
1325         HeapFree(GetProcessHeap(), 0, EventLog->LogName);
1326 
1327     if (EventLog->ComputerName)
1328         HeapFree(GetProcessHeap(), 0, EventLog->ComputerName);
1329 
1330     if (EventLog->FileName)
1331         HeapFree(GetProcessHeap(), 0, EventLog->FileName);
1332 
1333     HeapFree(GetProcessHeap(), 0, EventLog);
1334 }
1335 
1336 
1337 PWSTR
1338 AllocAndCopyMultiStr(IN PCWSTR MultiStr OPTIONAL)
1339 {
1340     PWSTR pStr;
1341     ULONG Length;
1342 
1343     if (!MultiStr)
1344         return NULL;
1345 
1346     pStr = (PWSTR)MultiStr;
1347     while (*pStr) pStr += (wcslen(pStr) + 1);
1348     Length = MultiStr - pStr + 2;
1349 
1350     pStr = HeapAlloc(GetProcessHeap(), 0, Length * sizeof(WCHAR));
1351     // NOTE: If we failed allocating the string, then fall back into no filter!
1352     if (pStr)
1353         CopyMemory(pStr, MultiStr, Length * sizeof(WCHAR));
1354 
1355     return pStr;
1356 }
1357 
1358 PEVENTLOGFILTER
1359 AllocEventLogFilter(// IN PCWSTR FilterName,
1360                     IN BOOL Information,
1361                     IN BOOL Warning,
1362                     IN BOOL Error,
1363                     IN BOOL AuditSuccess,
1364                     IN BOOL AuditFailure,
1365                     IN PCWSTR Sources OPTIONAL,
1366                     IN PCWSTR Users OPTIONAL,
1367                     IN PCWSTR ComputerNames OPTIONAL,
1368                     IN ULONG NumOfEventLogs,
1369                     IN PEVENTLOG* EventLogs)
1370 {
1371     PEVENTLOGFILTER EventLogFilter;
1372 
1373     /* Allocate a new event log filter entry, big enough to accommodate the list of logs */
1374     EventLogFilter = HeapAlloc(GetProcessHeap(),
1375                                HEAP_ZERO_MEMORY,
1376                                FIELD_OFFSET(EVENTLOGFILTER, EventLogs[NumOfEventLogs]));
1377     if (!EventLogFilter)
1378         return NULL;
1379 
1380     EventLogFilter->Information  = Information;
1381     EventLogFilter->Warning      = Warning;
1382     EventLogFilter->Error        = Error;
1383     EventLogFilter->AuditSuccess = AuditSuccess;
1384     EventLogFilter->AuditFailure = AuditFailure;
1385 
1386     /* Allocate and copy the sources, users, and computers multi-strings */
1387     EventLogFilter->Sources = AllocAndCopyMultiStr(Sources);
1388     EventLogFilter->Users   = AllocAndCopyMultiStr(Users);
1389     EventLogFilter->ComputerNames = AllocAndCopyMultiStr(ComputerNames);
1390 
1391     /* Copy the list of event logs */
1392     EventLogFilter->NumOfEventLogs = NumOfEventLogs;
1393     CopyMemory(EventLogFilter->EventLogs, EventLogs, NumOfEventLogs * sizeof(PEVENTLOG));
1394 
1395     /* Initialize the filter reference count */
1396     EventLogFilter->ReferenceCount = 1;
1397 
1398     return EventLogFilter;
1399 }
1400 
1401 VOID
1402 EventLogFilter_Free(IN PEVENTLOGFILTER EventLogFilter)
1403 {
1404     if (EventLogFilter->Sources)
1405         HeapFree(GetProcessHeap(), 0, EventLogFilter->Sources);
1406 
1407     if (EventLogFilter->Users)
1408         HeapFree(GetProcessHeap(), 0, EventLogFilter->Users);
1409 
1410     if (EventLogFilter->ComputerNames)
1411         HeapFree(GetProcessHeap(), 0, EventLogFilter->ComputerNames);
1412 
1413     HeapFree(GetProcessHeap(), 0, EventLogFilter);
1414 }
1415 
1416 LONG EventLogFilter_AddRef(IN PEVENTLOGFILTER EventLogFilter)
1417 {
1418     ASSERT(EventLogFilter);
1419     return InterlockedIncrement(&EventLogFilter->ReferenceCount);
1420 }
1421 
1422 LONG EventLogFilter_Release(IN PEVENTLOGFILTER EventLogFilter)
1423 {
1424     LONG RefCount;
1425 
1426     ASSERT(EventLogFilter);
1427 
1428     /* When the reference count reaches zero, delete the filter */
1429     RefCount = InterlockedDecrement(&EventLogFilter->ReferenceCount);
1430     if (RefCount <= 0)
1431     {
1432         /* Remove the filter from the list */
1433         /** RemoveEntryList(&EventLogFilter->ListEntry); **/
1434         EventLogFilter_Free(EventLogFilter);
1435     }
1436 
1437     return RefCount;
1438 }
1439 
1440 void
1441 TrimNulls(LPWSTR s)
1442 {
1443     WCHAR *c;
1444 
1445     if (s != NULL)
1446     {
1447         c = s + wcslen(s) - 1;
1448         while (c >= s && iswspace(*c))
1449             --c;
1450         *++c = L'\0';
1451     }
1452 }
1453 
1454 DWORD
1455 GetExpandedFilePathName(
1456     IN LPCWSTR ComputerName OPTIONAL,
1457     IN LPCWSTR lpFileName,
1458     OUT LPWSTR lpFullFileName OPTIONAL,
1459     IN DWORD nSize)
1460 {
1461     SIZE_T dwLength;
1462 
1463     /* Determine the needed size after expansion of any environment strings */
1464     dwLength = ExpandEnvironmentStringsW(lpFileName, NULL, 0);
1465     if (dwLength == 0)
1466     {
1467         /* We failed, bail out */
1468         return 0;
1469     }
1470 
1471     /* If the file path is on a remote computer, estimate its length */
1472     // FIXME: Use WNetGetUniversalName instead?
1473     if (ComputerName && *ComputerName)
1474     {
1475         /* Skip any leading backslashes */
1476         while (*ComputerName == L'\\')
1477             ++ComputerName;
1478 
1479         if (*ComputerName)
1480         {
1481             /* Count 2 backslashes plus the computer name and one backslash separator */
1482             dwLength += 2 + wcslen(ComputerName) + 1;
1483         }
1484     }
1485 
1486     /* Check whether we have enough space */
1487     if (dwLength > nSize)
1488     {
1489         /* No, return the needed size in characters (includes NULL-terminator) */
1490         return dwLength;
1491     }
1492 
1493 
1494     /* Now expand the file path */
1495     ASSERT(dwLength <= nSize);
1496 
1497     /* Expand any existing environment strings */
1498     if (ExpandEnvironmentStringsW(lpFileName, lpFullFileName, dwLength) == 0)
1499     {
1500         /* We failed, bail out */
1501         return 0;
1502     }
1503 
1504     /* If the file path is on a remote computer, retrieve the network share form of the file name */
1505     // FIXME: Use WNetGetUniversalName instead?
1506     if (ComputerName && *ComputerName)
1507     {
1508         /* Note that we previously skipped any potential leading backslashes */
1509 
1510         /* Replace ':' by '$' in the drive letter */
1511         if (*lpFullFileName && lpFullFileName[1] == L':')
1512             lpFullFileName[1] = L'$';
1513 
1514         /* Prepend the computer name */
1515         MoveMemory(lpFullFileName + 2 + wcslen(ComputerName) + 1,
1516                    lpFullFileName, dwLength * sizeof(WCHAR) - (2 + wcslen(ComputerName) + 1) * sizeof(WCHAR));
1517         lpFullFileName[0] = L'\\';
1518         lpFullFileName[1] = L'\\';
1519         wcsncpy(lpFullFileName + 2, ComputerName, wcslen(ComputerName));
1520         lpFullFileName[2 + wcslen(ComputerName)] = L'\\';
1521     }
1522 
1523     /* Return the number of stored characters (includes NULL-terminator) */
1524     return dwLength;
1525 }
1526 
1527 BOOL
1528 GetEventMessageFileDLL(IN LPCWSTR lpLogName,
1529                        IN LPCWSTR SourceName,
1530                        IN LPCWSTR EntryName,
1531                        OUT PWCHAR lpModuleName) // TODO: Add IN DWORD BufLen
1532 {
1533     BOOL Success = FALSE;
1534     LONG Result;
1535     DWORD dwType, dwSize;
1536     WCHAR szModuleName[MAX_PATH];
1537     WCHAR szKeyName[MAX_PATH];
1538     HKEY hLogKey = NULL;
1539     HKEY hSourceKey = NULL;
1540 
1541     StringCbCopyW(szKeyName, sizeof(szKeyName), EVENTLOG_BASE_KEY);
1542     StringCbCatW(szKeyName, sizeof(szKeyName), lpLogName);
1543 
1544     Result = RegOpenKeyExW(hkMachine,
1545                            szKeyName,
1546                            0,
1547                            KEY_READ,
1548                            &hLogKey);
1549     if (Result != ERROR_SUCCESS)
1550         return FALSE;
1551 
1552     Result = RegOpenKeyExW(hLogKey,
1553                            SourceName,
1554                            0,
1555                            KEY_QUERY_VALUE,
1556                            &hSourceKey);
1557     if (Result != ERROR_SUCCESS)
1558     {
1559         RegCloseKey(hLogKey);
1560         return FALSE;
1561     }
1562 
1563     dwSize = sizeof(szModuleName);
1564     Result = RegQueryValueExW(hSourceKey,
1565                               EntryName,
1566                               NULL,
1567                               &dwType,
1568                               (LPBYTE)szModuleName,
1569                               &dwSize);
1570     if ((Result != ERROR_SUCCESS) || (dwType != REG_EXPAND_SZ && dwType != REG_SZ))
1571     {
1572         szModuleName[0] = UNICODE_NULL;
1573     }
1574     else
1575     {
1576         /* NULL-terminate the string and expand it */
1577         szModuleName[dwSize / sizeof(WCHAR) - 1] = UNICODE_NULL;
1578         GetExpandedFilePathName(lpComputerName, szModuleName, lpModuleName, ARRAYSIZE(szModuleName));
1579         Success = TRUE;
1580     }
1581 
1582     RegCloseKey(hSourceKey);
1583     RegCloseKey(hLogKey);
1584 
1585     return Success;
1586 }
1587 
1588 BOOL
1589 GetEventCategory(IN LPCWSTR KeyName,
1590                  IN LPCWSTR SourceName,
1591                  IN PEVENTLOGRECORD pevlr,
1592                  OUT PWCHAR CategoryName) // TODO: Add IN DWORD BufLen
1593 {
1594     BOOL Success = FALSE;
1595     WCHAR szMessageDLL[MAX_PATH];
1596     LPWSTR lpMsgBuf = NULL;
1597 
1598     if (!GetEventMessageFileDLL(KeyName, SourceName, EVENT_CATEGORY_MESSAGE_FILE, szMessageDLL))
1599         goto Quit;
1600 
1601     /* Retrieve the message string without appending extra newlines */
1602     lpMsgBuf =
1603     GetMessageStringFromDllList(szMessageDLL,
1604                                 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE |
1605                                 FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK,
1606                                 pevlr->EventCategory,
1607                                 EVENT_MESSAGE_FILE_BUFFER,
1608                                 NULL);
1609     if (lpMsgBuf)
1610     {
1611         /* Trim the string */
1612         TrimNulls(lpMsgBuf);
1613 
1614         /* Copy the category name */
1615         StringCchCopyW(CategoryName, MAX_PATH, lpMsgBuf);
1616 
1617         /* Free the buffer allocated by FormatMessage */
1618         LocalFree(lpMsgBuf);
1619 
1620         /* The ID was found and the message was formatted */
1621         Success = TRUE;
1622     }
1623 
1624 Quit:
1625     if (!Success)
1626     {
1627         if (pevlr->EventCategory != 0)
1628         {
1629             StringCchPrintfW(CategoryName, MAX_PATH, L"(%lu)", pevlr->EventCategory);
1630             Success = TRUE;
1631         }
1632     }
1633 
1634     return Success;
1635 }
1636 
1637 
1638 BOOL                                                                        // NOTE: Used by evtdetctl.c
1639 GetEventMessage(IN LPCWSTR KeyName,
1640                 IN LPCWSTR SourceName,
1641                 IN PEVENTLOGRECORD pevlr,
1642                 OUT PWCHAR EventText) // TODO: Add IN DWORD BufLen
1643 {
1644     BOOL Success = FALSE;
1645     DWORD i;
1646     size_t cch;
1647     WCHAR SourceModuleName[1024];
1648     WCHAR ParameterModuleName[1024];
1649     BOOL IsParamModNameCached = FALSE;
1650     LPWSTR lpMsgBuf = NULL;
1651     LPWSTR szStringArray, szMessage;
1652     LPWSTR *szArguments;
1653 
1654     /* Get the event string array */
1655     szStringArray = (LPWSTR)((LPBYTE)pevlr + pevlr->StringOffset);
1656 
1657     /* NOTE: GetEventMessageFileDLL can return a comma-separated list of DLLs */
1658     if (!GetEventMessageFileDLL(KeyName, SourceName, EVENT_MESSAGE_FILE, SourceModuleName))
1659         goto Quit;
1660 
1661     /* Allocate space for insertion strings */
1662     szArguments = HeapAlloc(GetProcessHeap(), 0, pevlr->NumStrings * sizeof(LPVOID));
1663     if (!szArguments)
1664         goto Quit;
1665 
1666     if (!IsParamModNameCached)
1667     {
1668         /* Now that the parameter file list is loaded, no need to reload it at the next run! */
1669         IsParamModNameCached = GetEventMessageFileDLL(KeyName, SourceName, EVENT_PARAMETER_MESSAGE_FILE, ParameterModuleName);
1670         // FIXME: If the string loading failed the first time, no need to retry it just after???
1671     }
1672 
1673     if (IsParamModNameCached)
1674     {
1675         /* Not yet support for reading messages from parameter message DLL */
1676     }
1677 
1678     szMessage = szStringArray;
1679     /*
1680      * HACK:
1681      * We do some hackish preformatting of the cached event strings...
1682      * That's because after we pass the string to FormatMessage
1683      * (via GetMessageStringFromDllList) with the FORMAT_MESSAGE_ARGUMENT_ARRAY
1684      * flag, instead of ignoring the insertion parameters and do the formatting
1685      * by ourselves. Therefore, the resulting string should have the parameter
1686      * string placeholders starting with a single '%' instead of a mix of one
1687      * and two '%'.
1688      */
1689     /* HACK part 1: Compute the full length of the string array */
1690     cch = 0;
1691     for (i = 0; i < pevlr->NumStrings; i++)
1692     {
1693         szMessage += wcslen(szMessage) + 1;
1694     }
1695     cch = szMessage - szStringArray;
1696 
1697     /* HACK part 2: Now do the HACK proper! */
1698     szMessage = szStringArray;
1699     for (i = 0; i < pevlr->NumStrings; i++)
1700     {
1701         lpMsgBuf = szMessage;
1702         while ((lpMsgBuf = wcsstr(lpMsgBuf, L"%%")))
1703         {
1704             if (iswdigit(lpMsgBuf[2]))
1705             {
1706                 MoveMemory(lpMsgBuf, lpMsgBuf+1, ((szStringArray + cch) - lpMsgBuf - 1) * sizeof(WCHAR));
1707             }
1708         }
1709 
1710         szArguments[i] = szMessage;
1711         szMessage += wcslen(szMessage) + 1;
1712     }
1713 
1714     /* Retrieve the message string without appending extra newlines */
1715     lpMsgBuf =
1716     GetMessageStringFromDllList(SourceModuleName,
1717                                 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE |
1718                                 FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_MAX_WIDTH_MASK,
1719                                 pevlr->EventID,
1720                                 0,
1721                                 (va_list*)szArguments);
1722     if (lpMsgBuf)
1723     {
1724         /* Trim the string */
1725         TrimNulls(lpMsgBuf);
1726 
1727         szMessage = NULL;
1728         Success = (ApplyParameterStringsToMessage(ParameterModuleName,
1729                                                   TRUE,
1730                                                   lpMsgBuf,
1731                                                   &szMessage) == ERROR_SUCCESS);
1732         if (Success && szMessage)
1733         {
1734             /* Free the buffer allocated by FormatMessage */
1735             LocalFree(lpMsgBuf);
1736             lpMsgBuf = szMessage;
1737         }
1738 
1739         /* Copy the event text */
1740         StringCchCopyW(EventText, EVENT_MESSAGE_EVENTTEXT_BUFFER, lpMsgBuf);
1741 
1742         /* Free the buffer allocated by FormatMessage */
1743         LocalFree(lpMsgBuf);
1744     }
1745 
1746     HeapFree(GetProcessHeap(), 0, szArguments);
1747 
1748 Quit:
1749     if (!Success)
1750     {
1751         /* Get a read-only pointer to the "event-not-found" string */
1752         lpMsgBuf = HeapAlloc(GetProcessHeap(), 0, EVENT_MESSAGE_EVENTTEXT_BUFFER * sizeof(WCHAR));
1753         LoadStringW(hInst, IDS_EVENTSTRINGIDNOTFOUND, lpMsgBuf, EVENT_MESSAGE_EVENTTEXT_BUFFER);
1754         StringCchPrintfW(EventText, EVENT_MESSAGE_EVENTTEXT_BUFFER, lpMsgBuf, (pevlr->EventID & 0xFFFF), SourceName);
1755 
1756         /* Append the strings */
1757         szMessage = szStringArray;
1758         for (i = 0; i < pevlr->NumStrings; i++)
1759         {
1760             StringCchCatW(EventText, EVENT_MESSAGE_EVENTTEXT_BUFFER, szMessage);
1761             StringCchCatW(EventText, EVENT_MESSAGE_EVENTTEXT_BUFFER, L"\n");
1762             szMessage += wcslen(szMessage) + 1;
1763         }
1764     }
1765 
1766     return Success;
1767 }
1768 
1769 VOID
1770 GetEventType(IN WORD dwEventType,
1771              OUT PWCHAR eventTypeText) // TODO: Add IN DWORD BufLen
1772 {
1773     switch (dwEventType)
1774     {
1775         case EVENTLOG_ERROR_TYPE:
1776             LoadStringW(hInst, IDS_EVENTLOG_ERROR_TYPE, eventTypeText, MAX_LOADSTRING);
1777             break;
1778         case EVENTLOG_WARNING_TYPE:
1779             LoadStringW(hInst, IDS_EVENTLOG_WARNING_TYPE, eventTypeText, MAX_LOADSTRING);
1780             break;
1781         case EVENTLOG_INFORMATION_TYPE:
1782             LoadStringW(hInst, IDS_EVENTLOG_INFORMATION_TYPE, eventTypeText, MAX_LOADSTRING);
1783             break;
1784         case EVENTLOG_SUCCESS:
1785             LoadStringW(hInst, IDS_EVENTLOG_SUCCESS, eventTypeText, MAX_LOADSTRING);
1786             break;
1787         case EVENTLOG_AUDIT_SUCCESS:
1788             LoadStringW(hInst, IDS_EVENTLOG_AUDIT_SUCCESS, eventTypeText, MAX_LOADSTRING);
1789             break;
1790         case EVENTLOG_AUDIT_FAILURE:
1791             LoadStringW(hInst, IDS_EVENTLOG_AUDIT_FAILURE, eventTypeText, MAX_LOADSTRING);
1792             break;
1793         default:
1794             LoadStringW(hInst, IDS_EVENTLOG_UNKNOWN_TYPE, eventTypeText, MAX_LOADSTRING);
1795             break;
1796     }
1797 }
1798 
1799 BOOL
1800 GetEventUserName(IN PEVENTLOGRECORD pelr,
1801                  IN OUT PSID *pLastSid,
1802                  OUT PWCHAR pszUser) // TODO: Add IN DWORD BufLen
1803 {
1804     PSID pCurrentSid;
1805     PWSTR StringSid;
1806     WCHAR szName[1024];
1807     WCHAR szDomain[1024];
1808     SID_NAME_USE peUse;
1809     DWORD cchName = ARRAYSIZE(szName);
1810     DWORD cchDomain = ARRAYSIZE(szDomain);
1811     BOOL Success = FALSE;
1812 
1813     /* Point to the SID */
1814     pCurrentSid = (PSID)((LPBYTE)pelr + pelr->UserSidOffset);
1815 
1816     if (!IsValidSid(pCurrentSid))
1817     {
1818         *pLastSid = NULL;
1819         return FALSE;
1820     }
1821     else if (*pLastSid && EqualSid(*pLastSid, pCurrentSid))
1822     {
1823         return TRUE;
1824     }
1825 
1826     /* User SID */
1827     if (pelr->UserSidLength > 0)
1828     {
1829         /*
1830          * Try to retrieve the user account name and domain name corresponding
1831          * to the SID. If it cannot be retrieved, try to convert the SID to a
1832          * string-form. It should not be bigger than the user-provided buffer
1833          * 'pszUser', otherwise we return an error.
1834          */
1835         if (LookupAccountSidW(lpComputerName,
1836                               pCurrentSid,
1837                               szName,
1838                               &cchName,
1839                               szDomain,
1840                               &cchDomain,
1841                               &peUse))
1842         {
1843             StringCchCopyW(pszUser, MAX_PATH, szName);
1844             Success = TRUE;
1845         }
1846         else if (ConvertSidToStringSidW(pCurrentSid, &StringSid))
1847         {
1848             /* Copy the string only if the user-provided buffer is big enough */
1849             if (wcslen(StringSid) + 1 <= MAX_PATH) // + 1 for NULL-terminator
1850             {
1851                 StringCchCopyW(pszUser, MAX_PATH, StringSid);
1852                 Success = TRUE;
1853             }
1854             else
1855             {
1856                 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1857                 Success = FALSE;
1858             }
1859 
1860             /* Free the allocated buffer */
1861             LocalFree(StringSid);
1862         }
1863     }
1864 
1865     *pLastSid = Success ? pCurrentSid : NULL;
1866 
1867     return Success;
1868 }
1869 
1870 
1871 static VOID FreeRecords(VOID)
1872 {
1873     DWORD iIndex;
1874 
1875     if (!g_RecordPtrs)
1876         return;
1877 
1878     for (iIndex = 0; iIndex < g_TotalRecords; iIndex++)
1879     {
1880         if (g_RecordPtrs[iIndex])
1881             HeapFree(GetProcessHeap(), 0, g_RecordPtrs[iIndex]);
1882     }
1883     HeapFree(GetProcessHeap(), 0, g_RecordPtrs);
1884     g_RecordPtrs = NULL;
1885     g_TotalRecords = 0;
1886 }
1887 
1888 BOOL
1889 FilterByType(IN PEVENTLOGFILTER EventLogFilter,
1890              IN PEVENTLOGRECORD pevlr)
1891 {
1892     if ((pevlr->EventType == EVENTLOG_SUCCESS          && !EventLogFilter->Information ) ||
1893         (pevlr->EventType == EVENTLOG_INFORMATION_TYPE && !EventLogFilter->Information ) ||
1894         (pevlr->EventType == EVENTLOG_WARNING_TYPE     && !EventLogFilter->Warning     ) ||
1895         (pevlr->EventType == EVENTLOG_ERROR_TYPE       && !EventLogFilter->Error       ) ||
1896         (pevlr->EventType == EVENTLOG_AUDIT_SUCCESS    && !EventLogFilter->AuditSuccess) ||
1897         (pevlr->EventType == EVENTLOG_AUDIT_FAILURE    && !EventLogFilter->AuditFailure))
1898     {
1899         return FALSE;
1900     }
1901     return TRUE;
1902 }
1903 
1904 BOOL
1905 FilterByString(IN PCWSTR FilterString, // This is a multi-string
1906                IN PWSTR  String)
1907 {
1908     PCWSTR pStr;
1909 
1910     /* The filter string is NULL so it does not filter anything */
1911     if (!FilterString)
1912         return TRUE;
1913 
1914     /*
1915      * If the filter string filters for an empty string AND the source string
1916      * is an empty string, we have a match (particular case of the last one).
1917      */
1918     if (!*FilterString && !*String)
1919         return TRUE;
1920 
1921     // if (*FilterString || *String)
1922 
1923     /*
1924      * If the filter string is empty BUT the source string is not empty,
1925      * OR vice-versa, we cannot have a match.
1926      */
1927     if ( (!*FilterString && *String) || (*FilterString && !*String) )
1928         return FALSE;
1929 
1930     /*
1931      * If the filter string filters for at least a non-empty string,
1932      * browse it and search for a string that matches the source string.
1933      */
1934     // else if (*FilterString && *String)
1935     {
1936         pStr = FilterString;
1937         while (*pStr)
1938         {
1939             if (wcsicmp(pStr, String) == 0)
1940             {
1941                 /* We have a match, break the loop */
1942                 break;
1943             }
1944 
1945             pStr += (wcslen(pStr) + 1);
1946         }
1947         if (!*pStr) // && *String
1948         {
1949             /* We do not have a match */
1950             return FALSE;
1951         }
1952     }
1953 
1954     /* We have a match */
1955     return TRUE;
1956 }
1957 
1958 /*
1959  * The events enumerator thread.
1960  */
1961 static DWORD WINAPI
1962 EnumEventsThread(IN LPVOID lpParameter)
1963 {
1964     PEVENTLOGFILTER EventLogFilter = (PEVENTLOGFILTER)lpParameter;
1965     PEVENTLOG EventLog;
1966 
1967     ULONG LogIndex;
1968     HANDLE hEventLog;
1969     PEVENTLOGRECORD pEvlr = NULL;
1970     PBYTE pEvlrEnd;
1971     PBYTE pEvlrBuffer;
1972     DWORD dwWanted, dwRead, dwNeeded, dwStatus = ERROR_SUCCESS;
1973     DWORD dwTotalRecords = 0, dwCurrentRecord = 0;
1974     DWORD dwFlags, dwMaxLength;
1975     size_t cchRemaining;
1976     LPWSTR lpszSourceName;
1977     LPWSTR lpszComputerName;
1978     BOOL bResult = TRUE; /* Read succeeded */
1979     HANDLE hProcessHeap = GetProcessHeap();
1980     PSID pLastSid = NULL;
1981 
1982     UINT uStep = 0, uStepAt = 0, uPos = 0;
1983 
1984     WCHAR szWindowTitle[MAX_PATH];
1985     WCHAR szStatusText[MAX_PATH];
1986     WCHAR szLocalDate[MAX_PATH];
1987     WCHAR szLocalTime[MAX_PATH];
1988     WCHAR szEventID[MAX_PATH];
1989     WCHAR szEventTypeText[MAX_LOADSTRING];
1990     WCHAR szCategoryID[MAX_PATH];
1991     WCHAR szUsername[MAX_PATH];
1992     WCHAR szNoUsername[MAX_PATH];
1993     WCHAR szCategory[MAX_PATH];
1994     WCHAR szNoCategory[MAX_PATH];
1995     PWCHAR lpTitleTemplateEnd;
1996 
1997     SYSTEMTIME time;
1998     LVITEMW lviEventItem;
1999 
2000     /* Save the current event log filter globally */
2001     EventLogFilter_AddRef(EventLogFilter);
2002     ActiveFilter = EventLogFilter;
2003 
2004 
2005     /** HACK!! **/
2006     EventLog = EventLogFilter->EventLogs[0];
2007 
2008     // FIXME: Use something else instead of EventLog->LogName !!
2009 
2010     /*
2011      * Use a different formatting, whether the event log filter holds
2012      * only one log, or many logs (the latter case is WIP TODO!)
2013      */
2014     if (EventLogFilter->NumOfEventLogs <= 1)
2015     {
2016         StringCchPrintfExW(szWindowTitle,
2017                            ARRAYSIZE(szWindowTitle),
2018                            &lpTitleTemplateEnd,
2019                            &cchRemaining,
2020                            0,
2021                            szTitleTemplate, szTitle, EventLog->LogName); /* i = number of characters written */
2022         dwMaxLength = (DWORD)cchRemaining;
2023         if (!EventLog->ComputerName)
2024             GetComputerNameW(lpTitleTemplateEnd, &dwMaxLength);
2025         else
2026             StringCchCopyW(lpTitleTemplateEnd, dwMaxLength, EventLog->ComputerName);
2027 
2028         StringCbPrintfW(szStatusText,
2029                         sizeof(szStatusText),
2030                         szStatusBarTemplate,
2031                         EventLog->LogName,
2032                         0,
2033                         0);
2034     }
2035     else
2036     {
2037         // TODO: Use a different title & implement filtering for multi-log filters !!
2038         // (EventLogFilter->NumOfEventLogs > 1)
2039         MessageBoxW(hwndMainWindow,
2040                     L"Many-logs filtering is not implemented yet!!",
2041                     szTitle,
2042                     MB_OK | MB_ICONINFORMATION);
2043     }
2044 
2045     /* Set the window title */
2046     SetWindowTextW(hwndMainWindow, szWindowTitle);
2047 
2048     /* Update the status bar */
2049     StatusBar_SetText(hwndStatus, 0, szStatusText);
2050 
2051 
2052     /* Disable list view redraw */
2053     SendMessageW(hwndListView, WM_SETREDRAW, FALSE, 0);
2054 
2055     /* Clear the list view and free the cached records */
2056     ListView_DeleteAllItems(hwndListView);
2057     FreeRecords();
2058 
2059     SendMessageW(hwndListView, LVM_PROGRESS, 0, TRUE);
2060     ProgressBar_SetRange(hwndStatusProgress, 0);
2061     StatusBar_SetText(hwndStatus, 0, NULL);
2062     ShowWindow(hwndStatusProgress, SW_SHOW);
2063 
2064     /* Do a loop over the logs enumerated in the filter */
2065     // FIXME: For now we only support 1 event log per filter!
2066     LogIndex = 0;
2067     // for (LogIndex = 0; LogIndex < EventLogFilter->NumOfEventLogs; ++LogIndex)
2068     {
2069 
2070     EventLog = EventLogFilter->EventLogs[LogIndex];
2071 
2072     /* Open the event log */
2073     if (EventLog->Permanent)
2074         hEventLog = OpenEventLogW(EventLog->ComputerName, EventLog->LogName);
2075     else
2076         hEventLog = OpenBackupEventLogW(EventLog->ComputerName, EventLog->LogName); // FileName
2077 
2078     if (hEventLog == NULL)
2079     {
2080         ShowWin32Error(GetLastError());
2081         goto Cleanup;
2082     }
2083 
2084     // GetOldestEventLogRecord(hEventLog, &dwThisRecord);
2085 
2086     /* Get the total number of event log records */
2087     GetNumberOfEventLogRecords(hEventLog, &dwTotalRecords);
2088 
2089     if (dwTotalRecords > 0)
2090     {
2091         EnableMenuItem(hMainMenu, IDM_CLEAR_EVENTS, MF_BYCOMMAND | MF_ENABLED);
2092         EnableMenuItem(hMainMenu, IDM_SAVE_EVENTLOG, MF_BYCOMMAND | MF_ENABLED);
2093     }
2094     else
2095     {
2096         EnableMenuItem(hMainMenu, IDM_CLEAR_EVENTS, MF_BYCOMMAND | MF_GRAYED);
2097         EnableMenuItem(hMainMenu, IDM_SAVE_EVENTLOG, MF_BYCOMMAND | MF_GRAYED);
2098     }
2099 
2100     /* Set up the event records cache */
2101     g_RecordPtrs = HeapAlloc(hProcessHeap, HEAP_ZERO_MEMORY, dwTotalRecords * sizeof(*g_RecordPtrs));
2102     if (!g_RecordPtrs)
2103     {
2104         // ShowWin32Error(GetLastError());
2105         goto Quit;
2106     }
2107     g_TotalRecords = dwTotalRecords;
2108 
2109     if (WaitForSingleObject(hStopEnumEvent, 0) == WAIT_OBJECT_0)
2110         goto Quit;
2111 
2112     LoadStringW(hInst, IDS_NOT_AVAILABLE, szNoUsername, ARRAYSIZE(szNoUsername));
2113     LoadStringW(hInst, IDS_NONE, szNoCategory, ARRAYSIZE(szNoCategory));
2114 
2115     ProgressBar_SetRange(hwndStatusProgress, MAKELPARAM(0, 100));
2116     uStepAt = (dwTotalRecords / 100) + 1;
2117 
2118     dwFlags = EVENTLOG_SEQUENTIAL_READ | (Settings.bNewestEventsFirst ? EVENTLOG_FORWARDS_READ : EVENTLOG_BACKWARDS_READ);
2119 
2120     /* 0x7ffff is the maximum buffer size ReadEventLog will accept */
2121     dwWanted = 0x7ffff;
2122     pEvlr = HeapAlloc(hProcessHeap, 0, dwWanted);
2123 
2124     if (!pEvlr)
2125         goto Quit;
2126 
2127     while (dwStatus == ERROR_SUCCESS)
2128     {
2129         bResult =  ReadEventLogW(hEventLog, dwFlags, 0, pEvlr, dwWanted, &dwRead, &dwNeeded);
2130         dwStatus = GetLastError();
2131 
2132         if (!bResult && dwStatus == ERROR_INSUFFICIENT_BUFFER)
2133         {
2134             pEvlr = HeapReAlloc(hProcessHeap, 0, pEvlr, dwNeeded);
2135             dwWanted = dwNeeded;
2136 
2137             if (!pEvlr)
2138                 break;
2139 
2140             bResult =  ReadEventLogW(hEventLog, dwFlags, 0, pEvlr, dwNeeded, &dwRead, &dwNeeded);
2141 
2142             if (!bResult)
2143                 break;
2144         }
2145         else if (!bResult)
2146         {
2147             /* Exit on other errors (ERROR_HANDLE_EOF) */
2148             break;
2149         }
2150 
2151         pEvlrBuffer = (LPBYTE)pEvlr;
2152         pEvlrEnd = pEvlrBuffer + dwRead;
2153 
2154         while (pEvlrBuffer < pEvlrEnd)
2155         {
2156             PEVENTLOGRECORD pEvlrTmp = (PEVENTLOGRECORD)pEvlrBuffer;
2157             PWSTR lpszUsername, lpszCategoryName;
2158             g_RecordPtrs[dwCurrentRecord] = NULL;
2159 
2160             // ProgressBar_StepIt(hwndStatusProgress);
2161             uStep++;
2162             if (uStep % uStepAt == 0)
2163             {
2164                 ++uPos;
2165                 ProgressBar_SetPos(hwndStatusProgress, uPos);
2166             }
2167 
2168            if (WaitForSingleObject(hStopEnumEvent, 0) == WAIT_OBJECT_0)
2169                 goto Quit;
2170 
2171             /* Filter by event type */
2172             if (!FilterByType(EventLogFilter, pEvlrTmp))
2173                 goto SkipEvent;
2174 
2175             /* Get the event source name and filter it */
2176             lpszSourceName = (LPWSTR)(pEvlrBuffer + sizeof(EVENTLOGRECORD));
2177             if (!FilterByString(EventLogFilter->Sources, lpszSourceName))
2178                 goto SkipEvent;
2179 
2180             /* Get the computer name and filter it */
2181             lpszComputerName = (LPWSTR)(pEvlrBuffer + sizeof(EVENTLOGRECORD) + (wcslen(lpszSourceName) + 1) * sizeof(WCHAR));
2182             if (!FilterByString(EventLogFilter->ComputerNames, lpszComputerName))
2183                 goto SkipEvent;
2184 
2185             /* Compute the event time */
2186             EventTimeToSystemTime(pEvlrTmp->TimeWritten, &time);
2187             GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &time, NULL, szLocalDate, ARRAYSIZE(szLocalDate));
2188             GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &time, NULL, szLocalTime, ARRAYSIZE(szLocalTime));
2189 
2190             /* Get the username that generated the event, and filter it */
2191             lpszUsername = GetEventUserName(pEvlrTmp, &pLastSid, szUsername) ? szUsername : szNoUsername;
2192 
2193             if (!FilterByString(EventLogFilter->Users, lpszUsername))
2194                 goto SkipEvent;
2195 
2196             // TODO: Filter by event ID and category
2197             GetEventType(pEvlrTmp->EventType, szEventTypeText);
2198 
2199             lpszCategoryName = GetEventCategory(EventLog->LogName, lpszSourceName, pEvlrTmp, szCategory) ? szCategory : szNoCategory;
2200 
2201             StringCbPrintfW(szEventID, sizeof(szEventID), L"%u", (pEvlrTmp->EventID & 0xFFFF));
2202             StringCbPrintfW(szCategoryID, sizeof(szCategoryID), L"%u", pEvlrTmp->EventCategory);
2203 
2204             g_RecordPtrs[dwCurrentRecord] = HeapAlloc(hProcessHeap, 0, pEvlrTmp->Length);
2205             CopyMemory(g_RecordPtrs[dwCurrentRecord], pEvlrTmp, pEvlrTmp->Length);
2206 
2207             lviEventItem.mask = LVIF_IMAGE | LVIF_TEXT | LVIF_PARAM;
2208             lviEventItem.iItem = 0;
2209             lviEventItem.iSubItem = 0;
2210             lviEventItem.lParam = (LPARAM)g_RecordPtrs[dwCurrentRecord];
2211             lviEventItem.pszText = szEventTypeText;
2212 
2213             switch (pEvlrTmp->EventType)
2214             {
2215                 case EVENTLOG_SUCCESS:
2216                 case EVENTLOG_INFORMATION_TYPE:
2217                     lviEventItem.iImage = 0;
2218                     break;
2219 
2220                 case EVENTLOG_WARNING_TYPE:
2221                     lviEventItem.iImage = 1;
2222                     break;
2223 
2224                 case EVENTLOG_ERROR_TYPE:
2225                     lviEventItem.iImage = 2;
2226                     break;
2227 
2228                 case EVENTLOG_AUDIT_SUCCESS:
2229                     lviEventItem.iImage = 3;
2230                     break;
2231 
2232                 case EVENTLOG_AUDIT_FAILURE:
2233                     lviEventItem.iImage = 4;
2234                     break;
2235             }
2236 
2237             lviEventItem.iItem = ListView_InsertItem(hwndListView, &lviEventItem);
2238 
2239             ListView_SetItemText(hwndListView, lviEventItem.iItem, 1, szLocalDate);
2240             ListView_SetItemText(hwndListView, lviEventItem.iItem, 2, szLocalTime);
2241             ListView_SetItemText(hwndListView, lviEventItem.iItem, 3, lpszSourceName);
2242             ListView_SetItemText(hwndListView, lviEventItem.iItem, 4, lpszCategoryName);
2243             ListView_SetItemText(hwndListView, lviEventItem.iItem, 5, szEventID);
2244             ListView_SetItemText(hwndListView, lviEventItem.iItem, 6, lpszUsername);
2245             ListView_SetItemText(hwndListView, lviEventItem.iItem, 7, lpszComputerName);
2246 
2247 SkipEvent:
2248             pEvlrBuffer += pEvlrTmp->Length;
2249             dwCurrentRecord++;
2250         }
2251     }
2252 
2253 Quit:
2254 
2255     if (pEvlr)
2256         HeapFree(hProcessHeap, 0, pEvlr);
2257 
2258     /* Close the event log */
2259     CloseEventLog(hEventLog);
2260 
2261     } // end-for (LogIndex)
2262 
2263     /* All events loaded */
2264 
2265 Cleanup:
2266 
2267     ShowWindow(hwndStatusProgress, SW_HIDE);
2268     SendMessageW(hwndListView, LVM_PROGRESS, 0, FALSE);
2269 
2270     // FIXME: Use something else instead of EventLog->LogName !!
2271 
2272     /*
2273      * Use a different formatting, whether the event log filter holds
2274      * only one log, or many logs (the latter case is WIP TODO!)
2275      */
2276     if (EventLogFilter->NumOfEventLogs <= 1)
2277     {
2278         StringCbPrintfW(szStatusText,
2279                         sizeof(szStatusText),
2280                         szStatusBarTemplate,
2281                         EventLog->LogName,
2282                         dwTotalRecords,
2283                         ListView_GetItemCount(hwndListView));
2284     }
2285     else
2286     {
2287         // TODO: Use a different title & implement filtering for multi-log filters !!
2288         // (EventLogFilter->NumOfEventLogs > 1)
2289     }
2290 
2291     /* Update the status bar */
2292     StatusBar_SetText(hwndStatus, 0, szStatusText);
2293 
2294     /* Resume list view redraw */
2295     SendMessageW(hwndListView, WM_SETREDRAW, TRUE, 0);
2296 
2297     EventLogFilter_Release(EventLogFilter);
2298 
2299     CloseHandle(hStopEnumEvent);
2300     InterlockedExchangePointer((PVOID*)&hStopEnumEvent, NULL);
2301 
2302     return 0;
2303 }
2304 
2305 /*
2306  * The purpose of this thread is to serialize the creation of the events
2307  * enumeration thread, since the Event Log Viewer currently only supports
2308  * one view, one event list, one enumeration.
2309  */
2310 static DWORD WINAPI
2311 StartStopEnumEventsThread(IN LPVOID lpParameter)
2312 {
2313     HANDLE WaitHandles[2];
2314     DWORD  WaitResult;
2315 
2316     WaitHandles[0] = hStartStopEnumEvent; // End-of-application event
2317     WaitHandles[1] = hStartEnumEvent;     // Command event
2318 
2319     while (TRUE)
2320     {
2321         WaitResult = WaitForMultipleObjects(ARRAYSIZE(WaitHandles),
2322                                             WaitHandles,
2323                                             FALSE, // WaitAny
2324                                             INFINITE);
2325         switch (WaitResult)
2326         {
2327             case WAIT_OBJECT_0 + 0:
2328             {
2329                 /* End-of-application event signaled, quit this thread */
2330 
2331                 /* Stop the previous enumeration */
2332                 if (hEnumEventsThread)
2333                 {
2334                     if (hStopEnumEvent)
2335                     {
2336                         SetEvent(hStopEnumEvent);
2337                         WaitForSingleObject(hEnumEventsThread, INFINITE);
2338                         // NOTE: The following is done by the enumeration thread just before terminating.
2339                         // hStopEnumEvent = NULL;
2340                     }
2341 
2342                     CloseHandle(hEnumEventsThread);
2343                     hEnumEventsThread = NULL;
2344                 }
2345 
2346                 /* Clear the list view and free the cached records */
2347                 ListView_DeleteAllItems(hwndListView);
2348                 FreeRecords();
2349 
2350                 /* Reset the active filter */
2351                 ActiveFilter = NULL;
2352 
2353                 return 0;
2354             }
2355 
2356             case WAIT_OBJECT_0 + 1:
2357             {
2358                 /* Restart a new enumeration if needed */
2359                 PEVENTLOGFILTER EventLogFilter;
2360 
2361                 /* Stop the previous enumeration */
2362                 if (hEnumEventsThread)
2363                 {
2364                     if (hStopEnumEvent)
2365                     {
2366                         SetEvent(hStopEnumEvent);
2367                         WaitForSingleObject(hEnumEventsThread, INFINITE);
2368                         // NOTE: The following is done by the enumeration thread just before terminating.
2369                         // hStopEnumEvent = NULL;
2370                     }
2371 
2372                     CloseHandle(hEnumEventsThread);
2373                     hEnumEventsThread = NULL;
2374                 }
2375 
2376                 /* Clear the list view and free the cached records */
2377                 ListView_DeleteAllItems(hwndListView);
2378                 FreeRecords();
2379 
2380                 /* Reset the active filter */
2381                 ActiveFilter = NULL;
2382 
2383                 EventLogFilter = InterlockedExchangePointer((PVOID*)&EnumFilter, NULL);
2384                 if (!EventLogFilter)
2385                     break;
2386 
2387                 // Manual-reset event
2388                 hStopEnumEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
2389                 if (!hStopEnumEvent)
2390                     break;
2391 
2392                 hEnumEventsThread = CreateThread(NULL,
2393                                                  0,
2394                                                  EnumEventsThread,
2395                                                  (LPVOID)EventLogFilter,
2396                                                  CREATE_SUSPENDED,
2397                                                  NULL);
2398                 if (!hEnumEventsThread)
2399                 {
2400                     CloseHandle(hStopEnumEvent);
2401                     hStopEnumEvent = NULL;
2402                     break;
2403                 }
2404                 // CloseHandle(hEnumEventsThread);
2405                 ResumeThread(hEnumEventsThread);
2406 
2407                 break;
2408             }
2409 
2410             default:
2411             {
2412                 /* Unknown command, must never go there! */
2413                 return GetLastError();
2414             }
2415         }
2416     }
2417 
2418     return 0;
2419 }
2420 
2421 VOID
2422 EnumEvents(IN PEVENTLOGFILTER EventLogFilter)
2423 {
2424     /* Signal the enumerator thread we want to enumerate events */
2425     InterlockedExchangePointer((PVOID*)&EnumFilter, EventLogFilter);
2426     SetEvent(hStartEnumEvent);
2427     return;
2428 }
2429 
2430 
2431 PEVENTLOGFILTER
2432 GetSelectedFilter(OUT HTREEITEM* phti OPTIONAL)
2433 {
2434     TVITEMEXW tvItemEx;
2435     HTREEITEM hti;
2436 
2437     if (phti)
2438         *phti = NULL;
2439 
2440     /* Get index of selected item */
2441     hti = TreeView_GetSelection(hwndTreeView);
2442     if (hti == NULL)
2443         return NULL; // No filter
2444 
2445     tvItemEx.mask = TVIF_PARAM;
2446     tvItemEx.hItem = hti;
2447 
2448     TreeView_GetItem(hwndTreeView, &tvItemEx);
2449 
2450     if (phti)
2451         *phti = tvItemEx.hItem;
2452 
2453     return (PEVENTLOGFILTER)tvItemEx.lParam;
2454 }
2455 
2456 
2457 VOID
2458 OpenUserEventLogFile(IN LPCWSTR lpszFileName)
2459 {
2460     WIN32_FIND_DATAW FindData;
2461     HANDLE hFind;
2462     PEVENTLOG EventLog;
2463     PEVENTLOGFILTER EventLogFilter;
2464     SIZE_T cchFileName;
2465     HTREEITEM hItem = NULL;
2466 
2467     /* Check whether the file actually exists */
2468     hFind = FindFirstFileW(lpszFileName, &FindData);
2469     if (hFind == INVALID_HANDLE_VALUE)
2470     {
2471         ShowWin32Error(GetLastError());
2472         return;
2473     }
2474     FindClose(hFind);
2475 
2476     /* Allocate a new event log entry */
2477     EventLog = AllocEventLog(NULL, lpszFileName, FALSE);
2478     if (EventLog == NULL)
2479     {
2480         ShowWin32Error(ERROR_NOT_ENOUGH_MEMORY);
2481         return;
2482     }
2483 
2484     /* Allocate a new event log filter entry for this event log */
2485     EventLogFilter = AllocEventLogFilter(// LogName,
2486                                          TRUE, TRUE, TRUE, TRUE, TRUE,
2487                                          NULL, NULL, NULL,
2488                                          1, &EventLog);
2489     if (EventLogFilter == NULL)
2490     {
2491         ShowWin32Error(ERROR_NOT_ENOUGH_MEMORY);
2492         EventLog_Free(EventLog);
2493         return;
2494     }
2495 
2496     /* Add the event log and the filter into their lists */
2497     InsertTailList(&EventLogList, &EventLog->ListEntry);
2498     InsertTailList(&EventLogFilterList, &EventLogFilter->ListEntry);
2499 
2500     /* Retrieve and cache the event log file */
2501     cchFileName = wcslen(lpszFileName) + 1;
2502     EventLog->FileName = HeapAlloc(GetProcessHeap(), 0, cchFileName * sizeof(WCHAR));
2503     if (EventLog->FileName)
2504         StringCchCopyW(EventLog->FileName, cchFileName, lpszFileName);
2505 
2506     hItem = TreeViewAddItem(hwndTreeView, htiUserLogs,
2507                             (LPWSTR)lpszFileName,
2508                             2, 3, (LPARAM)EventLogFilter);
2509 
2510     /* Select the event log */
2511     if (hItem)
2512     {
2513         // TreeView_Expand(hwndTreeView, htiUserLogs, TVE_EXPAND);
2514         TreeView_SelectItem(hwndTreeView, hItem);
2515         TreeView_EnsureVisible(hwndTreeView, hItem);
2516     }
2517     InvalidateRect(hwndTreeView, NULL, FALSE);
2518     SetFocus(hwndTreeView);
2519 }
2520 
2521 VOID
2522 OpenUserEventLog(VOID)
2523 {
2524     WCHAR szFileName[MAX_PATH];
2525 
2526     ZeroMemory(szFileName, sizeof(szFileName));
2527 
2528     sfn.lpstrFile = szFileName;
2529     sfn.nMaxFile  = ARRAYSIZE(szFileName);
2530 
2531     if (!GetOpenFileNameW(&sfn))
2532         return;
2533     sfn.lpstrFile[sfn.nMaxFile-1] = UNICODE_NULL;
2534 
2535     OpenUserEventLogFile(sfn.lpstrFile);
2536 }
2537 
2538 VOID
2539 SaveEventLog(IN PEVENTLOGFILTER EventLogFilter)
2540 {
2541     PEVENTLOG EventLog;
2542     HANDLE hEventLog;
2543     WCHAR szFileName[MAX_PATH];
2544 
2545     /* Bail out if there is no available filter */
2546     if (!EventLogFilter)
2547         return;
2548 
2549     ZeroMemory(szFileName, sizeof(szFileName));
2550 
2551     sfn.lpstrFile = szFileName;
2552     sfn.nMaxFile  = ARRAYSIZE(szFileName);
2553 
2554     if (!GetSaveFileNameW(&sfn))
2555         return;
2556 
2557     EventLogFilter_AddRef(EventLogFilter);
2558 
2559     EventLog = EventLogFilter->EventLogs[0];
2560     hEventLog = OpenEventLogW(EventLog->ComputerName, EventLog->LogName);
2561 
2562     EventLogFilter_Release(EventLogFilter);
2563 
2564     if (!hEventLog)
2565     {
2566         ShowWin32Error(GetLastError());
2567         return;
2568     }
2569 
2570     if (!BackupEventLogW(hEventLog, szFileName))
2571         ShowWin32Error(GetLastError());
2572 
2573     CloseEventLog(hEventLog);
2574 }
2575 
2576 VOID
2577 CloseUserEventLog(IN PEVENTLOGFILTER EventLogFilter, IN HTREEITEM hti)
2578 {
2579     /* Bail out if there is no available filter */
2580     if (!EventLogFilter)
2581         return;
2582 
2583     if (InterlockedCompareExchangePointer((PVOID*)&ActiveFilter, NULL, NULL) == EventLogFilter)
2584     {
2585         /* Signal the enumerator thread we want to stop enumerating events */
2586         // EnumEvents(NULL);
2587         InterlockedExchangePointer((PVOID*)&EnumFilter, NULL);
2588         SetEvent(hStartEnumEvent);
2589     }
2590 
2591     /*
2592      * The deletion of the item automatically triggers a TVN_SELCHANGED
2593      * notification, that will reset the ActiveFilter (in case the item
2594      * selected is a filter). Otherwise we reset it there.
2595      */
2596     TreeView_DeleteItem(hwndTreeView, hti);
2597 
2598     /* Remove the filter from the list */
2599     RemoveEntryList(&EventLogFilter->ListEntry);
2600     EventLogFilter_Release(EventLogFilter);
2601 
2602     // /* Select the default event log */
2603     // // TreeView_Expand(hwndTreeView, htiUserLogs, TVE_EXPAND);
2604     // TreeView_SelectItem(hwndTreeView, hItem);
2605     // TreeView_EnsureVisible(hwndTreeView, hItem);
2606     InvalidateRect(hwndTreeView, NULL, FALSE);
2607     SetFocus(hwndTreeView);
2608 }
2609 
2610 
2611 BOOL
2612 ClearEvents(IN PEVENTLOGFILTER EventLogFilter)
2613 {
2614     BOOL Success;
2615     PEVENTLOG EventLog;
2616     HANDLE hEventLog;
2617     WCHAR szFileName[MAX_PATH];
2618     WCHAR szMessage[MAX_LOADSTRING];
2619 
2620     /* Bail out if there is no available filter */
2621     if (!EventLogFilter)
2622         return FALSE;
2623 
2624     ZeroMemory(szFileName, sizeof(szFileName));
2625     ZeroMemory(szMessage, sizeof(szMessage));
2626 
2627     LoadStringW(hInst, IDS_CLEAREVENTS_MSG, szMessage, ARRAYSIZE(szMessage));
2628 
2629     sfn.lpstrFile = szFileName;
2630     sfn.nMaxFile  = ARRAYSIZE(szFileName);
2631 
2632     switch (MessageBoxW(hwndMainWindow, szMessage, szTitle, MB_YESNOCANCEL | MB_ICONINFORMATION))
2633     {
2634         case IDCANCEL:
2635             return FALSE;
2636 
2637         case IDNO:
2638             sfn.lpstrFile = NULL;
2639             break;
2640 
2641         case IDYES:
2642             if (!GetSaveFileNameW(&sfn))
2643                 return FALSE;
2644             break;
2645     }
2646 
2647     EventLogFilter_AddRef(EventLogFilter);
2648 
2649     EventLog = EventLogFilter->EventLogs[0];
2650     hEventLog = OpenEventLogW(EventLog->ComputerName, EventLog->LogName);
2651 
2652     EventLogFilter_Release(EventLogFilter);
2653 
2654     if (!hEventLog)
2655     {
2656         ShowWin32Error(GetLastError());
2657         return FALSE;
2658     }
2659 
2660     Success = ClearEventLogW(hEventLog, sfn.lpstrFile);
2661     if (!Success)
2662         ShowWin32Error(GetLastError());
2663 
2664     CloseEventLog(hEventLog);
2665     return Success;
2666 }
2667 
2668 
2669 VOID
2670 Refresh(IN PEVENTLOGFILTER EventLogFilter)
2671 {
2672     /* Bail out if there is no available filter */
2673     if (!EventLogFilter)
2674         return;
2675 
2676     /* Reenumerate the events through the filter */
2677     EnumEvents(EventLogFilter);
2678 }
2679 
2680 
2681 ATOM
2682 MyRegisterClass(HINSTANCE hInstance)
2683 {
2684     WNDCLASSEXW wcex;
2685 
2686     wcex.cbSize = sizeof(wcex);
2687     wcex.style = 0;
2688     wcex.lpfnWndProc = WndProc;
2689     wcex.cbClsExtra = 0;
2690     wcex.cbWndExtra = 0;
2691     wcex.hInstance = hInstance;
2692     wcex.hIcon = LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_EVENTVWR));
2693     wcex.hCursor = LoadCursorW(NULL, MAKEINTRESOURCEW(IDC_ARROW));
2694     wcex.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1); // COLOR_WINDOW + 1
2695     wcex.lpszMenuName = MAKEINTRESOURCEW(IDM_EVENTVWR);
2696     wcex.lpszClassName = EVENTVWR_WNDCLASS;
2697     wcex.hIconSm = (HICON)LoadImageW(hInstance,
2698                                      MAKEINTRESOURCEW(IDI_EVENTVWR),
2699                                      IMAGE_ICON,
2700                                      16,
2701                                      16,
2702                                      LR_SHARED);
2703 
2704     return RegisterClassExW(&wcex);
2705 }
2706 
2707 
2708 BOOL
2709 GetDisplayNameFileAndID(IN LPCWSTR lpLogName,
2710                         OUT PWCHAR lpModuleName, // TODO: Add IN DWORD BufLen
2711                         OUT PDWORD pdwMessageID)
2712 {
2713     BOOL Success = FALSE;
2714     LONG Result;
2715     HKEY hLogKey;
2716     WCHAR *KeyPath;
2717     SIZE_T cbKeyPath;
2718     DWORD dwType, cbData;
2719     DWORD dwMessageID = 0;
2720     WCHAR szModuleName[MAX_PATH];
2721 
2722     /* Use a default value for the message ID */
2723     *pdwMessageID = 0;
2724 
2725     cbKeyPath = (wcslen(EVENTLOG_BASE_KEY) + wcslen(lpLogName) + 1) * sizeof(WCHAR);
2726     KeyPath = HeapAlloc(GetProcessHeap(), 0, cbKeyPath);
2727     if (!KeyPath)
2728     {
2729         ShowWin32Error(ERROR_NOT_ENOUGH_MEMORY);
2730         return FALSE;
2731     }
2732 
2733     StringCbCopyW(KeyPath, cbKeyPath, EVENTLOG_BASE_KEY);
2734     StringCbCatW(KeyPath, cbKeyPath, lpLogName);
2735 
2736     Result = RegOpenKeyExW(hkMachine, KeyPath, 0, KEY_QUERY_VALUE, &hLogKey);
2737     HeapFree(GetProcessHeap(), 0, KeyPath);
2738     if (Result != ERROR_SUCCESS)
2739     {
2740         ShowWin32Error(Result);
2741         return FALSE;
2742     }
2743 
2744     cbData = sizeof(szModuleName);
2745     Result = RegQueryValueExW(hLogKey,
2746                               L"DisplayNameFile",
2747                               NULL,
2748                               &dwType,
2749                               (LPBYTE)szModuleName,
2750                               &cbData);
2751     if ((Result != ERROR_SUCCESS) || (dwType != REG_EXPAND_SZ && dwType != REG_SZ))
2752     {
2753         szModuleName[0] = UNICODE_NULL;
2754     }
2755     else
2756     {
2757         /* NULL-terminate the string and expand it */
2758         szModuleName[cbData / sizeof(WCHAR) - 1] = UNICODE_NULL;
2759         GetExpandedFilePathName(lpComputerName, szModuleName, lpModuleName, ARRAYSIZE(szModuleName));
2760         Success = TRUE;
2761     }
2762 
2763     /*
2764      * If we have a 'DisplayNameFile', query for 'DisplayNameID';
2765      * otherwise it's not really useful. 'DisplayNameID' is optional.
2766      */
2767     if (Success)
2768     {
2769         cbData = sizeof(dwMessageID);
2770         Result = RegQueryValueExW(hLogKey,
2771                                   L"DisplayNameID",
2772                                   NULL,
2773                                   &dwType,
2774                                   (LPBYTE)&dwMessageID,
2775                                   &cbData);
2776         if ((Result != ERROR_SUCCESS) || (dwType != REG_DWORD))
2777             dwMessageID = 0;
2778 
2779         *pdwMessageID = dwMessageID;
2780     }
2781 
2782     RegCloseKey(hLogKey);
2783 
2784     return Success;
2785 }
2786 
2787 
2788 VOID
2789 BuildLogListAndFilterList(IN LPCWSTR lpComputerName)
2790 {
2791     LONG Result;
2792     HKEY hEventLogKey, hLogKey;
2793     DWORD dwNumLogs = 0;
2794     DWORD dwIndex, dwMaxKeyLength;
2795     DWORD dwType;
2796     PEVENTLOG EventLog;
2797     PEVENTLOGFILTER EventLogFilter;
2798     LPWSTR LogName = NULL;
2799     WCHAR szModuleName[MAX_PATH];
2800     DWORD lpcName;
2801     DWORD dwMessageID;
2802     LPWSTR lpDisplayName;
2803     HTREEITEM hRootNode = NULL, hItem = NULL, hItemDefault = NULL;
2804 
2805     if (hkMachine && hkMachine != HKEY_LOCAL_MACHINE)
2806     {
2807         /* We are connected to some other computer, close the old connection */
2808         RegCloseKey(hkMachine);
2809         hkMachine = NULL;
2810     }
2811     if (!lpComputerName || !*lpComputerName)
2812     {
2813         /* Use the local computer registry */
2814         hkMachine = HKEY_LOCAL_MACHINE;
2815     }
2816     else
2817     {
2818         /* Connect to the remote computer registry */
2819         Result = RegConnectRegistry(lpComputerName, HKEY_LOCAL_MACHINE, &hkMachine);
2820         if (Result != ERROR_SUCCESS)
2821         {
2822             /* Connection failed, display a message and bail out */
2823             hkMachine = NULL;
2824             ShowWin32Error(GetLastError());
2825             return;
2826         }
2827     }
2828 
2829     /* Open the EventLog key */
2830     Result = RegOpenKeyExW(hkMachine, EVENTLOG_BASE_KEY, 0, KEY_READ, &hEventLogKey);
2831     if (Result != ERROR_SUCCESS)
2832     {
2833         return;
2834     }
2835 
2836     /* Retrieve the number of event logs enumerated as registry keys */
2837     Result = RegQueryInfoKeyW(hEventLogKey, NULL, NULL, NULL, &dwNumLogs, &dwMaxKeyLength,
2838                               NULL, NULL, NULL, NULL, NULL, NULL);
2839     if (Result != ERROR_SUCCESS)
2840     {
2841         goto Quit;
2842     }
2843     if (!dwNumLogs)
2844         goto Quit;
2845 
2846     /* Take the NULL terminator into account */
2847     ++dwMaxKeyLength;
2848 
2849     /* Allocate the temporary buffer */
2850     LogName = HeapAlloc(GetProcessHeap(), 0, dwMaxKeyLength * sizeof(WCHAR));
2851     if (!LogName)
2852         goto Quit;
2853 
2854     /* Enumerate and retrieve each event log name */
2855     for (dwIndex = 0; dwIndex < dwNumLogs; dwIndex++)
2856     {
2857         lpcName = dwMaxKeyLength;
2858         Result = RegEnumKeyExW(hEventLogKey, dwIndex, LogName, &lpcName, NULL, NULL, NULL, NULL);
2859         if (Result != ERROR_SUCCESS)
2860             continue;
2861 
2862         /* Take the NULL terminator into account */
2863         ++lpcName;
2864 
2865         /* Allocate a new event log entry */
2866         EventLog = AllocEventLog(lpComputerName, LogName, TRUE);
2867         if (EventLog == NULL)
2868             continue;
2869 
2870         /* Allocate a new event log filter entry for this event log */
2871         EventLogFilter = AllocEventLogFilter(// LogName,
2872                                              TRUE, TRUE, TRUE, TRUE, TRUE,
2873                                              NULL, NULL, NULL,
2874                                              1, &EventLog);
2875         if (EventLogFilter == NULL)
2876         {
2877             EventLog_Free(EventLog);
2878             continue;
2879         }
2880 
2881         /* Add the event log and the filter into their lists */
2882         InsertTailList(&EventLogList, &EventLog->ListEntry);
2883         InsertTailList(&EventLogFilterList, &EventLogFilter->ListEntry);
2884 
2885         EventLog->FileName = NULL;
2886 
2887         /* Retrieve and cache the event log file */
2888         Result = RegOpenKeyExW(hEventLogKey,
2889                                LogName,
2890                                0,
2891                                KEY_QUERY_VALUE,
2892                                &hLogKey);
2893         if (Result == ERROR_SUCCESS)
2894         {
2895             lpcName = 0;
2896             Result = RegQueryValueExW(hLogKey,
2897                                       L"File",
2898                                       NULL,
2899                                       &dwType,
2900                                       NULL,
2901                                       &lpcName);
2902             if ((Result != ERROR_SUCCESS) || (dwType != REG_EXPAND_SZ && dwType != REG_SZ))
2903             {
2904                 // Windows' EventLog uses some kind of default value, we do not.
2905                 EventLog->FileName = NULL;
2906             }
2907             else
2908             {
2909                 lpcName = ROUND_DOWN(lpcName, sizeof(WCHAR));
2910                 EventLog->FileName = HeapAlloc(GetProcessHeap(), 0, lpcName);
2911                 if (EventLog->FileName)
2912                 {
2913                     Result = RegQueryValueExW(hLogKey,
2914                                               L"File",
2915                                               NULL,
2916                                               &dwType,
2917                                               (LPBYTE)EventLog->FileName,
2918                                               &lpcName);
2919                     if (Result != ERROR_SUCCESS)
2920                     {
2921                         HeapFree(GetProcessHeap(), 0, EventLog->FileName);
2922                         EventLog->FileName = NULL;
2923                     }
2924                     else
2925                     {
2926                         EventLog->FileName[lpcName / sizeof(WCHAR) - 1] = UNICODE_NULL;
2927                     }
2928                 }
2929             }
2930 
2931             RegCloseKey(hLogKey);
2932         }
2933 
2934         /* Get the display name for the event log */
2935         lpDisplayName = NULL;
2936 
2937         ZeroMemory(szModuleName, sizeof(szModuleName));
2938         if (GetDisplayNameFileAndID(LogName, szModuleName, &dwMessageID))
2939         {
2940             /* Retrieve the message string without appending extra newlines */
2941             lpDisplayName =
2942             GetMessageStringFromDll(szModuleName,
2943                                     FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE |
2944                                     FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK,
2945                                     dwMessageID,
2946                                     0,
2947                                     NULL);
2948         }
2949 
2950         /*
2951          * Select the correct tree root node, whether the log is a System
2952          * or an Application log. Default to Application log otherwise.
2953          */
2954         hRootNode = htiAppLogs;
2955         for (lpcName = 0; lpcName < ARRAYSIZE(SystemLogs); ++lpcName)
2956         {
2957             /* Check whether the log name is part of the system logs */
2958             if (wcsicmp(LogName, SystemLogs[lpcName]) == 0)
2959             {
2960                 hRootNode = htiSystemLogs;
2961                 break;
2962             }
2963         }
2964 
2965         hItem = TreeViewAddItem(hwndTreeView, hRootNode,
2966                                 (lpDisplayName ? lpDisplayName : LogName),
2967                                 2, 3, (LPARAM)EventLogFilter);
2968 
2969         /* Try to get the default event log: "Application" */
2970         if ((hItemDefault == NULL) && (wcsicmp(LogName, SystemLogs[0]) == 0))
2971         {
2972             hItemDefault = hItem;
2973         }
2974 
2975         /* Free the buffer allocated by FormatMessage */
2976         if (lpDisplayName)
2977             LocalFree(lpDisplayName);
2978     }
2979 
2980     HeapFree(GetProcessHeap(), 0, LogName);
2981 
2982 Quit:
2983     RegCloseKey(hEventLogKey);
2984 
2985     /* Select the default event log */
2986     if (hItemDefault)
2987     {
2988         // TreeView_Expand(hwndTreeView, hRootNode, TVE_EXPAND);
2989         TreeView_SelectItem(hwndTreeView, hItemDefault);
2990         TreeView_EnsureVisible(hwndTreeView, hItemDefault);
2991     }
2992     InvalidateRect(hwndTreeView, NULL, FALSE);
2993     SetFocus(hwndTreeView);
2994 
2995     return;
2996 }
2997 
2998 VOID
2999 FreeLogList(VOID)
3000 {
3001     PLIST_ENTRY Entry;
3002     PEVENTLOG EventLog;
3003 
3004     while (!IsListEmpty(&EventLogList))
3005     {
3006         Entry = RemoveHeadList(&EventLogList);
3007         EventLog = (PEVENTLOG)CONTAINING_RECORD(Entry, EVENTLOG, ListEntry);
3008         EventLog_Free(EventLog);
3009     }
3010 
3011     return;
3012 }
3013 
3014 VOID
3015 FreeLogFilterList(VOID)
3016 {
3017     PLIST_ENTRY Entry;
3018     PEVENTLOGFILTER EventLogFilter;
3019 
3020     while (!IsListEmpty(&EventLogFilterList))
3021     {
3022         Entry = RemoveHeadList(&EventLogFilterList);
3023         EventLogFilter = (PEVENTLOGFILTER)CONTAINING_RECORD(Entry, EVENTLOGFILTER, ListEntry);
3024         EventLogFilter_Free(EventLogFilter);
3025     }
3026 
3027     ActiveFilter = NULL;
3028 
3029     return;
3030 }
3031 
3032 BOOL
3033 InitInstance(HINSTANCE hInstance)
3034 {
3035     RECT rcClient, rs;
3036     LONG StatusHeight;
3037     HIMAGELIST hSmall;
3038     LVCOLUMNW lvc = {0};
3039     WCHAR szTemp[256];
3040 
3041     /* Create the main window */
3042     rs = Settings.wpPos.rcNormalPosition;
3043     hwndMainWindow = CreateWindowW(EVENTVWR_WNDCLASS,
3044                                    szTitle,
3045                                    WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
3046                                    rs.left, rs.top,
3047                                    (rs.right  != CW_USEDEFAULT && rs.left != CW_USEDEFAULT) ? rs.right - rs.left : CW_USEDEFAULT,
3048                                    (rs.bottom != CW_USEDEFAULT && rs.top  != CW_USEDEFAULT) ? rs.bottom - rs.top : CW_USEDEFAULT,
3049                                    NULL,
3050                                    NULL,
3051                                    hInstance,
3052                                    NULL);
3053     if (!hwndMainWindow)
3054         return FALSE;
3055 
3056     /* Create the status bar */
3057     hwndStatus = CreateWindowExW(0,                                  // no extended styles
3058                                  STATUSCLASSNAMEW,                   // status bar
3059                                  L"",                                // no text
3060                                  WS_CHILD | WS_VISIBLE | CCS_BOTTOM | SBARS_SIZEGRIP, // styles
3061                                  0, 0, 0, 0,                         // x, y, cx, cy
3062                                  hwndMainWindow,                     // parent window
3063                                  (HMENU)100,                         // window ID
3064                                  hInstance,                          // instance
3065                                  NULL);                              // window data
3066 
3067     GetClientRect(hwndMainWindow, &rcClient);
3068     GetWindowRect(hwndStatus, &rs);
3069     StatusHeight = rs.bottom - rs.top;
3070 
3071     /* Create a progress bar in the status bar (hidden by default) */
3072     StatusBar_GetItemRect(hwndStatus, 0, &rs);
3073     hwndStatusProgress = CreateWindowExW(0,                          // no extended styles
3074                                          PROGRESS_CLASSW,            // status bar
3075                                          NULL,                       // no text
3076                                          WS_CHILD | PBS_SMOOTH,      // styles
3077                                          rs.left, rs.top,            // x, y
3078                                          rs.right - rs.left, rs.bottom - rs.top, // cx, cy
3079                                          hwndStatus,                 // parent window
3080                                          NULL,                       // window ID
3081                                          hInstance,                  // instance
3082                                          NULL);                      // window data
3083     /* Remove its static edge */
3084     SetWindowLongPtrW(hwndStatusProgress, GWL_EXSTYLE,
3085                       GetWindowLongPtrW(hwndStatusProgress, GWL_EXSTYLE) & ~WS_EX_STATICEDGE);
3086     ProgressBar_SetStep(hwndStatusProgress, 1);
3087 
3088     /* Initialize the splitter default positions */
3089     nVSplitPos = Settings.nVSplitPos;
3090     nHSplitPos = Settings.nHSplitPos;
3091 
3092     /* Create the TreeView */
3093     hwndTreeView = CreateWindowExW(WS_EX_CLIENTEDGE,
3094                                    WC_TREEVIEWW,
3095                                    NULL,
3096                                    // WS_CHILD | WS_VISIBLE | TVS_HASLINES | TVS_SHOWSELALWAYS,
3097                                    WS_CHILD | WS_VISIBLE | /* WS_TABSTOP | */ TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT | TVS_EDITLABELS | TVS_SHOWSELALWAYS,
3098                                    0, 0,
3099                                    nVSplitPos - SPLIT_WIDTH/2,
3100                                    (rcClient.bottom - rcClient.top) - StatusHeight,
3101                                    hwndMainWindow,
3102                                    NULL,
3103                                    hInstance,
3104                                    NULL);
3105 
3106     /* Create the ImageList */
3107     hSmall = ImageList_Create(GetSystemMetrics(SM_CXSMICON),
3108                               GetSystemMetrics(SM_CYSMICON),
3109                               ILC_COLOR32 | ILC_MASK, // ILC_COLOR24
3110                               1, 1);
3111 
3112     /* Add event type icons to the ImageList: closed/opened folder, event log (normal/viewed) */
3113     ImageList_AddIcon(hSmall, LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_CLOSED_CATEGORY)));
3114     ImageList_AddIcon(hSmall, LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_OPENED_CATEGORY)));
3115     ImageList_AddIcon(hSmall, LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_EVENTLOG)));
3116     ImageList_AddIcon(hSmall, LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_EVENTVWR)));
3117 
3118     /* Assign the ImageList to the Tree View */
3119     TreeView_SetImageList(hwndTreeView, hSmall, TVSIL_NORMAL);
3120 
3121     /* Add the event logs nodes */
3122     // "System Logs"
3123     LoadStringW(hInstance, IDS_EVENTLOG_SYSTEM, szTemp, ARRAYSIZE(szTemp));
3124     htiSystemLogs = TreeViewAddItem(hwndTreeView, NULL, szTemp, 0, 1, (LPARAM)NULL);
3125     // "Application Logs"
3126     LoadStringW(hInstance, IDS_EVENTLOG_APP, szTemp, ARRAYSIZE(szTemp));
3127     htiAppLogs = TreeViewAddItem(hwndTreeView, NULL, szTemp, 0, 1, (LPARAM)NULL);
3128     // "User Logs"
3129     LoadStringW(hInstance, IDS_EVENTLOG_USER, szTemp, ARRAYSIZE(szTemp));
3130     htiUserLogs = TreeViewAddItem(hwndTreeView, NULL, szTemp, 0, 1, (LPARAM)NULL);
3131 
3132     /* Create the Event details pane (optional) */
3133     hwndEventDetails = CreateEventDetailsCtrl(hInst, hwndMainWindow, (LPARAM)NULL);
3134     if (hwndEventDetails)
3135     {
3136     SetWindowLongPtrW(hwndEventDetails, GWL_EXSTYLE,
3137                       GetWindowLongPtrW(hwndEventDetails, GWL_EXSTYLE) | WS_EX_CLIENTEDGE);
3138     SetWindowPos(hwndEventDetails, NULL,
3139                  nVSplitPos + SPLIT_WIDTH/2,
3140                  nHSplitPos + SPLIT_WIDTH/2,
3141                  (rcClient.right - rcClient.left) - nVSplitPos - SPLIT_WIDTH/2,
3142                  (rcClient.bottom - rcClient.top) - nHSplitPos - SPLIT_WIDTH/2 - StatusHeight,
3143                  SWP_NOZORDER | SWP_NOACTIVATE | (Settings.bShowDetailsPane ? SWP_SHOWWINDOW : SWP_HIDEWINDOW));
3144     }
3145 
3146     /* Create the ListView */
3147     hwndListView = CreateWindowExW(WS_EX_CLIENTEDGE,
3148                                    WC_LISTVIEWW,
3149                                    NULL,
3150                                    WS_CHILD | WS_VISIBLE | LVS_SHOWSELALWAYS | LVS_REPORT,
3151                                    nVSplitPos + SPLIT_WIDTH/2,
3152                                    0,
3153                                    (rcClient.right - rcClient.left) - nVSplitPos - SPLIT_WIDTH/2,
3154                                    hwndEventDetails && Settings.bShowDetailsPane
3155                                        ? nHSplitPos - SPLIT_WIDTH/2
3156                                        : (rcClient.bottom - rcClient.top) - StatusHeight,
3157                                    hwndMainWindow,
3158                                    NULL,
3159                                    hInstance,
3160                                    NULL);
3161 
3162     /* Add the extended ListView styles */
3163     ListView_SetExtendedListViewStyle(hwndListView, LVS_EX_HEADERDRAGDROP | LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP | (Settings.bShowGrid ? LVS_EX_GRIDLINES : 0));
3164 
3165     /* Create the ImageList */
3166     hSmall = ImageList_Create(GetSystemMetrics(SM_CXSMICON),
3167                               GetSystemMetrics(SM_CYSMICON),
3168                               ILC_COLOR32 | ILC_MASK, // ILC_COLOR24
3169                               1, 1);
3170 
3171     /* Add event type icons to the ImageList */
3172     ImageList_AddIcon(hSmall, LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_INFORMATIONICON)));
3173     ImageList_AddIcon(hSmall, LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_WARNINGICON)));
3174     ImageList_AddIcon(hSmall, LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_ERRORICON)));
3175     ImageList_AddIcon(hSmall, LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_AUDITSUCCESSICON)));
3176     ImageList_AddIcon(hSmall, LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_AUDITFAILUREICON)));
3177 
3178     /* Assign the ImageList to the List View */
3179     ListView_SetImageList(hwndListView, hSmall, LVSIL_SMALL);
3180 
3181     /* Now set up the listview with its columns */
3182     lvc.mask = LVCF_TEXT | LVCF_WIDTH;
3183     lvc.cx = 90;
3184     LoadStringW(hInstance,
3185                 IDS_COLUMNTYPE,
3186                 szTemp,
3187                 ARRAYSIZE(szTemp));
3188     lvc.pszText = szTemp;
3189     ListView_InsertColumn(hwndListView, 0, &lvc);
3190 
3191     lvc.cx = 70;
3192     LoadStringW(hInstance,
3193                 IDS_COLUMNDATE,
3194                 szTemp,
3195                 ARRAYSIZE(szTemp));
3196     lvc.pszText = szTemp;
3197     ListView_InsertColumn(hwndListView, 1, &lvc);
3198 
3199     lvc.cx = 70;
3200     LoadStringW(hInstance,
3201                 IDS_COLUMNTIME,
3202                 szTemp,
3203                 ARRAYSIZE(szTemp));
3204     lvc.pszText = szTemp;
3205     ListView_InsertColumn(hwndListView, 2, &lvc);
3206 
3207     lvc.cx = 150;
3208     LoadStringW(hInstance,
3209                 IDS_COLUMNSOURCE,
3210                 szTemp,
3211                 ARRAYSIZE(szTemp));
3212     lvc.pszText = szTemp;
3213     ListView_InsertColumn(hwndListView, 3, &lvc);
3214 
3215     lvc.cx = 100;
3216     LoadStringW(hInstance,
3217                 IDS_COLUMNCATEGORY,
3218                 szTemp,
3219                 ARRAYSIZE(szTemp));
3220     lvc.pszText = szTemp;
3221     ListView_InsertColumn(hwndListView, 4, &lvc);
3222 
3223     lvc.cx = 60;
3224     LoadStringW(hInstance,
3225                 IDS_COLUMNEVENT,
3226                 szTemp,
3227                 ARRAYSIZE(szTemp));
3228     lvc.pszText = szTemp;
3229     ListView_InsertColumn(hwndListView, 5, &lvc);
3230 
3231     lvc.cx = 120;
3232     LoadStringW(hInstance,
3233                 IDS_COLUMNUSER,
3234                 szTemp,
3235                 ARRAYSIZE(szTemp));
3236     lvc.pszText = szTemp;
3237     ListView_InsertColumn(hwndListView, 6, &lvc);
3238 
3239     lvc.cx = 100;
3240     LoadStringW(hInstance,
3241                 IDS_COLUMNCOMPUTER,
3242                 szTemp,
3243                 ARRAYSIZE(szTemp));
3244     lvc.pszText = szTemp;
3245     ListView_InsertColumn(hwndListView, 7, &lvc);
3246 
3247     /* Initialize the save Dialog */
3248     ZeroMemory(&sfn, sizeof(sfn));
3249     ZeroMemory(szSaveFilter, sizeof(szSaveFilter));
3250 
3251     LoadStringW(hInst, IDS_SAVE_FILTER, szSaveFilter, ARRAYSIZE(szSaveFilter));
3252 
3253     sfn.lStructSize     = sizeof(sfn);
3254     sfn.hwndOwner       = hwndMainWindow;
3255     sfn.hInstance       = hInstance;
3256     sfn.lpstrFilter     = szSaveFilter;
3257     sfn.lpstrInitialDir = NULL;
3258     sfn.Flags           = OFN_EXPLORER | OFN_HIDEREADONLY | OFN_SHAREAWARE;
3259     sfn.lpstrDefExt     = L"evt";
3260 
3261     ShowWindow(hwndMainWindow, Settings.wpPos.showCmd);
3262     UpdateWindow(hwndMainWindow);
3263 
3264     return TRUE;
3265 }
3266 
3267 VOID ResizeWnd(INT cx, INT cy)
3268 {
3269     RECT rs;
3270     LONG StatusHeight;
3271     LONG_PTR dwExStyle;
3272     HDWP hdwp;
3273 
3274     /* Resize the status bar -- now done in WM_SIZE */
3275     // SendMessageW(hwndStatus, WM_SIZE, 0, 0);
3276     GetWindowRect(hwndStatus, &rs);
3277     StatusHeight = rs.bottom - rs.top;
3278 
3279     /*
3280      * Move the progress bar -- Take into account for extra size due to the static edge
3281      * (AdjustWindowRectEx() does not seem to work for the progress bar).
3282      */
3283     StatusBar_GetItemRect(hwndStatus, 0, &rs);
3284     dwExStyle = GetWindowLongPtrW(hwndStatusProgress, GWL_EXSTYLE);
3285     SetWindowLongPtrW(hwndStatusProgress, GWL_EXSTYLE, dwExStyle | WS_EX_STATICEDGE);
3286     MoveWindow(hwndStatusProgress,
3287                rs.left, rs.top, rs.right - rs.left, rs.bottom - rs.top,
3288                IsWindowVisible(hwndStatusProgress) ? TRUE : FALSE);
3289     SetWindowLongPtrW(hwndStatusProgress, GWL_EXSTYLE, dwExStyle);
3290 
3291     /*
3292      * TODO: Adjust the splitter positions:
3293      * - Vertical splitter (1)  : fixed position from the left window side.
3294      * - Horizontal splitter (2): fixed position from the bottom window side.
3295      */
3296     nVSplitPos = min(max(nVSplitPos, SPLIT_WIDTH/2), cx - SPLIT_WIDTH/2); // OK
3297     nHSplitPos = min(max(nHSplitPos, SPLIT_WIDTH/2), cy - SPLIT_WIDTH/2 - StatusHeight); // FIXME!
3298 
3299     hdwp = BeginDeferWindowPos(3);
3300 
3301     if (hdwp)
3302         hdwp = DeferWindowPos(hdwp,
3303                               hwndTreeView,
3304                               HWND_TOP,
3305                               0, 0,
3306                               nVSplitPos - SPLIT_WIDTH/2,
3307                               cy - StatusHeight,
3308                               SWP_NOZORDER | SWP_NOACTIVATE);
3309 
3310     if (hdwp)
3311         hdwp = DeferWindowPos(hdwp,
3312                               hwndListView,
3313                               HWND_TOP,
3314                               nVSplitPos + SPLIT_WIDTH/2, 0,
3315                               cx - nVSplitPos - SPLIT_WIDTH/2,
3316                               hwndEventDetails && Settings.bShowDetailsPane
3317                                   ? nHSplitPos - SPLIT_WIDTH/2
3318                                   : cy - StatusHeight,
3319                               SWP_NOZORDER | SWP_NOACTIVATE);
3320 
3321     if (hwndEventDetails && Settings.bShowDetailsPane && hdwp)
3322         hdwp = DeferWindowPos(hdwp,
3323                               hwndEventDetails,
3324                               HWND_TOP,
3325                               nVSplitPos + SPLIT_WIDTH/2,
3326                               nHSplitPos + SPLIT_WIDTH/2,
3327                               cx - nVSplitPos - SPLIT_WIDTH/2,
3328                               cy - nHSplitPos - SPLIT_WIDTH/2 - StatusHeight,
3329                               SWP_NOZORDER | SWP_NOACTIVATE);
3330 
3331     if (hdwp)
3332         EndDeferWindowPos(hdwp);
3333 }
3334 
3335 
3336 LRESULT CALLBACK
3337 WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3338 {
3339     RECT rect;
3340 
3341     switch (uMsg)
3342     {
3343         case WM_CREATE:
3344             hMainMenu = GetMenu(hWnd);
3345             break;
3346 
3347         case WM_DESTROY:
3348         {
3349             GetWindowPlacement(hwndMainWindow, &Settings.wpPos);
3350             PostQuitMessage(0);
3351             break;
3352         }
3353 
3354         case WM_NOTIFY:
3355         {
3356             LPNMHDR hdr = (LPNMHDR)lParam;
3357 
3358             if (hdr->hwndFrom == hwndListView)
3359             {
3360                 switch (hdr->code)
3361                 {
3362                     case LVN_ITEMCHANGED:
3363                     {
3364                         LPNMLISTVIEW pnmv = (LPNMLISTVIEW)lParam;
3365 
3366                         if ( (pnmv->uChanged  & LVIF_STATE) && /* The state has changed */
3367                              (pnmv->uNewState & LVIS_SELECTED) /* The item has been (de)selected */ )
3368                         {
3369                             if (!hwndEventDetails)
3370                                 break;
3371 
3372                             /* Verify the index of selected item */
3373                             if (pnmv->iItem == -1)
3374                             {
3375                                 MessageBoxW(hWnd,
3376                                             L"No selected items!",
3377                                             szTitle,
3378                                             MB_OK | MB_ICONERROR);
3379                                 break;
3380                             }
3381                             SendMessageW(hwndEventDetails, EVT_DISPLAY, 0, (LPARAM)pnmv->iItem);
3382                         }
3383                         break;
3384                     }
3385 
3386 #ifdef LVN_ITEMACTIVATE
3387                     case LVN_ITEMACTIVATE:
3388                     {
3389                         /* Get the index of the single focused selected item */
3390                         LPNMITEMACTIVATE lpnmitem = (LPNMITEMACTIVATE)lParam;
3391                         INT iItem = lpnmitem->iItem;
3392                         if (iItem != -1)
3393                             SendMessageW(hWnd, WM_COMMAND, IDM_EVENT_DETAILS, (LPARAM)iItem);
3394                         break;
3395                     }
3396 #else // LVN_ITEMACTIVATE
3397                     case NM_DBLCLK:
3398                     case NM_RETURN:
3399                     {
3400                         /* Get the index of the single focused selected item */
3401                         INT iItem = ListView_GetNextItem(hwndListView, -1, LVNI_FOCUSED | LVNI_SELECTED);
3402                         if (iItem != -1)
3403                             SendMessageW(hWnd, WM_COMMAND, IDM_EVENT_DETAILS, (LPARAM)iItem);
3404                         break;
3405                     }
3406 #endif // LVN_ITEMACTIVATE
3407                 }
3408             }
3409             else if (hdr->hwndFrom == hwndTreeView)
3410             {
3411                 switch (hdr->code)
3412                 {
3413                     case TVN_BEGINLABELEDIT:
3414                     {
3415                         HTREEITEM hItem = ((LPNMTVDISPINFO)lParam)->item.hItem;
3416 
3417                         /* Disable label editing for root nodes */
3418                         return ((hItem == htiSystemLogs) ||
3419                                 (hItem == htiAppLogs)    ||
3420                                 (hItem == htiUserLogs));
3421                     }
3422 
3423                     case TVN_ENDLABELEDIT:
3424                     {
3425                         TVITEMW item = ((LPNMTVDISPINFO)lParam)->item;
3426                         HTREEITEM hItem = item.hItem;
3427 
3428                         /* Disable label editing for root nodes */
3429                         if ((hItem == htiSystemLogs) ||
3430                             (hItem == htiAppLogs)    ||
3431                             (hItem == htiUserLogs))
3432                         {
3433                             return FALSE;
3434                         }
3435 
3436                         if (item.pszText)
3437                         {
3438                             LPWSTR pszText = item.pszText;
3439 
3440                             /* Trim leading whitespace */
3441                             while (*pszText && iswspace(*pszText))
3442                                 ++pszText;
3443 
3444                             if (!*pszText)
3445                                 return FALSE;
3446 
3447                             return TRUE;
3448                         }
3449                         else
3450                         {
3451                             return FALSE;
3452                         }
3453                     }
3454 
3455                     case TVN_SELCHANGED:
3456                     {
3457                         PEVENTLOGFILTER EventLogFilter =
3458                             (PEVENTLOGFILTER)((LPNMTREEVIEW)lParam)->itemNew.lParam;
3459 
3460                         // FIXME: It might be nice to reference here the filter,
3461                         // so that we don't have to reference/dereference it many times
3462                         // in the other functions???
3463 
3464                         // FIXME: This is a hack!!
3465                         if (hwndEventDetails && EventLogFilter)
3466                         {
3467                             SendMessageW(hwndEventDetails, EVT_SETFILTER, 0, (LPARAM)EventLogFilter);
3468                         }
3469 
3470                         if (EventLogFilter)
3471                         {
3472                             /*
3473                              * If we have selected a filter, enable the menu commands;
3474                              * they will possibly be updated after events enumeration.
3475                              */
3476                             EnableMenuItem(hMainMenu, IDM_SAVE_EVENTLOG, MF_BYCOMMAND | MF_ENABLED);
3477                             EnableMenuItem(hMainMenu, IDM_CLOSE_EVENTLOG, MF_BYCOMMAND | MF_ENABLED);
3478                             EnableMenuItem(hMainMenu, IDM_CLEAR_EVENTS, MF_BYCOMMAND | MF_ENABLED);
3479                             EnableMenuItem(hMainMenu, IDM_RENAME_EVENTLOG, MF_BYCOMMAND | MF_ENABLED);
3480                             EnableMenuItem(hMainMenu, IDM_EVENTLOG_SETTINGS, MF_BYCOMMAND | MF_ENABLED);
3481                         }
3482                         else
3483                         {
3484                             EnableMenuItem(hMainMenu, IDM_SAVE_EVENTLOG, MF_BYCOMMAND | MF_GRAYED);
3485                             EnableMenuItem(hMainMenu, IDM_CLOSE_EVENTLOG, MF_BYCOMMAND | MF_GRAYED);
3486                             EnableMenuItem(hMainMenu, IDM_CLEAR_EVENTS, MF_BYCOMMAND | MF_GRAYED);
3487                             EnableMenuItem(hMainMenu, IDM_RENAME_EVENTLOG, MF_BYCOMMAND | MF_GRAYED);
3488                             EnableMenuItem(hMainMenu, IDM_EVENTLOG_SETTINGS, MF_BYCOMMAND | MF_GRAYED);
3489                         }
3490 
3491                         /*
3492                          * The enumeration thread that is triggered by EnumEvents
3493                          * will set a new value for the 'ActiveFilter'.
3494                          */
3495                         if (EventLogFilter)
3496                             EnumEvents(EventLogFilter);
3497 
3498                         break;
3499                     }
3500                 }
3501             }
3502             break;
3503         }
3504 
3505         case WM_COMMAND:
3506         {
3507             /* Parse the menu selections */
3508             switch (LOWORD(wParam))
3509             {
3510                 case IDM_OPEN_EVENTLOG:
3511                     OpenUserEventLog();
3512                     break;
3513 
3514                 case IDM_SAVE_EVENTLOG:
3515                     SaveEventLog(GetSelectedFilter(NULL));
3516                     break;
3517 
3518                 case IDM_CLOSE_EVENTLOG:
3519                 {
3520                     HTREEITEM hti;
3521                     PEVENTLOGFILTER EventLogFilter = GetSelectedFilter(&hti);
3522                     CloseUserEventLog(EventLogFilter, hti);
3523                     break;
3524                 }
3525 
3526                 case IDM_CLEAR_EVENTS:
3527                 {
3528                     PEVENTLOGFILTER EventLogFilter = GetSelectedFilter(NULL);
3529                     if (EventLogFilter && ClearEvents(EventLogFilter))
3530                         Refresh(EventLogFilter);
3531                     break;
3532                 }
3533 
3534                 case IDM_RENAME_EVENTLOG:
3535                     if (GetFocus() == hwndTreeView)
3536                         TreeView_EditLabel(hwndTreeView, TreeView_GetSelection(hwndTreeView));
3537                     break;
3538 
3539                 case IDM_EVENTLOG_SETTINGS:
3540                 {
3541                     PEVENTLOGFILTER EventLogFilter = GetSelectedFilter(NULL);
3542                     // TODO: Check the returned value?
3543                     if (EventLogFilter)
3544                         EventLogProperties(hInst, hWnd, EventLogFilter);
3545                     break;
3546                 }
3547 
3548                 case IDM_LIST_NEWEST:
3549                 case IDM_LIST_OLDEST:
3550                 {
3551                     BOOL bNewest = (LOWORD(wParam) == IDM_LIST_NEWEST);
3552                     CheckMenuRadioItem(hMainMenu, IDM_LIST_NEWEST, IDM_LIST_OLDEST, LOWORD(wParam), MF_BYCOMMAND);
3553 
3554                     if (bNewest != Settings.bNewestEventsFirst)
3555                     {
3556                         Settings.bNewestEventsFirst = bNewest;
3557                         Refresh(GetSelectedFilter(NULL));
3558                     }
3559                     break;
3560                 }
3561 
3562                 case IDM_EVENT_DETAILS:
3563                 {
3564                     INT iItem;
3565                     PEVENTLOGFILTER EventLogFilter;
3566 
3567                     /* Get the index of the single focused selected item */
3568                     iItem = ListView_GetNextItem(hwndListView, -1, LVNI_FOCUSED | LVNI_SELECTED);
3569                     if (iItem == -1)
3570                     {
3571                         /**
3572                         // FIXME: Reenable this check once menu items are
3573                         // correctly disabled when no event is selected, etc.
3574                         MessageBoxW(hWnd,
3575                                     L"No selected items!",
3576                                     szTitle,
3577                                     MB_OK | MB_ICONERROR);
3578                         **/
3579                         break;
3580                     }
3581 
3582                     EventLogFilter = GetSelectedFilter(NULL);
3583                     if (EventLogFilter)
3584                     {
3585                         EVENTDETAIL_INFO DetailInfo = {EventLogFilter, iItem};
3586 
3587                         EventLogFilter_AddRef(EventLogFilter);
3588                         DialogBoxParamW(hInst,
3589                                         MAKEINTRESOURCEW(IDD_EVENTDETAILS_DLG),
3590                                         hWnd,
3591                                         EventDetails,
3592                                         (LPARAM)&DetailInfo);
3593                         EventLogFilter_Release(EventLogFilter);
3594                     }
3595                     break;
3596                 }
3597 
3598                 case IDM_REFRESH:
3599                     Refresh(GetSelectedFilter(NULL));
3600                     break;
3601 
3602                 case IDM_EVENT_DETAILS_VIEW:
3603                 {
3604                     Settings.bShowDetailsPane = !Settings.bShowDetailsPane;
3605                     CheckMenuItem(hMainMenu, IDM_EVENT_DETAILS_VIEW,
3606                                   MF_BYCOMMAND | (Settings.bShowDetailsPane ? MF_CHECKED : MF_UNCHECKED));
3607 
3608                     GetClientRect(hWnd, &rect);
3609                     if (Settings.bShowDetailsPane)
3610                     {
3611                         ResizeWnd(rect.right - rect.left, rect.bottom - rect.top);
3612                         ShowWindow(hwndEventDetails, SW_SHOW);
3613                     }
3614                     else
3615                     {
3616                         ShowWindow(hwndEventDetails, SW_HIDE);
3617                         ResizeWnd(rect.right - rect.left, rect.bottom - rect.top);
3618                     }
3619 
3620                     break;
3621                 }
3622 
3623                 case IDM_LIST_GRID_LINES:
3624                 {
3625                     Settings.bShowGrid = !Settings.bShowGrid;
3626                     CheckMenuItem(hMainMenu, IDM_LIST_GRID_LINES,
3627                                   MF_BYCOMMAND | (Settings.bShowGrid ? MF_CHECKED : MF_UNCHECKED));
3628 
3629                     ListView_SetExtendedListViewStyleEx(hwndListView, LVS_EX_GRIDLINES, (Settings.bShowGrid ? LVS_EX_GRIDLINES : 0));
3630                     break;
3631                 }
3632 
3633                 case IDM_SAVE_SETTINGS:
3634                 {
3635                     Settings.bSaveSettings = !Settings.bSaveSettings;
3636                     CheckMenuItem(hMainMenu, IDM_SAVE_SETTINGS,
3637                                   MF_BYCOMMAND | (Settings.bSaveSettings ? MF_CHECKED : MF_UNCHECKED));
3638                     break;
3639                 }
3640 
3641                 case IDM_ABOUT:
3642                 {
3643                     HICON hIcon;
3644                     WCHAR szCopyright[MAX_LOADSTRING];
3645 
3646                     hIcon = LoadIconW(hInst, MAKEINTRESOURCEW(IDI_EVENTVWR));
3647                     LoadStringW(hInst, IDS_COPYRIGHT, szCopyright, ARRAYSIZE(szCopyright));
3648                     ShellAboutW(hWnd, szTitle, szCopyright, hIcon);
3649                     DeleteObject(hIcon);
3650                     break;
3651                 }
3652 
3653                 case IDM_HELP:
3654                     MessageBoxW(hWnd,
3655                                 L"Help not implemented yet!",
3656                                 szTitle,
3657                                 MB_OK | MB_ICONINFORMATION);
3658                     break;
3659 
3660                 case IDM_EXIT:
3661                     DestroyWindow(hWnd);
3662                     break;
3663 
3664                 default:
3665                     return DefWindowProcW(hWnd, uMsg, wParam, lParam);
3666             }
3667             break;
3668         }
3669 
3670         case WM_INITMENU:
3671         {
3672             if ((HMENU)wParam != hMainMenu)
3673                 break;
3674 
3675             CheckMenuRadioItem(hMainMenu, IDM_LIST_NEWEST, IDM_LIST_OLDEST,
3676                                Settings.bNewestEventsFirst ? IDM_LIST_NEWEST : IDM_LIST_OLDEST,
3677                                MF_BYCOMMAND);
3678 
3679             if (!hwndEventDetails)
3680             {
3681                 EnableMenuItem(hMainMenu, IDM_EVENT_DETAILS_VIEW,
3682                                MF_BYCOMMAND | MF_GRAYED);
3683             }
3684             CheckMenuItem(hMainMenu, IDM_EVENT_DETAILS_VIEW,
3685                           MF_BYCOMMAND | (Settings.bShowDetailsPane ? MF_CHECKED : MF_UNCHECKED));
3686 
3687             CheckMenuItem(hMainMenu, IDM_LIST_GRID_LINES,
3688                           MF_BYCOMMAND | (Settings.bShowGrid ? MF_CHECKED : MF_UNCHECKED));
3689 
3690             CheckMenuItem(hMainMenu, IDM_SAVE_SETTINGS,
3691                           MF_BYCOMMAND | (Settings.bSaveSettings ? MF_CHECKED : MF_UNCHECKED));
3692 
3693             break;
3694         }
3695 
3696 #if 0
3697         case WM_INITMENUPOPUP:
3698             lParam = lParam;
3699             break;
3700 #endif
3701 
3702         case WM_CONTEXTMENU:
3703         {
3704             RECT rc;
3705             HTREEITEM hItem;
3706             TVHITTESTINFO hInfo = {0};
3707 
3708             INT xPos = GET_X_LPARAM(lParam);
3709             INT yPos = GET_Y_LPARAM(lParam);
3710 
3711             GetWindowRect(hwndTreeView, &rc);
3712             hInfo.pt.x = xPos - rc.left;
3713             hInfo.pt.y = yPos - rc.top;
3714 
3715             hItem = TreeView_HitTest(hwndTreeView, &hInfo);
3716             if (hItem)
3717             {
3718                 TreeView_SelectItem(hwndTreeView, hItem);
3719 
3720                 if (TreeView_GetParent(hwndTreeView, hItem))
3721                 {
3722                     HMENU hCtxMenu = GetSubMenu(LoadMenuW(hInst, MAKEINTRESOURCEW(IDM_EVENTWR_CTX)), 0);
3723 
3724                     DWORD dwCmdID = TrackPopupMenuEx(hCtxMenu,
3725                                                      TPM_LEFTALIGN | TPM_TOPALIGN | TPM_NONOTIFY | TPM_RETURNCMD,
3726                                                      xPos, yPos, hWnd, NULL);
3727                     SendMessageW(hWnd, WM_COMMAND, (WPARAM)dwCmdID, (LPARAM)hwndTreeView);
3728                 }
3729             }
3730             break;
3731         }
3732 
3733         case WM_SETCURSOR:
3734         {
3735             POINT pt;
3736 
3737             if (LOWORD(lParam) != HTCLIENT)
3738                 goto Default;
3739 
3740             GetCursorPos(&pt);
3741             ScreenToClient(hWnd, &pt);
3742 
3743             /* Set the cursor for the vertical splitter */
3744             if (pt.x >= nVSplitPos - SPLIT_WIDTH/2 && pt.x < nVSplitPos + SPLIT_WIDTH/2 + 1)
3745             {
3746                 RECT rs;
3747                 GetClientRect(hWnd, &rect);
3748                 GetWindowRect(hwndStatus, &rs);
3749                 if (pt.y >= rect.top && pt.y < rect.bottom - (rs.bottom - rs.top))
3750                 {
3751                     SetCursor(LoadCursorW(NULL, IDC_SIZEWE));
3752                     return TRUE;
3753                 }
3754             }
3755             else
3756             /* Set the cursor for the horizontal splitter, if the Event details pane is displayed */
3757             if (hwndEventDetails && Settings.bShowDetailsPane &&
3758                 (pt.y >= nHSplitPos - SPLIT_WIDTH/2 && pt.y < nHSplitPos + SPLIT_WIDTH/2 + 1))
3759             {
3760                 // RECT rs;
3761                 GetClientRect(hWnd, &rect);
3762                 // GetWindowRect(hwndStatus, &rs);
3763                 if (pt.x >= nVSplitPos + SPLIT_WIDTH/2 + 1 /* rect.left + (rs.bottom - rs.top) */ &&
3764                     pt.x < rect.right)
3765                 {
3766                     SetCursor(LoadCursorW(NULL, IDC_SIZENS));
3767                     return TRUE;
3768                 }
3769             }
3770             goto Default;
3771         }
3772 
3773         case WM_LBUTTONDOWN:
3774         {
3775             INT x = GET_X_LPARAM(lParam);
3776             INT y = GET_Y_LPARAM(lParam);
3777 
3778             /* Reset the splitter state */
3779             bSplit = 0;
3780 
3781             /* Capture the cursor for the vertical splitter */
3782             if (x >= nVSplitPos - SPLIT_WIDTH/2 && x < nVSplitPos + SPLIT_WIDTH/2 + 1)
3783             {
3784                 bSplit = 1;
3785                 SetCapture(hWnd);
3786             }
3787             else
3788             /* Capture the cursor for the horizontal splitter, if the Event details pane is displayed */
3789             if (hwndEventDetails && Settings.bShowDetailsPane &&
3790                 (y >= nHSplitPos - SPLIT_WIDTH/2 && y < nHSplitPos + SPLIT_WIDTH/2 + 1))
3791             {
3792                 bSplit = 2;
3793                 SetCapture(hWnd);
3794             }
3795             break;
3796         }
3797 
3798         case WM_LBUTTONUP:
3799         case WM_RBUTTONDOWN:
3800         {
3801             if (GetCapture() != hWnd)
3802                 break;
3803 
3804             /* Adjust the correct splitter position */
3805             if (bSplit == 1)
3806                 nVSplitPos = GET_X_LPARAM(lParam);
3807             else if (bSplit == 2)
3808                 nHSplitPos = GET_Y_LPARAM(lParam);
3809 
3810             /* If we are splitting, resize the windows */
3811             if (bSplit != 0)
3812             {
3813                 GetClientRect(hWnd, &rect);
3814                 ResizeWnd(rect.right - rect.left, rect.bottom - rect.top);
3815             }
3816 
3817             /* Reset the splitter state */
3818             bSplit = 0;
3819 
3820             ReleaseCapture();
3821             break;
3822         }
3823 
3824         case WM_MOUSEMOVE:
3825         {
3826             if (GetCapture() != hWnd)
3827                 break;
3828 
3829             /* Move the correct splitter */
3830             if (bSplit == 1)
3831             {
3832                 INT x = GET_X_LPARAM(lParam);
3833 
3834                 GetClientRect(hWnd, &rect);
3835 
3836                 x = min(max(x, SPLIT_WIDTH/2), rect.right - rect.left - SPLIT_WIDTH/2);
3837                 if (nVSplitPos != x)
3838                 {
3839                     nVSplitPos = x;
3840                     ResizeWnd(rect.right - rect.left, rect.bottom - rect.top);
3841                 }
3842             }
3843             else if (bSplit == 2)
3844             {
3845                 RECT rs;
3846                 INT y = GET_Y_LPARAM(lParam);
3847 
3848                 GetClientRect(hWnd, &rect);
3849                 GetWindowRect(hwndStatus, &rs);
3850 
3851                 y = min(max(y, SPLIT_WIDTH/2), rect.bottom - rect.top - SPLIT_WIDTH/2 - (rs.bottom - rs.top));
3852                 if (nHSplitPos != y)
3853                 {
3854                     nHSplitPos = y;
3855                     ResizeWnd(rect.right - rect.left, rect.bottom - rect.top);
3856                 }
3857             }
3858             break;
3859         }
3860 
3861         case WM_SIZE:
3862         {
3863             if (wParam != SIZE_MINIMIZED)
3864             {
3865                 SendMessageW(hwndStatus, WM_SIZE, 0, 0);
3866                 ResizeWnd(LOWORD(lParam), HIWORD(lParam));
3867                 break;
3868             }
3869             /* Fall through the default case */
3870         }
3871 
3872         default: Default:
3873             return DefWindowProcW(hWnd, uMsg, wParam, lParam);
3874     }
3875 
3876     return 0;
3877 }
3878 
3879 
3880 static
3881 VOID
3882 InitPropertiesDlg(HWND hDlg, PEVENTLOG EventLog)
3883 {
3884     LPWSTR lpLogName = EventLog->LogName;
3885 
3886     DWORD Result, dwType;
3887     DWORD dwMaxSize = 0, dwRetention = 0;
3888     BOOL Success;
3889     WIN32_FIND_DATAW FileInfo; // WIN32_FILE_ATTRIBUTE_DATA
3890     ULARGE_INTEGER FileSize;
3891     WCHAR wszBuf[MAX_PATH];
3892     WCHAR szTemp[MAX_LOADSTRING];
3893     LPWSTR FileName;
3894 
3895     HKEY hLogKey;
3896     WCHAR *KeyPath;
3897     DWORD cbData;
3898     SIZE_T cbKeyPath;
3899 
3900     if (EventLog->Permanent)
3901     {
3902 
3903     cbKeyPath = (wcslen(EVENTLOG_BASE_KEY) + wcslen(lpLogName) + 1) * sizeof(WCHAR);
3904     KeyPath = HeapAlloc(GetProcessHeap(), 0, cbKeyPath);
3905     if (!KeyPath)
3906     {
3907         ShowWin32Error(ERROR_NOT_ENOUGH_MEMORY);
3908         goto Quit;
3909     }
3910 
3911     StringCbCopyW(KeyPath, cbKeyPath, EVENTLOG_BASE_KEY);
3912     StringCbCatW(KeyPath, cbKeyPath, lpLogName);
3913 
3914     Result = RegOpenKeyExW(hkMachine, KeyPath, 0, KEY_QUERY_VALUE, &hLogKey);
3915     HeapFree(GetProcessHeap(), 0, KeyPath);
3916     if (Result != ERROR_SUCCESS)
3917     {
3918         ShowWin32Error(Result);
3919         goto Quit;
3920     }
3921 
3922 
3923     cbData = sizeof(dwMaxSize);
3924     Result = RegQueryValueExW(hLogKey,
3925                               L"MaxSize",
3926                               NULL,
3927                               &dwType,
3928                               (LPBYTE)&dwMaxSize,
3929                               &cbData);
3930     if ((Result != ERROR_SUCCESS) || (dwType != REG_DWORD))
3931     {
3932         // dwMaxSize = 512 * 1024; /* 512 kBytes */
3933         /* Workstation: 512 KB; Server: 16384 KB */
3934         dwMaxSize = 16384 * 1024;
3935     }
3936     /* Convert in KB */
3937     dwMaxSize /= 1024;
3938 
3939     cbData = sizeof(dwRetention);
3940     Result = RegQueryValueExW(hLogKey,
3941                               L"Retention",
3942                               NULL,
3943                               &dwType,
3944                               (LPBYTE)&dwRetention,
3945                               &cbData);
3946     if ((Result != ERROR_SUCCESS) || (dwType != REG_DWORD))
3947     {
3948         /* By default, it is 604800 (secs) == 7 days. On Server, the retention is zeroed out. */
3949         dwRetention = 0;
3950     }
3951     /* Convert in days, rounded up */
3952     if (dwRetention != INFINITE)
3953         dwRetention = (dwRetention + 24*3600 - 1) / (24*3600);
3954 
3955     RegCloseKey(hLogKey);
3956 
3957     }
3958 
3959 Quit:
3960 
3961     SetDlgItemTextW(hDlg, IDC_DISPLAYNAME, lpLogName); // FIXME!
3962     SetDlgItemTextW(hDlg, IDC_LOGNAME, lpLogName);
3963 
3964     FileName = EventLog->FileName;
3965     if (FileName && *FileName)
3966     {
3967         /* Expand the file name. If the log file is on a remote computer, retrieve the network share form of the file name. */
3968         GetExpandedFilePathName(EventLog->ComputerName, FileName, wszBuf, ARRAYSIZE(wszBuf));
3969         FileName = wszBuf;
3970     }
3971     else
3972     {
3973         FileName = L"";
3974     }
3975     SetDlgItemTextW(hDlg, IDC_LOGFILE, FileName);
3976 
3977     if (FileName && *FileName)
3978     {
3979         /*
3980          * The general problem here (and in the shell as well) is that
3981          * GetFileAttributesEx fails for files that are opened without
3982          * shared access. To retrieve file information for those we need
3983          * to use something else: FindFirstFile, on the full file name.
3984          */
3985         Success = GetFileAttributesExW(FileName,
3986                                        GetFileExInfoStandard,
3987                                        (LPWIN32_FILE_ATTRIBUTE_DATA)&FileInfo);
3988         if (!Success)
3989         {
3990             HANDLE hFind = FindFirstFileW(FileName, &FileInfo);
3991             Success = (hFind != INVALID_HANDLE_VALUE);
3992             if (Success)
3993                 FindClose(hFind);
3994         }
3995     }
3996     else
3997     {
3998         Success = FALSE;
3999     }
4000 
4001     /* Starting there, FileName becomes invalid because we are reusing wszBuf */
4002 
4003     if (Success)
4004     {
4005         FileSize.u.LowPart = FileInfo.nFileSizeLow;
4006         FileSize.u.HighPart = FileInfo.nFileSizeHigh;
4007         if (FormatFileSizeWithBytes(&FileSize, wszBuf, ARRAYSIZE(wszBuf)))
4008             SetDlgItemTextW(hDlg, IDC_SIZE_LABEL, wszBuf);
4009 
4010         LoadStringW(hInst, IDS_NOT_AVAILABLE, szTemp, ARRAYSIZE(szTemp));
4011 
4012         if (GetFileTimeString(&FileInfo.ftCreationTime, wszBuf, ARRAYSIZE(wszBuf)))
4013             SetDlgItemTextW(hDlg, IDC_CREATED_LABEL, wszBuf);
4014         else
4015             SetDlgItemTextW(hDlg, IDC_CREATED_LABEL, szTemp);
4016 
4017         if (GetFileTimeString(&FileInfo.ftLastWriteTime, wszBuf, ARRAYSIZE(wszBuf)))
4018             SetDlgItemTextW(hDlg, IDC_MODIFIED_LABEL, wszBuf);
4019         else
4020             SetDlgItemTextW(hDlg, IDC_MODIFIED_LABEL, szTemp);
4021 
4022         if (GetFileTimeString(&FileInfo.ftLastAccessTime, wszBuf, ARRAYSIZE(wszBuf)))
4023             SetDlgItemTextW(hDlg, IDC_ACCESSED_LABEL, wszBuf);
4024         else
4025             SetDlgItemTextW(hDlg, IDC_MODIFIED_LABEL, szTemp);
4026     }
4027     else
4028     {
4029         LoadStringW(hInst, IDS_NOT_AVAILABLE, szTemp, ARRAYSIZE(szTemp));
4030 
4031         SetDlgItemTextW(hDlg, IDC_SIZE_LABEL, szTemp);
4032         SetDlgItemTextW(hDlg, IDC_CREATED_LABEL, szTemp);
4033         SetDlgItemTextW(hDlg, IDC_MODIFIED_LABEL, szTemp);
4034         SetDlgItemTextW(hDlg, IDC_ACCESSED_LABEL, szTemp);
4035     }
4036 
4037     if (EventLog->Permanent)
4038     {
4039         SendDlgItemMessageW(hDlg, IDC_UPDOWN_MAXLOGSIZE, UDM_SETRANGE32, (WPARAM)1, (LPARAM)0x3FFFC0);
4040         SendDlgItemMessageW(hDlg, IDC_UPDOWN_EVENTS_AGE, UDM_SETRANGE, 0, (LPARAM)MAKELONG(365, 1));
4041 
4042         SetDlgItemInt(hDlg, IDC_EDIT_MAXLOGSIZE, dwMaxSize, FALSE);
4043         SetDlgItemInt(hDlg, IDC_EDIT_EVENTS_AGE, (dwRetention == 0) ? 7 : dwRetention, FALSE);
4044 
4045         if (dwRetention == 0)
4046         {
4047             CheckRadioButton(hDlg, IDC_OVERWRITE_AS_NEEDED, IDC_NO_OVERWRITE, IDC_OVERWRITE_AS_NEEDED);
4048             EnableDlgItem(hDlg, IDC_EDIT_EVENTS_AGE, FALSE);
4049             EnableDlgItem(hDlg, IDC_UPDOWN_EVENTS_AGE, FALSE);
4050         }
4051         else if (dwRetention == INFINITE)
4052         {
4053             CheckRadioButton(hDlg, IDC_OVERWRITE_AS_NEEDED, IDC_NO_OVERWRITE, IDC_NO_OVERWRITE);
4054             EnableDlgItem(hDlg, IDC_EDIT_EVENTS_AGE, FALSE);
4055             EnableDlgItem(hDlg, IDC_UPDOWN_EVENTS_AGE, FALSE);
4056         }
4057         else
4058         {
4059             CheckRadioButton(hDlg, IDC_OVERWRITE_AS_NEEDED, IDC_NO_OVERWRITE, IDC_OVERWRITE_OLDER_THAN);
4060             EnableDlgItem(hDlg, IDC_EDIT_EVENTS_AGE, TRUE);
4061             EnableDlgItem(hDlg, IDC_UPDOWN_EVENTS_AGE, TRUE);
4062         }
4063     }
4064     else
4065     {
4066         // TODO: Hide the unused controls! Or, just use another type of property sheet!
4067     }
4068 }
4069 
4070 static
4071 VOID
4072 SavePropertiesDlg(HWND hDlg, PEVENTLOG EventLog)
4073 {
4074     LPWSTR lpLogName = EventLog->LogName;
4075 
4076     LONG Result;
4077     DWORD dwMaxSize = 0, dwRetention = 0;
4078     HKEY hLogKey;
4079     WCHAR *KeyPath;
4080     SIZE_T cbKeyPath;
4081 
4082     if (!EventLog->Permanent)
4083         return;
4084 
4085     cbKeyPath = (wcslen(EVENTLOG_BASE_KEY) + wcslen(lpLogName) + 1) * sizeof(WCHAR);
4086     KeyPath = HeapAlloc(GetProcessHeap(), 0, cbKeyPath);
4087     if (!KeyPath)
4088     {
4089         ShowWin32Error(ERROR_NOT_ENOUGH_MEMORY);
4090         return;
4091     }
4092 
4093     StringCbCopyW(KeyPath, cbKeyPath, EVENTLOG_BASE_KEY);
4094     StringCbCatW(KeyPath, cbKeyPath, lpLogName);
4095 
4096     Result = RegOpenKeyExW(hkMachine, KeyPath, 0, KEY_SET_VALUE, &hLogKey);
4097     HeapFree(GetProcessHeap(), 0, KeyPath);
4098     if (Result != ERROR_SUCCESS)
4099     {
4100         ShowWin32Error(Result);
4101         return;
4102     }
4103 
4104     dwMaxSize = GetDlgItemInt(hDlg, IDC_EDIT_MAXLOGSIZE, NULL, FALSE) * 1024;
4105     RegSetValueExW(hLogKey,
4106                    L"MaxSize",
4107                    0,
4108                    REG_DWORD,
4109                    (LPBYTE)&dwMaxSize,
4110                    sizeof(dwMaxSize));
4111 
4112     if (IsDlgButtonChecked(hDlg, IDC_OVERWRITE_AS_NEEDED) == BST_CHECKED)
4113         dwRetention = 0;
4114     else if (IsDlgButtonChecked(hDlg, IDC_NO_OVERWRITE) == BST_CHECKED)
4115         dwRetention = INFINITE;
4116     else // if (IsDlgButtonChecked(hDlg, IDC_OVERWRITE_OLDER_THAN) == BST_CHECKED)
4117         dwRetention = GetDlgItemInt(hDlg, IDC_EDIT_EVENTS_AGE, NULL, FALSE) * (24*3600);
4118 
4119     RegSetValueExW(hLogKey,
4120                    L"Retention",
4121                    0,
4122                    REG_DWORD,
4123                    (LPBYTE)&dwRetention,
4124                    sizeof(dwRetention));
4125 
4126     RegCloseKey(hLogKey);
4127 }
4128 
4129 /* Message handler for EventLog Properties dialog */
4130 INT_PTR CALLBACK
4131 EventLogPropProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
4132 {
4133     PEVENTLOG EventLog;
4134     WCHAR szText[MAX_LOADSTRING];
4135 
4136     EventLog = (PEVENTLOG)GetWindowLongPtrW(hDlg, DWLP_USER);
4137 
4138     switch (uMsg)
4139     {
4140         case WM_INITDIALOG:
4141         {
4142             EventLog = (PEVENTLOG)((LPPROPSHEETPAGE)lParam)->lParam;
4143             SetWindowLongPtrW(hDlg, DWLP_USER, (LONG_PTR)EventLog);
4144 
4145             InitPropertiesDlg(hDlg, EventLog);
4146 
4147             PropSheet_UnChanged(GetParent(hDlg), hDlg);
4148             return (INT_PTR)TRUE;
4149         }
4150 
4151         case WM_DESTROY:
4152             return (INT_PTR)TRUE;
4153 
4154         case WM_NOTIFY:
4155             switch (((LPNMHDR)lParam)->code)
4156             {
4157                 case PSN_APPLY:
4158                     PropSheet_UnChanged(GetParent(hDlg), hDlg);
4159                     SavePropertiesDlg(hDlg, EventLog);
4160                     return (INT_PTR)TRUE;
4161             }
4162             break;
4163 
4164         case WM_COMMAND:
4165             switch (LOWORD(wParam))
4166             {
4167                 case IDOK:
4168                 case IDCANCEL:
4169                     EndDialog(hDlg, LOWORD(wParam));
4170                     return (INT_PTR)TRUE;
4171 
4172                 case ID_CLEARLOG:
4173                 {
4174                     PEVENTLOGFILTER EventLogFilter = GetSelectedFilter(NULL);
4175                     if (EventLogFilter && ClearEvents(EventLogFilter))
4176                     {
4177                         Refresh(EventLogFilter);
4178                         InitPropertiesDlg(hDlg, EventLog);
4179                     }
4180                     return (INT_PTR)TRUE;
4181                 }
4182 
4183                 case IDC_EDIT_EVENTS_AGE:
4184                 case IDC_EDIT_MAXLOGSIZE:
4185                     if (HIWORD(wParam) == EN_CHANGE)
4186                     {
4187                         PropSheet_Changed(GetParent(hDlg), hDlg);
4188                     }
4189                     return (INT_PTR)TRUE;
4190 
4191                 case IDC_OVERWRITE_AS_NEEDED:
4192                 {
4193                     CheckRadioButton(hDlg, IDC_OVERWRITE_AS_NEEDED, IDC_NO_OVERWRITE, IDC_OVERWRITE_AS_NEEDED);
4194                     EnableDlgItem(hDlg, IDC_EDIT_EVENTS_AGE, FALSE);
4195                     EnableDlgItem(hDlg, IDC_UPDOWN_EVENTS_AGE, FALSE);
4196                     PropSheet_Changed(GetParent(hDlg), hDlg);
4197                     return (INT_PTR)TRUE;
4198                 }
4199 
4200                 case IDC_OVERWRITE_OLDER_THAN:
4201                 {
4202                     CheckRadioButton(hDlg, IDC_OVERWRITE_AS_NEEDED, IDC_NO_OVERWRITE, IDC_OVERWRITE_OLDER_THAN);
4203                     EnableDlgItem(hDlg, IDC_EDIT_EVENTS_AGE, TRUE);
4204                     EnableDlgItem(hDlg, IDC_UPDOWN_EVENTS_AGE, TRUE);
4205                     PropSheet_Changed(GetParent(hDlg), hDlg);
4206                     return (INT_PTR)TRUE;
4207                 }
4208 
4209                 case IDC_NO_OVERWRITE:
4210                 {
4211                     CheckRadioButton(hDlg, IDC_OVERWRITE_AS_NEEDED, IDC_NO_OVERWRITE, IDC_NO_OVERWRITE);
4212                     EnableDlgItem(hDlg, IDC_EDIT_EVENTS_AGE, FALSE);
4213                     EnableDlgItem(hDlg, IDC_UPDOWN_EVENTS_AGE, FALSE);
4214                     PropSheet_Changed(GetParent(hDlg), hDlg);
4215                     return (INT_PTR)TRUE;
4216                 }
4217 
4218                 case IDC_RESTOREDEFAULTS:
4219                 {
4220                     LoadStringW(hInst, IDS_RESTOREDEFAULTS, szText, _countof(szText));
4221 
4222                     if (MessageBoxW(hDlg, szText, szTitle, MB_YESNO | MB_ICONQUESTION) == IDYES)
4223                     {
4224                         CheckRadioButton(hDlg, IDC_OVERWRITE_AS_NEEDED, IDC_NO_OVERWRITE, IDC_OVERWRITE_AS_NEEDED);
4225                         /* Workstation: 512 KB; Server: 16384 KB */
4226                         SetDlgItemInt(hDlg, IDC_EDIT_MAXLOGSIZE, 5120, FALSE);
4227                         SetDlgItemInt(hDlg, IDC_EDIT_EVENTS_AGE, 7, FALSE);
4228                         EnableDlgItem(hDlg, IDC_EDIT_EVENTS_AGE, FALSE);
4229                         EnableDlgItem(hDlg, IDC_UPDOWN_EVENTS_AGE, FALSE);
4230                         PropSheet_Changed(GetParent(hDlg), hDlg);
4231                     }
4232                     return (INT_PTR)TRUE;
4233                 }
4234 
4235                 case IDHELP:
4236                     MessageBoxW(hDlg,
4237                                 L"Help not implemented yet!",
4238                                 szTitle,
4239                                 MB_OK | MB_ICONINFORMATION);
4240                     return (INT_PTR)TRUE;
4241 
4242                 default:
4243                     break;
4244             }
4245             break;
4246     }
4247 
4248     return (INT_PTR)FALSE;
4249 }
4250 
4251 INT_PTR
4252 EventLogProperties(HINSTANCE hInstance, HWND hWndParent, PEVENTLOGFILTER EventLogFilter)
4253 {
4254     INT_PTR ret = 0;
4255     PROPSHEETHEADERW psh;
4256     PROPSHEETPAGEW   psp[1]; // 2
4257 
4258     /*
4259      * Bail out if there is no available filter, or if the filter
4260      * contains more than one log.
4261      */
4262     if (!EventLogFilter)
4263         return 0;
4264 
4265     EventLogFilter_AddRef(EventLogFilter);
4266 
4267     if (EventLogFilter->NumOfEventLogs > 1 ||
4268         EventLogFilter->EventLogs[0] == NULL)
4269     {
4270         goto Quit;
4271     }
4272 
4273     /* Header */
4274     psh.dwSize      = sizeof(psh);
4275     psh.dwFlags     = PSH_PROPSHEETPAGE /*| PSH_USEICONID */ | PSH_PROPTITLE | PSH_HASHELP /*| PSH_NOCONTEXTHELP */ /*| PSH_USECALLBACK */;
4276     psh.hInstance   = hInstance;
4277     psh.hwndParent  = hWndParent;
4278     // psh.pszIcon     = MAKEINTRESOURCEW(IDI_APPICON); // Disabled because it only sets the small icon; the big icon is a stretched version of the small one.
4279     psh.pszCaption  = EventLogFilter->EventLogs[0]->LogName;
4280     psh.nStartPage  = 0;
4281     psh.ppsp        = psp;
4282     psh.nPages      = ARRAYSIZE(psp);
4283     // psh.pfnCallback = PropSheetCallback;
4284 
4285     /* Log properties page */
4286     psp[0].dwSize      = sizeof(psp[0]);
4287     psp[0].dwFlags     = PSP_HASHELP;
4288     psp[0].hInstance   = hInstance;
4289     psp[0].pszTemplate = MAKEINTRESOURCEW(IDD_LOGPROPERTIES_GENERAL);
4290     psp[0].pfnDlgProc  = EventLogPropProc;
4291     psp[0].lParam      = (LPARAM)EventLogFilter->EventLogs[0];
4292 
4293 #if 0
4294     /* TODO: Log sources page */
4295     psp[1].dwSize      = sizeof(psp[1]);
4296     psp[1].dwFlags     = PSP_HASHELP;
4297     psp[1].hInstance   = hInstance;
4298     psp[1].pszTemplate = MAKEINTRESOURCEW(IDD_GENERAL_PAGE);
4299     psp[1].pfnDlgProc  = GeneralPageWndProc;
4300     psp[1].lParam      = (LPARAM)EventLogFilter->EventLogs[0];
4301 #endif
4302 
4303     /* Create the property sheet */
4304     ret = PropertySheetW(&psh);
4305 
4306 Quit:
4307     EventLogFilter_Release(EventLogFilter);
4308     return ret;
4309 }
4310 
4311 /* Message handler for Event Details dialog */
4312 static HWND hWndDetailsCtrl = NULL; // May go into the DWLP_USER
4313 static HWND hWndGrip = NULL;
4314 static INT cxMin, cyMin;    // In window coordinates
4315 static INT cxOld, cyOld;    // In client coordinates
4316 
4317 INT_PTR CALLBACK
4318 EventDetails(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
4319 {
4320     switch (uMsg)
4321     {
4322         case WM_INITDIALOG:
4323         {
4324             LONG_PTR dwStyle;
4325             RECT rcWnd, rect;
4326             INT iEventItem;
4327 
4328             hWndDetailsCtrl = CreateEventDetailsCtrl(hInst, hDlg, lParam);
4329             if (!hWndDetailsCtrl)
4330             {
4331                 EndDialog(hDlg, 0);
4332                 return (INT_PTR)TRUE;
4333             }
4334 
4335             /* Create a size grip if the dialog has a sizing border */
4336             GetClientRect(hDlg, &rcWnd);
4337             dwStyle = GetWindowLongPtrW(hDlg, GWL_STYLE);
4338             if (dwStyle & WS_THICKFRAME /* == WS_SIZEBOX */)
4339             {
4340                 INT sbVXSize = GetSystemMetrics(SM_CXVSCROLL);
4341                 INT sbHYSize = GetSystemMetrics(SM_CYHSCROLL);
4342 
4343                 hWndGrip = CreateWindowW(WC_SCROLLBARW,
4344                                          NULL,
4345                                          WS_CHILD | WS_VISIBLE | /**/ WS_CLIPSIBLINGS | /**/ SBS_SIZEGRIP | SBS_SIZEBOXBOTTOMRIGHTALIGN,
4346                                          rcWnd.right - sbVXSize,
4347                                          rcWnd.bottom - sbHYSize,
4348                                          sbVXSize, sbHYSize,
4349                                          hDlg,
4350                                          NULL,
4351                                          hInst,
4352                                          NULL);
4353             }
4354 
4355             // SetWindowLongPtrW(hDlg, DWLP_USER, (LONG_PTR)hWndDetailsCtrl);
4356 
4357             /*
4358              * Compute the minimum window size (in window coordinates) by
4359              * adding the widths/heights of the "Help" and "Close" buttons,
4360              * together with the margins, and add some minimal spacing
4361              * between the buttons.
4362              */
4363             GetWindowRect(hDlg, &rcWnd);
4364             cxMin = cyMin = 0;
4365 
4366             GetWindowRect(GetDlgItem(hDlg, IDHELP), &rect);
4367             cxMin += (rect.right - rect.left) + (rect.left - rcWnd.left); // == (rect.right - rcWnd.left);
4368             cyMin += (rect.bottom - rect.top) + (rcWnd.bottom - rect.bottom); // == (rcWnd.bottom - rect.top);
4369 
4370             GetWindowRect(GetDlgItem(hDlg, IDOK), &rect);
4371             cxMin += (rect.right - rect.left) + (rcWnd.right - rect.right); // == (rcWnd.right - rect.left);
4372             cyMin += (rect.bottom - rect.top) + (rcWnd.bottom - rect.bottom); // == (rcWnd.bottom - rect.top);
4373 
4374             /*
4375              * Convert the window rect from window to client coordinates
4376              * in order to retrieve the sizes of the left and top margins,
4377              * and add some extra space.
4378              */
4379             MapWindowPoints(HWND_DESKTOP /*NULL*/, hDlg, (LPPOINT)&rcWnd, sizeof(RECT)/sizeof(POINT));
4380 
4381             cxMin += -2*rcWnd.left;   // Minimal spacing between the buttons == 2 * left margin
4382             cyMin += -rcWnd.top + 12; // Add some space on top
4383 
4384             GetClientRect(hDlg, &rcWnd);
4385             cxOld = rcWnd.right - rcWnd.left;
4386             cyOld = rcWnd.bottom - rcWnd.top;
4387 
4388             /* Show event info in dialog control */
4389             iEventItem = (lParam != 0 ? ((PEVENTDETAIL_INFO)lParam)->iEventItem : 0);
4390             SendMessageW(hWndDetailsCtrl, EVT_DISPLAY, 0, (LPARAM)iEventItem);
4391 
4392             // SetWindowPos(hWndDetailsCtrl, NULL,
4393                          // 0, 0,
4394                          // (rcWnd.right - rcWnd.left),
4395                          // (rcWnd.bottom - rcWnd.top),
4396                          // SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW);
4397 
4398             /*
4399              * Hide the placeholder static control and show the event details
4400              * control instead. Note that the placeholder is here so far just
4401              * to get the dimensions right in the dialog resource editor.
4402              * I plan to remove it and use a custom control with a suitable
4403              * window class for it, that would create the event details control
4404              * instead.
4405              */
4406             ShowWindow(GetDlgItem(hDlg, IDC_STATIC), SW_HIDE);
4407             ShowWindow(hWndDetailsCtrl, SW_SHOW);
4408             return (INT_PTR)TRUE;
4409         }
4410 
4411         case WM_DESTROY:
4412             if (IsWindow(hWndDetailsCtrl))
4413                 DestroyWindow(hWndDetailsCtrl);
4414             hWndDetailsCtrl = NULL;
4415             return (INT_PTR)TRUE;
4416 
4417         case WM_COMMAND:
4418             switch (LOWORD(wParam))
4419             {
4420                 case IDOK:
4421                 case IDCANCEL:
4422                     EndDialog(hDlg, LOWORD(wParam));
4423                     return (INT_PTR)TRUE;
4424 
4425                 case IDHELP:
4426                     MessageBoxW(hDlg,
4427                                 L"Help not implemented yet!",
4428                                 szTitle,
4429                                 MB_OK | MB_ICONINFORMATION);
4430                     return (INT_PTR)TRUE;
4431 
4432                 default:
4433                     break;
4434             }
4435             break;
4436 
4437         case WM_SETCURSOR:
4438             if (((HWND)wParam == hWndGrip) && (LOWORD(lParam) == HTCLIENT))
4439             {
4440                 SetCursor(LoadCursorW(NULL, IDC_SIZENWSE));
4441                 SetWindowLongPtrW(hDlg, DWLP_MSGRESULT, TRUE);
4442                 return (INT_PTR)TRUE;
4443             }
4444             break;
4445 
4446         case WM_SIZING:
4447         {
4448             /* Forbid resizing the dialog smaller than its minimal size */
4449             PRECT dragRect = (PRECT)lParam;
4450 
4451             if ((wParam == WMSZ_LEFT) || (wParam == WMSZ_TOPLEFT) || (wParam == WMSZ_BOTTOMLEFT))
4452             {
4453                 if (dragRect->right - dragRect->left < cxMin)
4454                     dragRect->left = dragRect->right - cxMin;
4455             }
4456 
4457             if ((wParam == WMSZ_RIGHT) || (wParam == WMSZ_TOPRIGHT) || (wParam == WMSZ_BOTTOMRIGHT))
4458             {
4459                 if (dragRect->right - dragRect->left < cxMin)
4460                     dragRect->right = dragRect->left + cxMin;
4461             }
4462 
4463             if ((wParam == WMSZ_TOP) || (wParam == WMSZ_TOPLEFT) || (wParam == WMSZ_TOPRIGHT))
4464             {
4465                 if (dragRect->bottom - dragRect->top < cyMin)
4466                     dragRect->top = dragRect->bottom - cyMin;
4467             }
4468 
4469             if ((wParam == WMSZ_BOTTOM) || (wParam == WMSZ_BOTTOMLEFT) || (wParam == WMSZ_BOTTOMRIGHT))
4470             {
4471                 if (dragRect->bottom - dragRect->top < cyMin)
4472                     dragRect->bottom = dragRect->top + cyMin;
4473             }
4474 
4475             SetWindowLongPtrW(hDlg, DWLP_MSGRESULT, TRUE);
4476             return (INT_PTR)TRUE;
4477         }
4478 
4479         case WM_SIZE:
4480         {
4481             INT cx = LOWORD(lParam);
4482             INT cy = HIWORD(lParam);
4483 
4484             HDWP hdwp;
4485             HWND hItemWnd;
4486             RECT rect;
4487 
4488             hdwp = BeginDeferWindowPos(4);
4489 
4490             /* Resize the event details control window */
4491 
4492             hItemWnd = hWndDetailsCtrl;
4493             GetWindowRect(hItemWnd, &rect);
4494             MapWindowPoints(HWND_DESKTOP /*NULL*/, hDlg, (LPPOINT)&rect, sizeof(RECT)/sizeof(POINT));
4495 
4496             if (hdwp)
4497                 hdwp = DeferWindowPos(hdwp,
4498                                       hItemWnd,
4499                                       HWND_TOP,
4500                                       0, 0,
4501                                       (rect.right - rect.left) + (cx - cxOld),
4502                                       (rect.bottom - rect.top) + (cy - cyOld),
4503                                       SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
4504 
4505             /* Move the buttons */
4506 
4507             hItemWnd = GetDlgItem(hDlg, IDHELP);
4508             GetWindowRect(hItemWnd, &rect);
4509             MapWindowPoints(HWND_DESKTOP /*NULL*/, hDlg, (LPPOINT)&rect, sizeof(RECT)/sizeof(POINT));
4510 
4511             if (hdwp)
4512                 hdwp = DeferWindowPos(hdwp,
4513                                       hItemWnd,
4514                                       HWND_TOP,
4515                                       rect.left,
4516                                       rect.top + (cy - cyOld),
4517                                       0, 0,
4518                                       SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
4519 
4520             hItemWnd = GetDlgItem(hDlg, IDOK);
4521             GetWindowRect(hItemWnd, &rect);
4522             MapWindowPoints(HWND_DESKTOP /*NULL*/, hDlg, (LPPOINT)&rect, sizeof(RECT)/sizeof(POINT));
4523 
4524             if (hdwp)
4525                 hdwp = DeferWindowPos(hdwp,
4526                                       hItemWnd,
4527                                       HWND_TOP,
4528                                       rect.left + (cx - cxOld),
4529                                       rect.top  + (cy - cyOld),
4530                                       0, 0,
4531                                       SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
4532 
4533             /* Move the size grip */
4534             if (hWndGrip && hdwp)
4535             {
4536                 GetWindowRect(hWndGrip, &rect);
4537                 MapWindowPoints(HWND_DESKTOP /*NULL*/, hDlg, (LPPOINT)&rect, sizeof(RECT)/sizeof(POINT));
4538 
4539                 hdwp = DeferWindowPos(hdwp,
4540                                       hWndGrip,
4541                                       HWND_TOP,
4542                                       rect.left + (cx - cxOld),
4543                                       rect.top  + (cy - cyOld),
4544                                       0, 0,
4545                                       SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
4546             }
4547 
4548             if (hdwp)
4549                 EndDeferWindowPos(hdwp);
4550 
4551             /* Hide the size grip if we are in maximized mode */
4552             if (hWndGrip)
4553                 ShowWindow(hWndGrip, (wParam == SIZE_MAXIMIZED) ? SW_HIDE : SW_SHOW);
4554 
4555             cxOld = cx;
4556             cyOld = cy;
4557 
4558             SetWindowLongPtrW(hDlg, DWLP_MSGRESULT, 0);
4559             return (INT_PTR)TRUE;
4560         }
4561     }
4562 
4563     return (INT_PTR)FALSE;
4564 }
4565