1 /*
2 ** i_main.cpp
3 ** System-specific startup code. Eventually calls D_DoomMain.
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 1998-2009 Randy Heit
7 ** All rights reserved.
8 **
9 ** Redistribution and use in source and binary forms, with or without
10 ** modification, are permitted provided that the following conditions
11 ** are met:
12 **
13 ** 1. Redistributions of source code must retain the above copyright
14 ** notice, this list of conditions and the following disclaimer.
15 ** 2. Redistributions in binary form must reproduce the above copyright
16 ** notice, this list of conditions and the following disclaimer in the
17 ** documentation and/or other materials provided with the distribution.
18 ** 3. The name of the author may not be used to endorse or promote products
19 ** derived from this software without specific prior written permission.
20 **
21 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 **---------------------------------------------------------------------------
32 **
33 */
34
35 // HEADER FILES ------------------------------------------------------------
36
37 #define WIN32_LEAN_AND_MEAN
38 #define _WIN32_WINNT 0x0501
39 #include <windows.h>
40 #include <mmsystem.h>
41 #include <objbase.h>
42 #include <commctrl.h>
43 #include <richedit.h>
44
45 #ifdef _MSC_VER
46 #pragma warning(disable:4244)
47 #endif
48
49 //#include <wtsapi32.h>
50 #define NOTIFY_FOR_THIS_SESSION 0
51
52 #include <stdlib.h>
53 #ifdef _MSC_VER
54 #include <eh.h>
55 #include <new.h>
56 #include <crtdbg.h>
57 #endif
58 #include "resource.h"
59
60 #include <stdio.h>
61 #include <stdarg.h>
62 #include <math.h>
63
64 #define USE_WINDOWS_DWORD
65 #include "doomerrors.h"
66 #include "hardware.h"
67
68 #include "doomtype.h"
69 #include "m_argv.h"
70 #include "d_main.h"
71 #include "i_system.h"
72 #include "c_console.h"
73 #include "version.h"
74 #include "i_video.h"
75 #include "i_sound.h"
76 #include "i_input.h"
77 #include "w_wad.h"
78 #include "templates.h"
79 #include "cmdlib.h"
80 #include "g_level.h"
81 #include "doomstat.h"
82 #include "r_utility.h"
83
84 #include "stats.h"
85 #include "st_start.h"
86
87 #include <assert.h>
88
89 // MACROS ------------------------------------------------------------------
90
91 // The main window's title.
92 #ifdef _M_X64
93 #define X64 " 64-bit"
94 #else
95 #define X64 ""
96 #endif
97
98 // The maximum number of functions that can be registered with atterm.
99 #define MAX_TERMS 64
100
101 // TYPES -------------------------------------------------------------------
102
103 // EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
104
105 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
106 void CreateCrashLog (char *custominfo, DWORD customsize, HWND richedit);
107 void DisplayCrashLog ();
108 extern BYTE *ST_Util_BitsForBitmap (BITMAPINFO *bitmap_info);
109 void I_FlushBufferedConsoleStuff();
110
111 // PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
112
113 // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
114
115 // EXTERNAL DATA DECLARATIONS ----------------------------------------------
116
117 extern EXCEPTION_POINTERS CrashPointers;
118 extern BITMAPINFO *StartupBitmap;
119 extern UINT TimerPeriod;
120
121 // PUBLIC DATA DEFINITIONS -------------------------------------------------
122
123 // The command line arguments.
124 DArgs *Args;
125
126 HINSTANCE g_hInst;
127 DWORD SessionID;
128 HANDLE MainThread;
129 DWORD MainThreadID;
130 HANDLE StdOut;
131 bool FancyStdOut, AttachedStdOut;
132 bool ConWindowHidden;
133
134 // The main window
135 HWND Window;
136
137 // The subwindows used for startup and error output
138 HWND ConWindow, GameTitleWindow;
139 HWND ErrorPane, ProgressBar, NetStartPane, StartupScreen, ErrorIcon;
140
141 HFONT GameTitleFont;
142 LONG GameTitleFontHeight;
143 LONG DefaultGUIFontHeight;
144 LONG ErrorIconChar;
145
146 // PRIVATE DATA DEFINITIONS ------------------------------------------------
147
148 static const char WinClassName[] = GAMENAME "MainWindow";
149 static HMODULE hwtsapi32; // handle to wtsapi32.dll
150 static void (*TermFuncs[MAX_TERMS])(void);
151 static int NumTerms;
152
153 // CODE --------------------------------------------------------------------
154
155 //==========================================================================
156 //
157 // atterm
158 //
159 // Our own atexit because atexit can be problematic under Linux, though I
160 // forget the circumstances that cause trouble.
161 //
162 //==========================================================================
163
atterm(void (* func)(void))164 void atterm (void (*func)(void))
165 {
166 // Make sure this function wasn't already registered.
167 for (int i = 0; i < NumTerms; ++i)
168 {
169 if (TermFuncs[i] == func)
170 {
171 return;
172 }
173 }
174 if (NumTerms == MAX_TERMS)
175 {
176 func ();
177 I_FatalError ("Too many exit functions registered.\nIncrease MAX_TERMS in i_main.cpp");
178 }
179 TermFuncs[NumTerms++] = func;
180 }
181
182 //==========================================================================
183 //
184 // popterm
185 //
186 // Removes the most recently register atterm function.
187 //
188 //==========================================================================
189
popterm()190 void popterm ()
191 {
192 if (NumTerms)
193 NumTerms--;
194 }
195
196 //==========================================================================
197 //
198 // call_terms
199 //
200 //==========================================================================
201
call_terms(void)202 static void STACK_ARGS call_terms (void)
203 {
204 while (NumTerms > 0)
205 {
206 TermFuncs[--NumTerms]();
207 }
208 }
209
210 #ifdef _MSC_VER
NewFailure(size_t size)211 static int STACK_ARGS NewFailure (size_t size)
212 {
213 I_FatalError ("Failed to allocate %d bytes from process heap", size);
214 return 0;
215 }
216 #endif
217
218 //==========================================================================
219 //
220 // UnCOM
221 //
222 // Called by atterm if CoInitialize() succeeded.
223 //
224 //==========================================================================
225
UnCOM(void)226 static void UnCOM (void)
227 {
228 CoUninitialize ();
229 }
230
231 //==========================================================================
232 //
233 // UnWTS
234 //
235 // Called by atterm if RegisterSessionNotification() succeeded.
236 //
237 //==========================================================================
238
UnWTS(void)239 static void UnWTS (void)
240 {
241 if (hwtsapi32 != 0)
242 {
243 typedef BOOL (WINAPI *ursn)(HWND);
244 ursn unreg = (ursn)GetProcAddress (hwtsapi32, "WTSUnRegisterSessionNotification");
245 if (unreg != 0)
246 {
247 unreg (Window);
248 }
249 FreeLibrary (hwtsapi32);
250 hwtsapi32 = 0;
251 }
252 }
253
254 //==========================================================================
255 //
256 // LayoutErrorPane
257 //
258 // Lays out the error pane to the desired width, returning the required
259 // height.
260 //
261 //==========================================================================
262
LayoutErrorPane(HWND pane,int w)263 static int LayoutErrorPane (HWND pane, int w)
264 {
265 HWND ctl;
266 RECT rectc;
267
268 // Right-align the Quit button.
269 ctl = GetDlgItem (pane, IDOK);
270 GetClientRect (ctl, &rectc); // Find out how big it is.
271 MoveWindow (ctl, w - rectc.right - 1, 1, rectc.right, rectc.bottom, TRUE);
272 InvalidateRect (ctl, NULL, TRUE);
273
274 // Return the needed height for this layout
275 return rectc.bottom + 2;
276 }
277
278 //==========================================================================
279 //
280 // LayoutNetStartPane
281 //
282 // Lays out the network startup pane to the specified width, returning
283 // its required height.
284 //
285 //==========================================================================
286
LayoutNetStartPane(HWND pane,int w)287 int LayoutNetStartPane (HWND pane, int w)
288 {
289 HWND ctl;
290 RECT margin, rectc;
291 int staticheight, barheight;
292
293 // Determine margin sizes.
294 SetRect (&margin, 7, 7, 0, 0);
295 MapDialogRect (pane, &margin);
296
297 // Stick the message text in the upper left corner.
298 ctl = GetDlgItem (pane, IDC_NETSTARTMESSAGE);
299 GetClientRect (ctl, &rectc);
300 MoveWindow (ctl, margin.left, margin.top, rectc.right, rectc.bottom, TRUE);
301
302 // Stick the count text in the upper right corner.
303 ctl = GetDlgItem (pane, IDC_NETSTARTCOUNT);
304 GetClientRect (ctl, &rectc);
305 MoveWindow (ctl, w - rectc.right - margin.left, margin.top, rectc.right, rectc.bottom, TRUE);
306 staticheight = rectc.bottom;
307
308 // Stretch the progress bar to fill the entire width.
309 ctl = GetDlgItem (pane, IDC_NETSTARTPROGRESS);
310 barheight = GetSystemMetrics (SM_CYVSCROLL);
311 MoveWindow (ctl, margin.left, margin.top*2 + staticheight, w - margin.left*2, barheight, TRUE);
312
313 // Center the abort button underneath the progress bar.
314 ctl = GetDlgItem (pane, IDCANCEL);
315 GetClientRect (ctl, &rectc);
316 MoveWindow (ctl, (w - rectc.right) / 2, margin.top*3 + staticheight + barheight, rectc.right, rectc.bottom, TRUE);
317
318 return margin.top*4 + staticheight + barheight + rectc.bottom;
319 }
320
321 //==========================================================================
322 //
323 // LayoutMainWindow
324 //
325 // Lays out the main window with the game title and log controls and
326 // possibly the error pane and progress bar.
327 //
328 //==========================================================================
329
LayoutMainWindow(HWND hWnd,HWND pane)330 void LayoutMainWindow (HWND hWnd, HWND pane)
331 {
332 RECT rect;
333 int errorpaneheight = 0;
334 int bannerheight = 0;
335 int progressheight = 0;
336 int netpaneheight = 0;
337 int leftside = 0;
338 int w, h;
339
340 GetClientRect (hWnd, &rect);
341 w = rect.right;
342 h = rect.bottom;
343
344 if (DoomStartupInfo.Name.IsNotEmpty() && GameTitleWindow != NULL)
345 {
346 bannerheight = GameTitleFontHeight + 5;
347 MoveWindow (GameTitleWindow, 0, 0, w, bannerheight, TRUE);
348 InvalidateRect (GameTitleWindow, NULL, FALSE);
349 }
350 if (ProgressBar != NULL)
351 {
352 // Base the height of the progress bar on the height of a scroll bar
353 // arrow, just as in the progress bar example.
354 progressheight = GetSystemMetrics (SM_CYVSCROLL);
355 MoveWindow (ProgressBar, 0, h - progressheight, w, progressheight, TRUE);
356 }
357 if (NetStartPane != NULL)
358 {
359 netpaneheight = LayoutNetStartPane (NetStartPane, w);
360 SetWindowPos (NetStartPane, HWND_TOP, 0, h - progressheight - netpaneheight, w, netpaneheight, SWP_SHOWWINDOW);
361 }
362 if (pane != NULL)
363 {
364 errorpaneheight = LayoutErrorPane (pane, w);
365 SetWindowPos (pane, HWND_TOP, 0, h - progressheight - netpaneheight - errorpaneheight, w, errorpaneheight, 0);
366 }
367 if (ErrorIcon != NULL)
368 {
369 leftside = GetSystemMetrics (SM_CXICON) + 6;
370 MoveWindow (ErrorIcon, 0, bannerheight, leftside, h - bannerheight - errorpaneheight - progressheight - netpaneheight, TRUE);
371 }
372 // If there is a startup screen, it covers the log window
373 if (StartupScreen != NULL)
374 {
375 SetWindowPos (StartupScreen, HWND_TOP, leftside, bannerheight, w - leftside,
376 h - bannerheight - errorpaneheight - progressheight - netpaneheight, SWP_SHOWWINDOW);
377 InvalidateRect (StartupScreen, NULL, FALSE);
378 MoveWindow (ConWindow, 0, 0, 0, 0, TRUE);
379 }
380 else
381 {
382 // The log window uses whatever space is left.
383 MoveWindow (ConWindow, leftside, bannerheight, w - leftside,
384 h - bannerheight - errorpaneheight - progressheight - netpaneheight, TRUE);
385 }
386 }
387
388
389 //==========================================================================
390 //
391 // I_SetIWADInfo
392 //
393 //==========================================================================
394
I_SetIWADInfo()395 void I_SetIWADInfo()
396 {
397 // Make the startup banner show itself
398 LayoutMainWindow(Window, NULL);
399 }
400
401 //==========================================================================
402 //
403 // LConProc
404 //
405 // The main window's WndProc during startup. During gameplay, the WndProc
406 // in i_input.cpp is used instead.
407 //
408 //==========================================================================
409
LConProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)410 LRESULT CALLBACK LConProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
411 {
412 HWND view;
413 HDC hdc;
414 HBRUSH hbr;
415 HGDIOBJ oldfont;
416 RECT rect;
417 int titlelen;
418 SIZE size;
419 LOGFONT lf;
420 TEXTMETRIC tm;
421 HINSTANCE inst = (HINSTANCE)(LONG_PTR)GetWindowLongPtr(hWnd, GWLP_HINSTANCE);
422 DRAWITEMSTRUCT *drawitem;
423 CHARFORMAT2W format;
424
425 switch (msg)
426 {
427 case WM_CREATE:
428 // Create game title static control
429 memset (&lf, 0, sizeof(lf));
430 hdc = GetDC (hWnd);
431 lf.lfHeight = -MulDiv(12, GetDeviceCaps(hdc, LOGPIXELSY), 72);
432 lf.lfCharSet = ANSI_CHARSET;
433 lf.lfWeight = FW_BOLD;
434 lf.lfPitchAndFamily = VARIABLE_PITCH | FF_ROMAN;
435 strcpy (lf.lfFaceName, "Trebuchet MS");
436 GameTitleFont = CreateFontIndirect (&lf);
437
438 oldfont = SelectObject (hdc, GetStockObject (DEFAULT_GUI_FONT));
439 GetTextMetrics (hdc, &tm);
440 DefaultGUIFontHeight = tm.tmHeight;
441 if (GameTitleFont == NULL)
442 {
443 GameTitleFontHeight = DefaultGUIFontHeight;
444 }
445 else
446 {
447 SelectObject (hdc, GameTitleFont);
448 GetTextMetrics (hdc, &tm);
449 GameTitleFontHeight = tm.tmHeight;
450 }
451 SelectObject (hdc, oldfont);
452
453 // Create log read-only edit control
454 view = CreateWindowEx (WS_EX_NOPARENTNOTIFY, "RichEdit20W", NULL,
455 WS_CHILD | WS_VISIBLE | WS_VSCROLL |
456 ES_LEFT | ES_MULTILINE | WS_CLIPSIBLINGS,
457 0, 0, 0, 0,
458 hWnd, NULL, inst, NULL);
459 HRESULT hr;
460 hr = GetLastError();
461 if (view == NULL)
462 {
463 ReleaseDC (hWnd, hdc);
464 return -1;
465 }
466 SendMessage (view, EM_SETREADONLY, TRUE, 0);
467 SendMessage (view, EM_EXLIMITTEXT, 0, 0x7FFFFFFE);
468 SendMessage (view, EM_SETBKGNDCOLOR, 0, RGB(70,70,70));
469 // Setup default font for the log.
470 //SendMessage (view, WM_SETFONT, (WPARAM)GetStockObject (DEFAULT_GUI_FONT), FALSE);
471 format.cbSize = sizeof(format);
472 format.dwMask = CFM_BOLD | CFM_COLOR | CFM_FACE | CFM_SIZE | CFM_CHARSET;
473 format.dwEffects = 0;
474 format.yHeight = 200;
475 format.crTextColor = RGB(223,223,223);
476 format.bCharSet = ANSI_CHARSET;
477 format.bPitchAndFamily = FF_SWISS | VARIABLE_PITCH;
478 wcscpy(format.szFaceName, L"DejaVu Sans"); // At least I have it. :p
479 SendMessageW(view, EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&format);
480
481 ConWindow = view;
482 ReleaseDC (hWnd, hdc);
483
484 view = CreateWindowEx (WS_EX_NOPARENTNOTIFY, "STATIC", NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | SS_OWNERDRAW, 0, 0, 0, 0, hWnd, NULL, inst, NULL);
485 if (view == NULL)
486 {
487 return -1;
488 }
489 SetWindowLong (view, GWL_ID, IDC_STATIC_TITLE);
490 GameTitleWindow = view;
491
492 return 0;
493
494 case WM_SIZE:
495 if (wParam != SIZE_MAXHIDE && wParam != SIZE_MAXSHOW)
496 {
497 LayoutMainWindow (hWnd, ErrorPane);
498 }
499 return 0;
500
501 case WM_DRAWITEM:
502 // Draw title banner.
503 if (wParam == IDC_STATIC_TITLE && DoomStartupInfo.Name.IsNotEmpty())
504 {
505 const PalEntry *c;
506
507 // Draw the game title strip at the top of the window.
508 drawitem = (LPDRAWITEMSTRUCT)lParam;
509
510 // Draw the background.
511 rect = drawitem->rcItem;
512 rect.bottom -= 1;
513 c = (const PalEntry *)&DoomStartupInfo.BkColor;
514 hbr = CreateSolidBrush (RGB(c->r,c->g,c->b));
515 FillRect (drawitem->hDC, &drawitem->rcItem, hbr);
516 DeleteObject (hbr);
517
518 // Calculate width of the title string.
519 SetTextAlign (drawitem->hDC, TA_TOP);
520 oldfont = SelectObject (drawitem->hDC, GameTitleFont != NULL ? GameTitleFont : (HFONT)GetStockObject (DEFAULT_GUI_FONT));
521 titlelen = (int)DoomStartupInfo.Name.Len();
522 GetTextExtentPoint32 (drawitem->hDC, DoomStartupInfo.Name, titlelen, &size);
523
524 // Draw the title.
525 c = (const PalEntry *)&DoomStartupInfo.FgColor;
526 SetTextColor (drawitem->hDC, RGB(c->r,c->g,c->b));
527 SetBkMode (drawitem->hDC, TRANSPARENT);
528 TextOut (drawitem->hDC, rect.left + (rect.right - rect.left - size.cx) / 2, 2, DoomStartupInfo.Name, titlelen);
529 SelectObject (drawitem->hDC, oldfont);
530 return TRUE;
531 }
532 // Draw startup screen
533 else if (wParam == IDC_STATIC_STARTUP)
534 {
535 if (StartupScreen != NULL)
536 {
537 drawitem = (LPDRAWITEMSTRUCT)lParam;
538
539 rect = drawitem->rcItem;
540 // Windows expects DIBs to be bottom-up but ours is top-down,
541 // so flip it vertically while drawing it.
542 StretchDIBits (drawitem->hDC, rect.left, rect.bottom - 1, rect.right - rect.left, rect.top - rect.bottom,
543 0, 0, StartupBitmap->bmiHeader.biWidth, StartupBitmap->bmiHeader.biHeight,
544 ST_Util_BitsForBitmap(StartupBitmap), StartupBitmap, DIB_RGB_COLORS, SRCCOPY);
545
546 // If the title banner is gone, then this is an ENDOOM screen, so draw a short prompt
547 // where the command prompt would have been in DOS.
548 if (GameTitleWindow == NULL)
549 {
550 static const char QuitText[] = "Press any key or click anywhere in the window to quit.";
551
552 SetTextColor (drawitem->hDC, RGB(240,240,240));
553 SetBkMode (drawitem->hDC, TRANSPARENT);
554 oldfont = SelectObject (drawitem->hDC, (HFONT)GetStockObject (DEFAULT_GUI_FONT));
555 TextOut (drawitem->hDC, 3, drawitem->rcItem.bottom - DefaultGUIFontHeight - 3, QuitText, countof(QuitText)-1);
556 SelectObject (drawitem->hDC, oldfont);
557 }
558 return TRUE;
559 }
560 }
561 // Draw stop icon.
562 else if (wParam == IDC_ICONPIC)
563 {
564 HICON icon;
565 POINTL char_pos;
566 drawitem = (LPDRAWITEMSTRUCT)lParam;
567
568 // This background color should match the edit control's.
569 hbr = CreateSolidBrush (RGB(70,70,70));
570 FillRect (drawitem->hDC, &drawitem->rcItem, hbr);
571 DeleteObject (hbr);
572
573 // Draw the icon aligned with the first line of error text.
574 SendMessage (ConWindow, EM_POSFROMCHAR, (WPARAM)&char_pos, ErrorIconChar);
575 icon = (HICON)LoadImage (0, IDI_ERROR, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
576 DrawIcon (drawitem->hDC, 6, char_pos.y, icon);
577 return TRUE;
578 }
579 return FALSE;
580
581 case WM_COMMAND:
582 if (ErrorIcon != NULL && (HWND)lParam == ConWindow && HIWORD(wParam) == EN_UPDATE)
583 {
584 // Be sure to redraw the error icon if the edit control changes.
585 InvalidateRect (ErrorIcon, NULL, TRUE);
586 return 0;
587 }
588 break;
589
590 case WM_CLOSE:
591 PostQuitMessage (0);
592 break;
593
594 case WM_DESTROY:
595 if (GameTitleFont != NULL)
596 {
597 DeleteObject (GameTitleFont);
598 }
599 break;
600 }
601 return DefWindowProc (hWnd, msg, wParam, lParam);
602 }
603
604 //==========================================================================
605 //
606 // ErrorPaneProc
607 //
608 // DialogProc for the error pane.
609 //
610 //==========================================================================
611
ErrorPaneProc(HWND hDlg,UINT msg,WPARAM wParam,LPARAM lParam)612 INT_PTR CALLBACK ErrorPaneProc (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
613 {
614 switch (msg)
615 {
616 case WM_INITDIALOG:
617 // Appear in the main window.
618 LayoutMainWindow (GetParent (hDlg), hDlg);
619 return TRUE;
620
621 case WM_COMMAND:
622 // There is only one button, and it's "Ok" and makes us quit.
623 if (HIWORD(wParam) == BN_CLICKED)
624 {
625 PostQuitMessage (0);
626 return TRUE;
627 }
628 break;
629 }
630 return FALSE;
631 }
632
633 //==========================================================================
634 //
635 // I_SetWndProc
636 //
637 // Sets the main WndProc, hides all the child windows, and starts up
638 // in-game input.
639 //
640 //==========================================================================
641
I_SetWndProc()642 void I_SetWndProc()
643 {
644 if (GetWindowLongPtr (Window, GWLP_USERDATA) == 0)
645 {
646 SetWindowLongPtr (Window, GWLP_USERDATA, 1);
647 SetWindowLongPtr (Window, GWLP_WNDPROC, (WLONG_PTR)WndProc);
648 ShowWindow (ConWindow, SW_HIDE);
649 ConWindowHidden = true;
650 ShowWindow (GameTitleWindow, SW_HIDE);
651 I_InitInput (Window);
652 }
653 }
654
655 //==========================================================================
656 //
657 // RestoreConView
658 //
659 // Returns the main window to its startup state.
660 //
661 //==========================================================================
662
RestoreConView()663 void RestoreConView()
664 {
665 // Make sure the window has a frame in case it was fullscreened.
666 SetWindowLongPtr (Window, GWL_STYLE, WS_VISIBLE|WS_OVERLAPPEDWINDOW);
667 if (GetWindowLong (Window, GWL_EXSTYLE) & WS_EX_TOPMOST)
668 {
669 SetWindowPos (Window, HWND_BOTTOM, 0, 0, 512, 384,
670 SWP_DRAWFRAME | SWP_NOCOPYBITS | SWP_NOMOVE);
671 SetWindowPos (Window, HWND_TOP, 0, 0, 0, 0, SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOSIZE);
672 }
673 else
674 {
675 SetWindowPos (Window, NULL, 0, 0, 512, 384,
676 SWP_DRAWFRAME | SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOZORDER);
677 }
678
679 SetWindowLongPtr (Window, GWLP_WNDPROC, (WLONG_PTR)LConProc);
680 ShowWindow (ConWindow, SW_SHOW);
681 ConWindowHidden = false;
682 ShowWindow (GameTitleWindow, SW_SHOW);
683 I_ShutdownInput (); // Make sure the mouse pointer is available.
684 I_FlushBufferedConsoleStuff();
685 // Make sure the progress bar isn't visible.
686 if (StartScreen != NULL)
687 {
688 delete StartScreen;
689 StartScreen = NULL;
690 }
691 }
692
693 //==========================================================================
694 //
695 // ShowErrorPane
696 //
697 // Shows an error message, preferably in the main window, but it can
698 // use a normal message box too.
699 //
700 //==========================================================================
701
ShowErrorPane(const char * text)702 void ShowErrorPane(const char *text)
703 {
704 if (Window == NULL || ConWindow == NULL)
705 {
706 if (text != NULL)
707 {
708 MessageBox (Window, text,
709 GAMESIG " Fatal Error", MB_OK|MB_ICONSTOP|MB_TASKMODAL);
710 }
711 return;
712 }
713
714 if (StartScreen != NULL) // Ensure that the network pane is hidden.
715 {
716 StartScreen->NetDone();
717 }
718 if (text != NULL)
719 {
720 char caption[100];
721 mysnprintf(caption, countof(caption), "Fatal Error - " GAMESIG " %s " X64 " (%s)", GetVersionString(), GetGitTime());
722 SetWindowText (Window, caption);
723 ErrorIcon = CreateWindowEx (WS_EX_NOPARENTNOTIFY, "STATIC", NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | SS_OWNERDRAW, 0, 0, 0, 0, Window, NULL, g_hInst, NULL);
724 if (ErrorIcon != NULL)
725 {
726 SetWindowLong (ErrorIcon, GWL_ID, IDC_ICONPIC);
727 }
728 }
729 ErrorPane = CreateDialogParam (g_hInst, MAKEINTRESOURCE(IDD_ERRORPANE), Window, ErrorPaneProc, (LONG_PTR)NULL);
730
731 if (text != NULL)
732 {
733 CHARRANGE end;
734 CHARFORMAT2 oldformat, newformat;
735 PARAFORMAT2 paraformat;
736
737 // Append the error message to the log.
738 end.cpMax = end.cpMin = GetWindowTextLength (ConWindow);
739 SendMessage (ConWindow, EM_EXSETSEL, 0, (LPARAM)&end);
740
741 // Remember current charformat.
742 oldformat.cbSize = sizeof(oldformat);
743 SendMessage (ConWindow, EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM)&oldformat);
744
745 // Use bigger font and standout colors.
746 newformat.cbSize = sizeof(newformat);
747 newformat.dwMask = CFM_BOLD | CFM_COLOR | CFM_SIZE;
748 newformat.dwEffects = CFE_BOLD;
749 newformat.yHeight = oldformat.yHeight * 5 / 4;
750 newformat.crTextColor = RGB(255,170,170);
751 SendMessage (ConWindow, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&newformat);
752
753 // Indent the rest of the text to make the error message stand out a little more.
754 paraformat.cbSize = sizeof(paraformat);
755 paraformat.dwMask = PFM_STARTINDENT | PFM_OFFSETINDENT | PFM_RIGHTINDENT;
756 paraformat.dxStartIndent = paraformat.dxOffset = paraformat.dxRightIndent = 120;
757 SendMessage (ConWindow, EM_SETPARAFORMAT, 0, (LPARAM)¶format);
758 SendMessage (ConWindow, EM_REPLACESEL, FALSE, (LPARAM)"\n");
759
760 // Find out where the error lines start for the error icon display control.
761 SendMessage (ConWindow, EM_EXGETSEL, 0, (LPARAM)&end);
762 ErrorIconChar = end.cpMax;
763
764 // Now start adding the actual error message.
765 SendMessage (ConWindow, EM_REPLACESEL, FALSE, (LPARAM)"Execution could not continue.\n\n");
766
767 // Restore old charformat but with light yellow text.
768 oldformat.crTextColor = RGB(255,255,170);
769 SendMessage (ConWindow, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&oldformat);
770
771 // Add the error text.
772 SendMessage (ConWindow, EM_REPLACESEL, FALSE, (LPARAM)text);
773
774 // Make sure the error text is not scrolled below the window.
775 SendMessage (ConWindow, EM_LINESCROLL, 0, SendMessage (ConWindow, EM_GETLINECOUNT, 0, 0));
776 // The above line scrolled everything off the screen, so pretend to move the scroll
777 // bar thumb, which clamps to not show any extra lines if it doesn't need to.
778 SendMessage (ConWindow, EM_SCROLL, SB_PAGEDOWN, 0);
779 }
780
781 BOOL bRet;
782 MSG msg;
783
784 while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
785 {
786 if (bRet == -1)
787 {
788 MessageBox (Window, text,
789 GAMESIG " Fatal Error", MB_OK|MB_ICONSTOP|MB_TASKMODAL);
790 return;
791 }
792 else if (!IsDialogMessage (ErrorPane, &msg))
793 {
794 TranslateMessage (&msg);
795 DispatchMessage (&msg);
796 }
797 }
798 }
799
800 //==========================================================================
801 //
802 // DoMain
803 //
804 //==========================================================================
805
DoMain(HINSTANCE hInstance)806 void DoMain (HINSTANCE hInstance)
807 {
808 LONG WinWidth, WinHeight;
809 int height, width, x, y;
810 RECT cRect;
811 TIMECAPS tc;
812 DEVMODE displaysettings;
813
814 try
815 {
816 #ifdef _MSC_VER
817 _set_new_handler (NewFailure);
818 #endif
819
820 Args = new DArgs(__argc, __argv);
821
822 // Under XP, get our session ID so we can know when the user changes/locks sessions.
823 // Since we need to remain binary compatible with older versions of Windows, we
824 // need to extract the ProcessIdToSessionId function from kernel32.dll manually.
825 HMODULE kernel = GetModuleHandle ("kernel32.dll");
826
827 if (Args->CheckParm("-stdout"))
828 {
829 // As a GUI application, we don't normally get a console when we start.
830 // If we were run from the shell and are on XP+, we can attach to its
831 // console. Otherwise, we can create a new one. If we already have a
832 // stdout handle, then we have been redirected and should just use that
833 // handle instead of creating a console window.
834
835 StdOut = GetStdHandle(STD_OUTPUT_HANDLE);
836 if (StdOut != NULL)
837 {
838 // It seems that running from a shell always creates a std output
839 // for us, even if it doesn't go anywhere. (Running from Explorer
840 // does not.) If we can get file information for this handle, it's
841 // a file or pipe, so use it. Otherwise, pretend it wasn't there
842 // and find a console to use instead.
843 BY_HANDLE_FILE_INFORMATION info;
844 if (!GetFileInformationByHandle(StdOut, &info))
845 {
846 StdOut = NULL;
847 }
848 }
849 if (StdOut == NULL)
850 {
851 // AttachConsole was introduced with Windows XP. (OTOH, since we
852 // have to share the console with the shell, I'm not sure if it's
853 // a good idea to actually attach to it.)
854 typedef BOOL (WINAPI *ac)(DWORD);
855 ac attach_console = kernel != NULL ? (ac)GetProcAddress(kernel, "AttachConsole") : NULL;
856 if (attach_console != NULL && attach_console(ATTACH_PARENT_PROCESS))
857 {
858 StdOut = GetStdHandle(STD_OUTPUT_HANDLE);
859 DWORD foo; WriteFile(StdOut, "\n", 1, &foo, NULL);
860 AttachedStdOut = true;
861 }
862 if (StdOut == NULL && AllocConsole())
863 {
864 StdOut = GetStdHandle(STD_OUTPUT_HANDLE);
865 }
866 FancyStdOut = true;
867 }
868 }
869
870 // Set the timer to be as accurate as possible
871 if (timeGetDevCaps (&tc, sizeof(tc)) != TIMERR_NOERROR)
872 TimerPeriod = 1; // Assume minimum resolution of 1 ms
873 else
874 TimerPeriod = tc.wPeriodMin;
875
876 timeBeginPeriod (TimerPeriod);
877
878 /*
879 killough 1/98:
880
881 This fixes some problems with exit handling
882 during abnormal situations.
883
884 The old code called I_Quit() to end program,
885 while now I_Quit() is installed as an exit
886 handler and exit() is called to exit, either
887 normally or abnormally.
888 */
889
890 atexit (call_terms);
891
892 atterm (I_Quit);
893
894 // Figure out what directory the program resides in.
895 char *program;
896
897 #ifdef _MSC_VER
898 if (_get_pgmptr(&program) != 0)
899 {
900 I_FatalError("Could not determine program location.");
901 }
902 #else
903 char progbuff[1024];
904 GetModuleFileName(0, progbuff, sizeof(progbuff));
905 progbuff[1023] = '\0';
906 program = progbuff;
907 #endif
908
909 progdir = program;
910 program = progdir.LockBuffer();
911 *(strrchr(program, '\\') + 1) = '\0';
912 FixPathSeperator(program);
913 progdir.Truncate((long)strlen(program));
914 progdir.UnlockBuffer();
915
916 width = 512;
917 height = 384;
918
919 // Many Windows structures that specify their size do so with the first
920 // element. DEVMODE is not one of those structures.
921 memset (&displaysettings, 0, sizeof(displaysettings));
922 displaysettings.dmSize = sizeof(displaysettings);
923 EnumDisplaySettings (NULL, ENUM_CURRENT_SETTINGS, &displaysettings);
924 x = (displaysettings.dmPelsWidth - width) / 2;
925 y = (displaysettings.dmPelsHeight - height) / 2;
926
927 if (Args->CheckParm ("-0"))
928 {
929 x = y = 0;
930 }
931
932 WNDCLASS WndClass;
933 WndClass.style = 0;
934 WndClass.lpfnWndProc = LConProc;
935 WndClass.cbClsExtra = 0;
936 WndClass.cbWndExtra = 0;
937 WndClass.hInstance = hInstance;
938 WndClass.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE(IDI_ICON1));
939 WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);
940 WndClass.hbrBackground = NULL;
941 WndClass.lpszMenuName = NULL;
942 WndClass.lpszClassName = (LPCTSTR)WinClassName;
943
944 /* register this new class with Windows */
945 if (!RegisterClass((LPWNDCLASS)&WndClass))
946 I_FatalError ("Could not register window class");
947
948 /* create window */
949 char caption[100];
950 mysnprintf(caption, countof(caption), "" GAMESIG " %s " X64 " (%s)", GetVersionString(), GetGitTime());
951 Window = CreateWindowEx(
952 WS_EX_APPWINDOW,
953 (LPCTSTR)WinClassName,
954 (LPCTSTR)caption,
955 WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN,
956 x, y, width, height,
957 (HWND) NULL,
958 (HMENU) NULL,
959 hInstance,
960 NULL);
961
962 if (!Window)
963 I_FatalError ("Could not open window");
964
965 if (kernel != NULL)
966 {
967 typedef BOOL (WINAPI *pts)(DWORD, DWORD *);
968 pts pidsid = (pts)GetProcAddress (kernel, "ProcessIdToSessionId");
969 if (pidsid != 0)
970 {
971 if (!pidsid (GetCurrentProcessId(), &SessionID))
972 {
973 SessionID = 0;
974 }
975 hwtsapi32 = LoadLibraryA ("wtsapi32.dll");
976 if (hwtsapi32 != 0)
977 {
978 FARPROC reg = GetProcAddress (hwtsapi32, "WTSRegisterSessionNotification");
979 if (reg == 0 || !((BOOL(WINAPI *)(HWND, DWORD))reg) (Window, NOTIFY_FOR_THIS_SESSION))
980 {
981 FreeLibrary (hwtsapi32);
982 hwtsapi32 = 0;
983 }
984 else
985 {
986 atterm (UnWTS);
987 }
988 }
989 }
990 }
991
992 GetClientRect (Window, &cRect);
993
994 WinWidth = cRect.right;
995 WinHeight = cRect.bottom;
996
997 CoInitialize (NULL);
998 atterm (UnCOM);
999
1000 C_InitConsole (((WinWidth / 8) + 2) * 8, (WinHeight / 12) * 8, false);
1001
1002 I_DetectOS ();
1003 D_DoomMain ();
1004 }
1005 catch (class CNoRunExit &)
1006 {
1007 I_ShutdownGraphics();
1008 if (FancyStdOut && !AttachedStdOut)
1009 { // Outputting to a new console window: Wait for a keypress before quitting.
1010 DWORD bytes;
1011 HANDLE stdinput = GetStdHandle(STD_INPUT_HANDLE);
1012
1013 ShowWindow (Window, SW_HIDE);
1014 WriteFile(StdOut, "Press any key to exit...", 24, &bytes, NULL);
1015 FlushConsoleInputBuffer(stdinput);
1016 SetConsoleMode(stdinput, 0);
1017 ReadConsole(stdinput, &bytes, 1, &bytes, NULL);
1018 }
1019 else if (StdOut == NULL)
1020 {
1021 ShowErrorPane(NULL);
1022 }
1023 exit(0);
1024 }
1025 catch (class CDoomError &error)
1026 {
1027 I_ShutdownGraphics ();
1028 RestoreConView ();
1029 if (error.GetMessage ())
1030 {
1031 ShowErrorPane (error.GetMessage());
1032 }
1033 exit (-1);
1034 }
1035 }
1036
1037 //==========================================================================
1038 //
1039 // DoomSpecificInfo
1040 //
1041 // Called by the crash logger to get application-specific information.
1042 //
1043 //==========================================================================
1044
DoomSpecificInfo(char * buffer,size_t bufflen)1045 void DoomSpecificInfo (char *buffer, size_t bufflen)
1046 {
1047 const char *arg;
1048 char *const buffend = buffer + bufflen - 2; // -2 for CRLF at end
1049 int i;
1050
1051 buffer += mysnprintf (buffer, buffend - buffer, GAMENAME " version %s (%s)", GetVersionString(), GetGitHash());
1052 buffer += mysnprintf (buffer, buffend - buffer, "\r\nCommand line: %s\r\n", GetCommandLine());
1053
1054 for (i = 0; (arg = Wads.GetWadName (i)) != NULL; ++i)
1055 {
1056 buffer += mysnprintf (buffer, buffend - buffer, "\r\nWad %d: %s", i, arg);
1057 }
1058
1059 if (gamestate != GS_LEVEL && gamestate != GS_TITLELEVEL)
1060 {
1061 buffer += mysnprintf (buffer, buffend - buffer, "\r\n\r\nNot in a level.");
1062 }
1063 else
1064 {
1065 buffer += mysnprintf (buffer, buffend - buffer, "\r\n\r\nCurrent map: %s", level.MapName.GetChars());
1066
1067 if (!viewactive)
1068 {
1069 buffer += mysnprintf (buffer, buffend - buffer, "\r\n\r\nView not active.");
1070 }
1071 else
1072 {
1073 buffer += mysnprintf (buffer, buffend - buffer, "\r\n\r\nviewx = %d", viewx);
1074 buffer += mysnprintf (buffer, buffend - buffer, "\r\nviewy = %d", viewy);
1075 buffer += mysnprintf (buffer, buffend - buffer, "\r\nviewz = %d", viewz);
1076 buffer += mysnprintf (buffer, buffend - buffer, "\r\nviewangle = %x", viewangle);
1077 }
1078 }
1079 *buffer++ = '\r';
1080 *buffer++ = '\n';
1081 *buffer++ = '\0';
1082 }
1083
1084 // Here is how the error logging system works.
1085 //
1086 // To catch exceptions that occur in secondary threads, CatchAllExceptions is
1087 // set as the UnhandledExceptionFilter for this process. It records the state
1088 // of the thread at the time of the crash using CreateCrashLog and then queues
1089 // an APC on the primary thread. When the APC executes, it raises a software
1090 // exception that gets caught by the __try/__except block in WinMain.
1091 // I_GetEvent calls SleepEx to put the primary thread in a waitable state
1092 // periodically so that the APC has a chance to execute.
1093 //
1094 // Exceptions on the primary thread are caught by the __try/__except block in
1095 // WinMain. Not only does it record the crash information, it also shuts
1096 // everything down and displays a dialog with the information present. If a
1097 // console log is being produced, the information will also be appended to it.
1098 //
1099 // If a debugger is running, CatchAllExceptions never executes, so secondary
1100 // thread exceptions will always be caught by the debugger. For the primary
1101 // thread, IsDebuggerPresent is called to determine if a debugger is present.
1102 // Note that this function is not present on Windows 95, so we cannot
1103 // statically link to it.
1104 //
1105 // To make this work with MinGW, you will need to use inline assembly
1106 // because GCC offers no native support for Windows' SEH.
1107
1108 //==========================================================================
1109 //
1110 // SleepForever
1111 //
1112 //==========================================================================
1113
SleepForever()1114 void SleepForever ()
1115 {
1116 Sleep (INFINITE);
1117 }
1118
1119 //==========================================================================
1120 //
1121 // ExitMessedUp
1122 //
1123 // An exception occurred while exiting, so don't do any standard processing.
1124 // Just die.
1125 //
1126 //==========================================================================
1127
ExitMessedUp(LPEXCEPTION_POINTERS foo)1128 LONG WINAPI ExitMessedUp (LPEXCEPTION_POINTERS foo)
1129 {
1130 ExitProcess (1000);
1131 }
1132
1133 //==========================================================================
1134 //
1135 // ExitFatally
1136 //
1137 //==========================================================================
1138
ExitFatally(ULONG_PTR dummy)1139 void CALLBACK ExitFatally (ULONG_PTR dummy)
1140 {
1141 SetUnhandledExceptionFilter (ExitMessedUp);
1142 I_ShutdownGraphics ();
1143 RestoreConView ();
1144 DisplayCrashLog ();
1145 exit (-1);
1146 }
1147
1148 //==========================================================================
1149 //
1150 // CatchAllExceptions
1151 //
1152 //==========================================================================
1153
CatchAllExceptions(LPEXCEPTION_POINTERS info)1154 LONG WINAPI CatchAllExceptions (LPEXCEPTION_POINTERS info)
1155 {
1156 #ifdef _DEBUG
1157 if (info->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT)
1158 {
1159 return EXCEPTION_CONTINUE_SEARCH;
1160 }
1161 #endif
1162
1163 static bool caughtsomething = false;
1164
1165 if (caughtsomething) return EXCEPTION_EXECUTE_HANDLER;
1166 caughtsomething = true;
1167
1168 char *custominfo = (char *)HeapAlloc (GetProcessHeap(), 0, 16384);
1169
1170 CrashPointers = *info;
1171 DoomSpecificInfo (custominfo, 16384);
1172 CreateCrashLog (custominfo, (DWORD)strlen(custominfo), ConWindow);
1173
1174 // If the main thread crashed, then make it clean up after itself.
1175 // Otherwise, put the crashing thread to sleep and signal the main thread to clean up.
1176 if (GetCurrentThreadId() == MainThreadID)
1177 {
1178 #ifndef _M_X64
1179 info->ContextRecord->Eip = (DWORD_PTR)ExitFatally;
1180 #else
1181 info->ContextRecord->Rip = (DWORD_PTR)ExitFatally;
1182 #endif
1183 }
1184 else
1185 {
1186 #ifndef _M_X64
1187 info->ContextRecord->Eip = (DWORD_PTR)SleepForever;
1188 #else
1189 info->ContextRecord->Rip = (DWORD_PTR)SleepForever;
1190 #endif
1191 QueueUserAPC (ExitFatally, MainThread, 0);
1192 }
1193 return EXCEPTION_CONTINUE_EXECUTION;
1194 }
1195
1196 //==========================================================================
1197 //
1198 // infiniterecursion
1199 //
1200 // Debugging routine for testing the crash logger.
1201 //
1202 //==========================================================================
1203
1204 #ifdef _DEBUG
infiniterecursion(int foo)1205 static void infiniterecursion(int foo)
1206 {
1207 if (foo)
1208 {
1209 infiniterecursion(foo);
1210 }
1211 }
1212 #endif
1213
1214 //==========================================================================
1215 //
1216 // WinMain
1217 //
1218 //==========================================================================
1219
WinMain(HINSTANCE hInstance,HINSTANCE nothing,LPSTR cmdline,int nCmdShow)1220 int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE nothing, LPSTR cmdline, int nCmdShow)
1221 {
1222 g_hInst = hInstance;
1223
1224 InitCommonControls (); // Load some needed controls and be pretty under XP
1225
1226 // We need to load riched20.dll so that we can create the control.
1227 if (NULL == LoadLibrary ("riched20.dll"))
1228 {
1229 // This should only happen on basic Windows 95 installations, but since we
1230 // don't support Windows 95, we have no obligation to provide assistance in
1231 // getting it installed.
1232 MessageBoxA(NULL, "Could not load riched20.dll", GAMENAME " Error", MB_OK | MB_ICONSTOP);
1233 exit(0);
1234 }
1235
1236 #if !defined(__GNUC__) && defined(_DEBUG)
1237 if (__argc == 2 && strcmp (__argv[1], "TestCrash") == 0)
1238 {
1239 __try
1240 {
1241 *(int *)0 = 0;
1242 }
1243 __except(CrashPointers = *GetExceptionInformation(),
1244 CreateCrashLog (__argv[1], 9, NULL), EXCEPTION_EXECUTE_HANDLER)
1245 {
1246 }
1247 DisplayCrashLog ();
1248 exit (0);
1249 }
1250 if (__argc == 2 && strcmp (__argv[1], "TestStackCrash") == 0)
1251 {
1252 __try
1253 {
1254 infiniterecursion(1);
1255 }
1256 __except(CrashPointers = *GetExceptionInformation(),
1257 CreateCrashLog (__argv[1], 14, NULL), EXCEPTION_EXECUTE_HANDLER)
1258 {
1259 }
1260 DisplayCrashLog ();
1261 exit (0);
1262 }
1263 #endif
1264
1265 MainThread = INVALID_HANDLE_VALUE;
1266 DuplicateHandle (GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &MainThread,
1267 0, FALSE, DUPLICATE_SAME_ACCESS);
1268 MainThreadID = GetCurrentThreadId();
1269
1270 #ifndef _DEBUG
1271 if (MainThread != INVALID_HANDLE_VALUE)
1272 {
1273 SetUnhandledExceptionFilter (CatchAllExceptions);
1274 }
1275 #endif
1276
1277 #if defined(_DEBUG) && defined(_MSC_VER)
1278 // Uncomment this line to make the Visual C++ CRT check the heap before
1279 // every allocation and deallocation. This will be slow, but it can be a
1280 // great help in finding problem areas.
1281 //_CrtSetDbgFlag (_CRTDBG_ALLOC_MEM_DF | _CRTDBG_CHECK_ALWAYS_DF);
1282
1283 // Enable leak checking at exit.
1284 _CrtSetDbgFlag (_CrtSetDbgFlag(0) | _CRTDBG_LEAK_CHECK_DF);
1285
1286 // Use this to break at a specific allocation number.
1287 //_crtBreakAlloc = 77624;
1288 #endif
1289
1290 DoMain (hInstance);
1291
1292 CloseHandle (MainThread);
1293 MainThread = INVALID_HANDLE_VALUE;
1294 return 0;
1295 }
1296