qzdoom/src/win32/i_main.cpp
Christoph Oelckers cd180d29c7 - block direct use of 'new' for DObjects.
This is to ensure that the Class pointer can be set right on creation. ZDoom had always depended on handling this lazily which poses some problems for the VM.
So now there is a variadic Create<classtype> function taking care of that, but to ensure that it gets used, direct access to the new operator has been blocked.

This also neccessitated making DArgs a regular object because they get created before the type system is up. Since the few uses of DArgs are easily controllable this wasn't a big issue.

- did a bit of optimization on the bots' decision making whether to pick up a health item or not.
2017-04-14 13:31:58 +02:00

1347 lines
38 KiB
C++

/*
** i_main.cpp
** System-specific startup code. Eventually calls D_DoomMain.
**
**---------------------------------------------------------------------------
** Copyright 1998-2009 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
// HEADER FILES ------------------------------------------------------------
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0501
#include <windows.h>
#include <mmsystem.h>
#include <objbase.h>
#include <commctrl.h>
#include <richedit.h>
#ifdef _MSC_VER
#pragma warning(disable:4244)
#endif
//#include <wtsapi32.h>
#define NOTIFY_FOR_THIS_SESSION 0
#include <stdlib.h>
#ifdef _MSC_VER
#include <eh.h>
#include <new.h>
#include <crtdbg.h>
#endif
#include "resource.h"
#include <stdio.h>
#include <stdarg.h>
#include <math.h>
#include "doomerrors.h"
#include "hardware.h"
#include "doomtype.h"
#include "m_argv.h"
#include "d_main.h"
#include "i_module.h"
#include "i_system.h"
#include "c_console.h"
#include "version.h"
#include "i_video.h"
#include "i_sound.h"
#include "i_input.h"
#include "w_wad.h"
#include "templates.h"
#include "cmdlib.h"
#include "g_level.h"
#include "doomstat.h"
#include "r_utility.h"
#include "g_levellocals.h"
#include "stats.h"
#include "st_start.h"
#include "optwin32.h"
#include <assert.h>
// MACROS ------------------------------------------------------------------
// The main window's title.
#ifdef _M_X64
#define X64 " 64-bit"
#else
#define X64 ""
#endif
// The maximum number of functions that can be registered with atterm.
#define MAX_TERMS 64
// TYPES -------------------------------------------------------------------
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
void CreateCrashLog (char *custominfo, DWORD customsize, HWND richedit);
void DisplayCrashLog ();
extern uint8_t *ST_Util_BitsForBitmap (BITMAPINFO *bitmap_info);
void I_FlushBufferedConsoleStuff();
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
extern EXCEPTION_POINTERS CrashPointers;
extern BITMAPINFO *StartupBitmap;
extern UINT TimerPeriod;
// PUBLIC DATA DEFINITIONS -------------------------------------------------
// The command line arguments.
FArgs *Args;
HINSTANCE g_hInst;
DWORD SessionID;
HANDLE MainThread;
DWORD MainThreadID;
HANDLE StdOut;
bool FancyStdOut, AttachedStdOut;
bool ConWindowHidden;
// The main window
HWND Window;
// The subwindows used for startup and error output
HWND ConWindow, GameTitleWindow;
HWND ErrorPane, ProgressBar, NetStartPane, StartupScreen, ErrorIcon;
HFONT GameTitleFont;
LONG GameTitleFontHeight;
LONG DefaultGUIFontHeight;
LONG ErrorIconChar;
FModule Kernel32Module{"Kernel32"};
FModule Shell32Module{"Shell32"};
FModule User32Module{"User32"};
namespace OptWin32 {
#define DYN_WIN32_SYM(x) decltype(x) x{#x}
DYN_WIN32_SYM(SHGetFolderPathA);
DYN_WIN32_SYM(SHGetKnownFolderPath);
DYN_WIN32_SYM(GetLongPathNameA);
DYN_WIN32_SYM(GetMonitorInfoA);
#undef DYN_WIN32_SYM
} // namespace OptWin32
// PRIVATE DATA DEFINITIONS ------------------------------------------------
static const char WinClassName[] = GAMENAME "MainWindow";
static HMODULE hwtsapi32; // handle to wtsapi32.dll
static void (*TermFuncs[MAX_TERMS])(void);
static int NumTerms;
// CODE --------------------------------------------------------------------
//==========================================================================
//
// atterm
//
// Our own atexit because atexit can be problematic under Linux, though I
// forget the circumstances that cause trouble.
//
//==========================================================================
void atterm (void (*func)(void))
{
// Make sure this function wasn't already registered.
for (int i = 0; i < NumTerms; ++i)
{
if (TermFuncs[i] == func)
{
return;
}
}
if (NumTerms == MAX_TERMS)
{
func ();
I_FatalError ("Too many exit functions registered.\nIncrease MAX_TERMS in i_main.cpp");
}
TermFuncs[NumTerms++] = func;
}
//==========================================================================
//
// popterm
//
// Removes the most recently register atterm function.
//
//==========================================================================
void popterm ()
{
if (NumTerms)
NumTerms--;
}
//==========================================================================
//
// call_terms
//
//==========================================================================
static void call_terms (void)
{
while (NumTerms > 0)
{
TermFuncs[--NumTerms]();
}
}
#ifdef _MSC_VER
static int NewFailure (size_t size)
{
I_FatalError ("Failed to allocate %d bytes from process heap", size);
return 0;
}
#endif
//==========================================================================
//
// UnCOM
//
// Called by atterm if CoInitialize() succeeded.
//
//==========================================================================
static void UnCOM (void)
{
CoUninitialize ();
}
//==========================================================================
//
// UnWTS
//
// Called by atterm if RegisterSessionNotification() succeeded.
//
//==========================================================================
static void UnWTS (void)
{
if (hwtsapi32 != 0)
{
typedef BOOL (WINAPI *ursn)(HWND);
ursn unreg = (ursn)GetProcAddress (hwtsapi32, "WTSUnRegisterSessionNotification");
if (unreg != 0)
{
unreg (Window);
}
FreeLibrary (hwtsapi32);
hwtsapi32 = 0;
}
}
//==========================================================================
//
// LayoutErrorPane
//
// Lays out the error pane to the desired width, returning the required
// height.
//
//==========================================================================
static int LayoutErrorPane (HWND pane, int w)
{
HWND ctl;
RECT rectc;
// Right-align the Quit button.
ctl = GetDlgItem (pane, IDOK);
GetClientRect (ctl, &rectc); // Find out how big it is.
MoveWindow (ctl, w - rectc.right - 1, 1, rectc.right, rectc.bottom, TRUE);
InvalidateRect (ctl, NULL, TRUE);
// Return the needed height for this layout
return rectc.bottom + 2;
}
//==========================================================================
//
// LayoutNetStartPane
//
// Lays out the network startup pane to the specified width, returning
// its required height.
//
//==========================================================================
int LayoutNetStartPane (HWND pane, int w)
{
HWND ctl;
RECT margin, rectc;
int staticheight, barheight;
// Determine margin sizes.
SetRect (&margin, 7, 7, 0, 0);
MapDialogRect (pane, &margin);
// Stick the message text in the upper left corner.
ctl = GetDlgItem (pane, IDC_NETSTARTMESSAGE);
GetClientRect (ctl, &rectc);
MoveWindow (ctl, margin.left, margin.top, rectc.right, rectc.bottom, TRUE);
// Stick the count text in the upper right corner.
ctl = GetDlgItem (pane, IDC_NETSTARTCOUNT);
GetClientRect (ctl, &rectc);
MoveWindow (ctl, w - rectc.right - margin.left, margin.top, rectc.right, rectc.bottom, TRUE);
staticheight = rectc.bottom;
// Stretch the progress bar to fill the entire width.
ctl = GetDlgItem (pane, IDC_NETSTARTPROGRESS);
barheight = GetSystemMetrics (SM_CYVSCROLL);
MoveWindow (ctl, margin.left, margin.top*2 + staticheight, w - margin.left*2, barheight, TRUE);
// Center the abort button underneath the progress bar.
ctl = GetDlgItem (pane, IDCANCEL);
GetClientRect (ctl, &rectc);
MoveWindow (ctl, (w - rectc.right) / 2, margin.top*3 + staticheight + barheight, rectc.right, rectc.bottom, TRUE);
return margin.top*4 + staticheight + barheight + rectc.bottom;
}
//==========================================================================
//
// LayoutMainWindow
//
// Lays out the main window with the game title and log controls and
// possibly the error pane and progress bar.
//
//==========================================================================
void LayoutMainWindow (HWND hWnd, HWND pane)
{
RECT rect;
int errorpaneheight = 0;
int bannerheight = 0;
int progressheight = 0;
int netpaneheight = 0;
int leftside = 0;
int w, h;
GetClientRect (hWnd, &rect);
w = rect.right;
h = rect.bottom;
if (DoomStartupInfo.Name.IsNotEmpty() && GameTitleWindow != NULL)
{
bannerheight = GameTitleFontHeight + 5;
MoveWindow (GameTitleWindow, 0, 0, w, bannerheight, TRUE);
InvalidateRect (GameTitleWindow, NULL, FALSE);
}
if (ProgressBar != NULL)
{
// Base the height of the progress bar on the height of a scroll bar
// arrow, just as in the progress bar example.
progressheight = GetSystemMetrics (SM_CYVSCROLL);
MoveWindow (ProgressBar, 0, h - progressheight, w, progressheight, TRUE);
}
if (NetStartPane != NULL)
{
netpaneheight = LayoutNetStartPane (NetStartPane, w);
SetWindowPos (NetStartPane, HWND_TOP, 0, h - progressheight - netpaneheight, w, netpaneheight, SWP_SHOWWINDOW);
}
if (pane != NULL)
{
errorpaneheight = LayoutErrorPane (pane, w);
SetWindowPos (pane, HWND_TOP, 0, h - progressheight - netpaneheight - errorpaneheight, w, errorpaneheight, 0);
}
if (ErrorIcon != NULL)
{
leftside = GetSystemMetrics (SM_CXICON) + 6;
MoveWindow (ErrorIcon, 0, bannerheight, leftside, h - bannerheight - errorpaneheight - progressheight - netpaneheight, TRUE);
}
// If there is a startup screen, it covers the log window
if (StartupScreen != NULL)
{
SetWindowPos (StartupScreen, HWND_TOP, leftside, bannerheight, w - leftside,
h - bannerheight - errorpaneheight - progressheight - netpaneheight, SWP_SHOWWINDOW);
InvalidateRect (StartupScreen, NULL, FALSE);
MoveWindow (ConWindow, 0, 0, 0, 0, TRUE);
}
else
{
// The log window uses whatever space is left.
MoveWindow (ConWindow, leftside, bannerheight, w - leftside,
h - bannerheight - errorpaneheight - progressheight - netpaneheight, TRUE);
}
}
//==========================================================================
//
// I_SetIWADInfo
//
//==========================================================================
void I_SetIWADInfo()
{
// Make the startup banner show itself
LayoutMainWindow(Window, NULL);
}
//==========================================================================
//
// LConProc
//
// The main window's WndProc during startup. During gameplay, the WndProc
// in i_input.cpp is used instead.
//
//==========================================================================
LRESULT CALLBACK LConProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HWND view;
HDC hdc;
HBRUSH hbr;
HGDIOBJ oldfont;
RECT rect;
int titlelen;
SIZE size;
LOGFONT lf;
TEXTMETRIC tm;
HINSTANCE inst = (HINSTANCE)(LONG_PTR)GetWindowLongPtr(hWnd, GWLP_HINSTANCE);
DRAWITEMSTRUCT *drawitem;
CHARFORMAT2W format;
switch (msg)
{
case WM_CREATE:
// Create game title static control
memset (&lf, 0, sizeof(lf));
hdc = GetDC (hWnd);
lf.lfHeight = -MulDiv(12, GetDeviceCaps(hdc, LOGPIXELSY), 72);
lf.lfCharSet = ANSI_CHARSET;
lf.lfWeight = FW_BOLD;
lf.lfPitchAndFamily = VARIABLE_PITCH | FF_ROMAN;
strcpy (lf.lfFaceName, "Trebuchet MS");
GameTitleFont = CreateFontIndirect (&lf);
oldfont = SelectObject (hdc, GetStockObject (DEFAULT_GUI_FONT));
GetTextMetrics (hdc, &tm);
DefaultGUIFontHeight = tm.tmHeight;
if (GameTitleFont == NULL)
{
GameTitleFontHeight = DefaultGUIFontHeight;
}
else
{
SelectObject (hdc, GameTitleFont);
GetTextMetrics (hdc, &tm);
GameTitleFontHeight = tm.tmHeight;
}
SelectObject (hdc, oldfont);
// Create log read-only edit control
view = CreateWindowEx (WS_EX_NOPARENTNOTIFY, "RichEdit20W", NULL,
WS_CHILD | WS_VISIBLE | WS_VSCROLL |
ES_LEFT | ES_MULTILINE | WS_CLIPSIBLINGS,
0, 0, 0, 0,
hWnd, NULL, inst, NULL);
HRESULT hr;
hr = GetLastError();
if (view == NULL)
{
ReleaseDC (hWnd, hdc);
return -1;
}
SendMessage (view, EM_SETREADONLY, TRUE, 0);
SendMessage (view, EM_EXLIMITTEXT, 0, 0x7FFFFFFE);
SendMessage (view, EM_SETBKGNDCOLOR, 0, RGB(70,70,70));
// Setup default font for the log.
//SendMessage (view, WM_SETFONT, (WPARAM)GetStockObject (DEFAULT_GUI_FONT), FALSE);
format.cbSize = sizeof(format);
format.dwMask = CFM_BOLD | CFM_COLOR | CFM_FACE | CFM_SIZE | CFM_CHARSET;
format.dwEffects = 0;
format.yHeight = 200;
format.crTextColor = RGB(223,223,223);
format.bCharSet = ANSI_CHARSET;
format.bPitchAndFamily = FF_SWISS | VARIABLE_PITCH;
wcscpy(format.szFaceName, L"DejaVu Sans"); // At least I have it. :p
SendMessageW(view, EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&format);
ConWindow = view;
ReleaseDC (hWnd, hdc);
view = CreateWindowEx (WS_EX_NOPARENTNOTIFY, "STATIC", NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | SS_OWNERDRAW, 0, 0, 0, 0, hWnd, NULL, inst, NULL);
if (view == NULL)
{
return -1;
}
SetWindowLong (view, GWL_ID, IDC_STATIC_TITLE);
GameTitleWindow = view;
return 0;
case WM_SIZE:
if (wParam != SIZE_MAXHIDE && wParam != SIZE_MAXSHOW)
{
LayoutMainWindow (hWnd, ErrorPane);
}
return 0;
case WM_DRAWITEM:
// Draw title banner.
if (wParam == IDC_STATIC_TITLE && DoomStartupInfo.Name.IsNotEmpty())
{
const PalEntry *c;
// Draw the game title strip at the top of the window.
drawitem = (LPDRAWITEMSTRUCT)lParam;
// Draw the background.
rect = drawitem->rcItem;
rect.bottom -= 1;
c = (const PalEntry *)&DoomStartupInfo.BkColor;
hbr = CreateSolidBrush (RGB(c->r,c->g,c->b));
FillRect (drawitem->hDC, &drawitem->rcItem, hbr);
DeleteObject (hbr);
// Calculate width of the title string.
SetTextAlign (drawitem->hDC, TA_TOP);
oldfont = SelectObject (drawitem->hDC, GameTitleFont != NULL ? GameTitleFont : (HFONT)GetStockObject (DEFAULT_GUI_FONT));
titlelen = (int)DoomStartupInfo.Name.Len();
GetTextExtentPoint32 (drawitem->hDC, DoomStartupInfo.Name, titlelen, &size);
// Draw the title.
c = (const PalEntry *)&DoomStartupInfo.FgColor;
SetTextColor (drawitem->hDC, RGB(c->r,c->g,c->b));
SetBkMode (drawitem->hDC, TRANSPARENT);
TextOut (drawitem->hDC, rect.left + (rect.right - rect.left - size.cx) / 2, 2, DoomStartupInfo.Name, titlelen);
SelectObject (drawitem->hDC, oldfont);
return TRUE;
}
// Draw startup screen
else if (wParam == IDC_STATIC_STARTUP)
{
if (StartupScreen != NULL)
{
drawitem = (LPDRAWITEMSTRUCT)lParam;
rect = drawitem->rcItem;
// Windows expects DIBs to be bottom-up but ours is top-down,
// so flip it vertically while drawing it.
StretchDIBits (drawitem->hDC, rect.left, rect.bottom - 1, rect.right - rect.left, rect.top - rect.bottom,
0, 0, StartupBitmap->bmiHeader.biWidth, StartupBitmap->bmiHeader.biHeight,
ST_Util_BitsForBitmap(StartupBitmap), StartupBitmap, DIB_RGB_COLORS, SRCCOPY);
// If the title banner is gone, then this is an ENDOOM screen, so draw a short prompt
// where the command prompt would have been in DOS.
if (GameTitleWindow == NULL)
{
static const char QuitText[] = "Press any key or click anywhere in the window to quit.";
SetTextColor (drawitem->hDC, RGB(240,240,240));
SetBkMode (drawitem->hDC, TRANSPARENT);
oldfont = SelectObject (drawitem->hDC, (HFONT)GetStockObject (DEFAULT_GUI_FONT));
TextOut (drawitem->hDC, 3, drawitem->rcItem.bottom - DefaultGUIFontHeight - 3, QuitText, countof(QuitText)-1);
SelectObject (drawitem->hDC, oldfont);
}
return TRUE;
}
}
// Draw stop icon.
else if (wParam == IDC_ICONPIC)
{
HICON icon;
POINTL char_pos;
drawitem = (LPDRAWITEMSTRUCT)lParam;
// This background color should match the edit control's.
hbr = CreateSolidBrush (RGB(70,70,70));
FillRect (drawitem->hDC, &drawitem->rcItem, hbr);
DeleteObject (hbr);
// Draw the icon aligned with the first line of error text.
SendMessage (ConWindow, EM_POSFROMCHAR, (WPARAM)&char_pos, ErrorIconChar);
icon = (HICON)LoadImage (0, IDI_ERROR, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
DrawIcon (drawitem->hDC, 6, char_pos.y, icon);
return TRUE;
}
return FALSE;
case WM_COMMAND:
if (ErrorIcon != NULL && (HWND)lParam == ConWindow && HIWORD(wParam) == EN_UPDATE)
{
// Be sure to redraw the error icon if the edit control changes.
InvalidateRect (ErrorIcon, NULL, TRUE);
return 0;
}
break;
case WM_CLOSE:
PostQuitMessage (0);
break;
case WM_DESTROY:
if (GameTitleFont != NULL)
{
DeleteObject (GameTitleFont);
}
break;
}
return DefWindowProc (hWnd, msg, wParam, lParam);
}
//==========================================================================
//
// ErrorPaneProc
//
// DialogProc for the error pane.
//
//==========================================================================
INT_PTR CALLBACK ErrorPaneProc (HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_INITDIALOG:
// Appear in the main window.
LayoutMainWindow (GetParent (hDlg), hDlg);
return TRUE;
case WM_COMMAND:
// There is only one button, and it's "Ok" and makes us quit.
if (HIWORD(wParam) == BN_CLICKED)
{
PostQuitMessage (0);
return TRUE;
}
break;
}
return FALSE;
}
//==========================================================================
//
// I_SetWndProc
//
// Sets the main WndProc, hides all the child windows, and starts up
// in-game input.
//
//==========================================================================
void I_SetWndProc()
{
if (GetWindowLongPtr (Window, GWLP_USERDATA) == 0)
{
SetWindowLongPtr (Window, GWLP_USERDATA, 1);
SetWindowLongPtr (Window, GWLP_WNDPROC, (WLONG_PTR)WndProc);
ShowWindow (ConWindow, SW_HIDE);
ConWindowHidden = true;
ShowWindow (GameTitleWindow, SW_HIDE);
I_InitInput (Window);
}
}
//==========================================================================
//
// RestoreConView
//
// Returns the main window to its startup state.
//
//==========================================================================
void RestoreConView()
{
HDC screenDC = GetDC(0);
int dpi = GetDeviceCaps(screenDC, LOGPIXELSX);
ReleaseDC(0, screenDC);
int width = (512 * dpi + 96 / 2) / 96;
int height = (384 * dpi + 96 / 2) / 96;
// Make sure the window has a frame in case it was fullscreened.
SetWindowLongPtr (Window, GWL_STYLE, WS_VISIBLE|WS_OVERLAPPEDWINDOW);
if (GetWindowLong (Window, GWL_EXSTYLE) & WS_EX_TOPMOST)
{
SetWindowPos (Window, HWND_BOTTOM, 0, 0, width, height,
SWP_DRAWFRAME | SWP_NOCOPYBITS | SWP_NOMOVE);
SetWindowPos (Window, HWND_TOP, 0, 0, 0, 0, SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOSIZE);
}
else
{
SetWindowPos (Window, NULL, 0, 0, width, height,
SWP_DRAWFRAME | SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOZORDER);
}
SetWindowLongPtr (Window, GWLP_WNDPROC, (WLONG_PTR)LConProc);
ShowWindow (ConWindow, SW_SHOW);
ConWindowHidden = false;
ShowWindow (GameTitleWindow, SW_SHOW);
I_ShutdownInput (); // Make sure the mouse pointer is available.
// Make sure the progress bar isn't visible.
if (StartScreen != NULL)
{
delete StartScreen;
StartScreen = NULL;
}
}
//==========================================================================
//
// ShowErrorPane
//
// Shows an error message, preferably in the main window, but it can
// use a normal message box too.
//
//==========================================================================
void ShowErrorPane(const char *text)
{
if (Window == NULL || ConWindow == NULL)
{
if (text != NULL)
{
MessageBox (Window, text,
GAMESIG " Fatal Error", MB_OK|MB_ICONSTOP|MB_TASKMODAL);
}
return;
}
if (StartScreen != NULL) // Ensure that the network pane is hidden.
{
StartScreen->NetDone();
}
if (text != NULL)
{
char caption[100];
mysnprintf(caption, countof(caption), "Fatal Error - " GAMESIG " %s " X64 " (%s)", GetVersionString(), GetGitTime());
SetWindowText (Window, caption);
ErrorIcon = CreateWindowEx (WS_EX_NOPARENTNOTIFY, "STATIC", NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | SS_OWNERDRAW, 0, 0, 0, 0, Window, NULL, g_hInst, NULL);
if (ErrorIcon != NULL)
{
SetWindowLong (ErrorIcon, GWL_ID, IDC_ICONPIC);
}
}
ErrorPane = CreateDialogParam (g_hInst, MAKEINTRESOURCE(IDD_ERRORPANE), Window, ErrorPaneProc, (LONG_PTR)NULL);
if (text != NULL)
{
CHARRANGE end;
CHARFORMAT2 oldformat, newformat;
PARAFORMAT2 paraformat;
// Append the error message to the log.
end.cpMax = end.cpMin = GetWindowTextLength (ConWindow);
SendMessage (ConWindow, EM_EXSETSEL, 0, (LPARAM)&end);
// Remember current charformat.
oldformat.cbSize = sizeof(oldformat);
SendMessage (ConWindow, EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM)&oldformat);
// Use bigger font and standout colors.
newformat.cbSize = sizeof(newformat);
newformat.dwMask = CFM_BOLD | CFM_COLOR | CFM_SIZE;
newformat.dwEffects = CFE_BOLD;
newformat.yHeight = oldformat.yHeight * 5 / 4;
newformat.crTextColor = RGB(255,170,170);
SendMessage (ConWindow, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&newformat);
// Indent the rest of the text to make the error message stand out a little more.
paraformat.cbSize = sizeof(paraformat);
paraformat.dwMask = PFM_STARTINDENT | PFM_OFFSETINDENT | PFM_RIGHTINDENT;
paraformat.dxStartIndent = paraformat.dxOffset = paraformat.dxRightIndent = 120;
SendMessage (ConWindow, EM_SETPARAFORMAT, 0, (LPARAM)&paraformat);
SendMessage (ConWindow, EM_REPLACESEL, FALSE, (LPARAM)"\n");
// Find out where the error lines start for the error icon display control.
SendMessage (ConWindow, EM_EXGETSEL, 0, (LPARAM)&end);
ErrorIconChar = end.cpMax;
// Now start adding the actual error message.
SendMessage (ConWindow, EM_REPLACESEL, FALSE, (LPARAM)"Execution could not continue.\n\n");
// Restore old charformat but with light yellow text.
oldformat.crTextColor = RGB(255,255,170);
SendMessage (ConWindow, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&oldformat);
// Add the error text.
SendMessage (ConWindow, EM_REPLACESEL, FALSE, (LPARAM)text);
// Make sure the error text is not scrolled below the window.
SendMessage (ConWindow, EM_LINESCROLL, 0, SendMessage (ConWindow, EM_GETLINECOUNT, 0, 0));
// The above line scrolled everything off the screen, so pretend to move the scroll
// bar thumb, which clamps to not show any extra lines if it doesn't need to.
SendMessage (ConWindow, EM_SCROLL, SB_PAGEDOWN, 0);
}
BOOL bRet;
MSG msg;
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (bRet == -1)
{
MessageBox (Window, text,
GAMESIG " Fatal Error", MB_OK|MB_ICONSTOP|MB_TASKMODAL);
return;
}
else if (!IsDialogMessage (ErrorPane, &msg))
{
TranslateMessage (&msg);
DispatchMessage (&msg);
}
}
}
//==========================================================================
//
// DoMain
//
//==========================================================================
void DoMain (HINSTANCE hInstance)
{
LONG WinWidth, WinHeight;
int height, width, x, y;
RECT cRect;
TIMECAPS tc;
DEVMODE displaysettings;
try
{
#ifdef _MSC_VER
_set_new_handler (NewFailure);
#endif
Args = new FArgs(__argc, __argv);
// Load Win32 modules
Kernel32Module.Load({"kernel32.dll"});
Shell32Module.Load({"shell32.dll"});
User32Module.Load({"user32.dll"});
// Under XP, get our session ID so we can know when the user changes/locks sessions.
// Since we need to remain binary compatible with older versions of Windows, we
// need to extract the ProcessIdToSessionId function from kernel32.dll manually.
HMODULE kernel = GetModuleHandle ("kernel32.dll");
if (Args->CheckParm("-stdout"))
{
// As a GUI application, we don't normally get a console when we start.
// If we were run from the shell and are on XP+, we can attach to its
// console. Otherwise, we can create a new one. If we already have a
// stdout handle, then we have been redirected and should just use that
// handle instead of creating a console window.
StdOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (StdOut != NULL)
{
// It seems that running from a shell always creates a std output
// for us, even if it doesn't go anywhere. (Running from Explorer
// does not.) If we can get file information for this handle, it's
// a file or pipe, so use it. Otherwise, pretend it wasn't there
// and find a console to use instead.
BY_HANDLE_FILE_INFORMATION info;
if (!GetFileInformationByHandle(StdOut, &info))
{
StdOut = NULL;
}
}
if (StdOut == NULL)
{
// AttachConsole was introduced with Windows XP. (OTOH, since we
// have to share the console with the shell, I'm not sure if it's
// a good idea to actually attach to it.)
typedef BOOL (WINAPI *ac)(DWORD);
ac attach_console = kernel != NULL ? (ac)GetProcAddress(kernel, "AttachConsole") : NULL;
if (attach_console != NULL && attach_console(ATTACH_PARENT_PROCESS))
{
StdOut = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD foo; WriteFile(StdOut, "\n", 1, &foo, NULL);
AttachedStdOut = true;
}
if (StdOut == NULL && AllocConsole())
{
StdOut = GetStdHandle(STD_OUTPUT_HANDLE);
}
FancyStdOut = true;
}
}
// Set the timer to be as accurate as possible
if (timeGetDevCaps (&tc, sizeof(tc)) != TIMERR_NOERROR)
TimerPeriod = 1; // Assume minimum resolution of 1 ms
else
TimerPeriod = tc.wPeriodMin;
timeBeginPeriod (TimerPeriod);
/*
killough 1/98:
This fixes some problems with exit handling
during abnormal situations.
The old code called I_Quit() to end program,
while now I_Quit() is installed as an exit
handler and exit() is called to exit, either
normally or abnormally.
*/
atexit (call_terms);
atterm (I_Quit);
// Figure out what directory the program resides in.
char *program;
#ifdef _MSC_VER
if (_get_pgmptr(&program) != 0)
{
I_FatalError("Could not determine program location.");
}
#else
char progbuff[1024];
GetModuleFileName(0, progbuff, sizeof(progbuff));
progbuff[1023] = '\0';
program = progbuff;
#endif
progdir = program;
program = progdir.LockBuffer();
*(strrchr(program, '\\') + 1) = '\0';
FixPathSeperator(program);
progdir.Truncate((long)strlen(program));
progdir.UnlockBuffer();
HDC screenDC = GetDC(0);
int dpi = GetDeviceCaps(screenDC, LOGPIXELSX);
ReleaseDC(0, screenDC);
width = (512 * dpi + 96 / 2) / 96;
height = (384 * dpi + 96 / 2) / 96;
// Many Windows structures that specify their size do so with the first
// element. DEVMODE is not one of those structures.
memset (&displaysettings, 0, sizeof(displaysettings));
displaysettings.dmSize = sizeof(displaysettings);
EnumDisplaySettings (NULL, ENUM_CURRENT_SETTINGS, &displaysettings);
x = (displaysettings.dmPelsWidth - width) / 2;
y = (displaysettings.dmPelsHeight - height) / 2;
if (Args->CheckParm ("-0"))
{
x = y = 0;
}
WNDCLASS WndClass;
WndClass.style = 0;
WndClass.lpfnWndProc = LConProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hInstance = hInstance;
WndClass.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE(IDI_ICON1));
WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);
WndClass.hbrBackground = NULL;
WndClass.lpszMenuName = NULL;
WndClass.lpszClassName = (LPCTSTR)WinClassName;
/* register this new class with Windows */
if (!RegisterClass((LPWNDCLASS)&WndClass))
I_FatalError ("Could not register window class");
/* create window */
char caption[100];
mysnprintf(caption, countof(caption), "" GAMESIG " %s " X64 " (%s)", GetVersionString(), GetGitTime());
Window = CreateWindowEx(
WS_EX_APPWINDOW,
(LPCTSTR)WinClassName,
(LPCTSTR)caption,
WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN,
x, y, width, height,
(HWND) NULL,
(HMENU) NULL,
hInstance,
NULL);
if (!Window)
I_FatalError ("Could not open window");
if (kernel != NULL)
{
typedef BOOL (WINAPI *pts)(DWORD, DWORD *);
pts pidsid = (pts)GetProcAddress (kernel, "ProcessIdToSessionId");
if (pidsid != 0)
{
if (!pidsid (GetCurrentProcessId(), &SessionID))
{
SessionID = 0;
}
hwtsapi32 = LoadLibraryA ("wtsapi32.dll");
if (hwtsapi32 != 0)
{
FARPROC reg = GetProcAddress (hwtsapi32, "WTSRegisterSessionNotification");
if (reg == 0 || !((BOOL(WINAPI *)(HWND, DWORD))reg) (Window, NOTIFY_FOR_THIS_SESSION))
{
FreeLibrary (hwtsapi32);
hwtsapi32 = 0;
}
else
{
atterm (UnWTS);
}
}
}
}
GetClientRect (Window, &cRect);
WinWidth = cRect.right;
WinHeight = cRect.bottom;
CoInitialize (NULL);
atterm (UnCOM);
C_InitConsole (((WinWidth / 8) + 2) * 8, (WinHeight / 12) * 8, false);
I_DetectOS ();
D_DoomMain ();
}
catch (class CNoRunExit &)
{
I_ShutdownGraphics();
if (!batchrun)
{
if (FancyStdOut && !AttachedStdOut)
{ // Outputting to a new console window: Wait for a keypress before quitting.
DWORD bytes;
HANDLE stdinput = GetStdHandle(STD_INPUT_HANDLE);
ShowWindow(Window, SW_HIDE);
WriteFile(StdOut, "Press any key to exit...", 24, &bytes, NULL);
FlushConsoleInputBuffer(stdinput);
SetConsoleMode(stdinput, 0);
ReadConsole(stdinput, &bytes, 1, &bytes, NULL);
}
else if (StdOut == NULL)
{
ShowErrorPane(NULL);
}
}
exit(0);
}
catch (class CDoomError &error)
{
I_ShutdownGraphics ();
RestoreConView ();
I_FlushBufferedConsoleStuff();
if (error.GetMessage ())
{
if (!batchrun)
{
ShowErrorPane(error.GetMessage());
}
else
{
Printf("%s\n", error.GetMessage());
}
}
exit (-1);
}
}
//==========================================================================
//
// DoomSpecificInfo
//
// Called by the crash logger to get application-specific information.
//
//==========================================================================
void DoomSpecificInfo (char *buffer, size_t bufflen)
{
const char *arg;
char *const buffend = buffer + bufflen - 2; // -2 for CRLF at end
int i;
buffer += mysnprintf (buffer, buffend - buffer, GAMENAME " version %s (%s)", GetVersionString(), GetGitHash());
buffer += mysnprintf (buffer, buffend - buffer, "\r\nCommand line: %s\r\n", GetCommandLine());
for (i = 0; (arg = Wads.GetWadName (i)) != NULL; ++i)
{
buffer += mysnprintf (buffer, buffend - buffer, "\r\nWad %d: %s", i, arg);
}
if (gamestate != GS_LEVEL && gamestate != GS_TITLELEVEL)
{
buffer += mysnprintf (buffer, buffend - buffer, "\r\n\r\nNot in a level.");
}
else
{
buffer += mysnprintf (buffer, buffend - buffer, "\r\n\r\nCurrent map: %s", level.MapName.GetChars());
if (!viewactive)
{
buffer += mysnprintf (buffer, buffend - buffer, "\r\n\r\nView not active.");
}
else
{
buffer += mysnprintf (buffer, buffend - buffer, "\r\n\r\nviewx = %f", r_viewpoint.Pos.X);
buffer += mysnprintf (buffer, buffend - buffer, "\r\nviewy = %f", r_viewpoint.Pos.Y);
buffer += mysnprintf (buffer, buffend - buffer, "\r\nviewz = %f", r_viewpoint.Pos.Z);
buffer += mysnprintf (buffer, buffend - buffer, "\r\nviewangle = %f", r_viewpoint.Angles.Yaw);
}
}
*buffer++ = '\r';
*buffer++ = '\n';
*buffer++ = '\0';
}
// Here is how the error logging system works.
//
// To catch exceptions that occur in secondary threads, CatchAllExceptions is
// set as the UnhandledExceptionFilter for this process. It records the state
// of the thread at the time of the crash using CreateCrashLog and then queues
// an APC on the primary thread. When the APC executes, it raises a software
// exception that gets caught by the __try/__except block in WinMain.
// I_GetEvent calls SleepEx to put the primary thread in a waitable state
// periodically so that the APC has a chance to execute.
//
// Exceptions on the primary thread are caught by the __try/__except block in
// WinMain. Not only does it record the crash information, it also shuts
// everything down and displays a dialog with the information present. If a
// console log is being produced, the information will also be appended to it.
//
// If a debugger is running, CatchAllExceptions never executes, so secondary
// thread exceptions will always be caught by the debugger. For the primary
// thread, IsDebuggerPresent is called to determine if a debugger is present.
// Note that this function is not present on Windows 95, so we cannot
// statically link to it.
//
// To make this work with MinGW, you will need to use inline assembly
// because GCC offers no native support for Windows' SEH.
//==========================================================================
//
// SleepForever
//
//==========================================================================
void SleepForever ()
{
Sleep (INFINITE);
}
//==========================================================================
//
// ExitMessedUp
//
// An exception occurred while exiting, so don't do any standard processing.
// Just die.
//
//==========================================================================
LONG WINAPI ExitMessedUp (LPEXCEPTION_POINTERS foo)
{
ExitProcess (1000);
}
//==========================================================================
//
// ExitFatally
//
//==========================================================================
void CALLBACK ExitFatally (ULONG_PTR dummy)
{
SetUnhandledExceptionFilter (ExitMessedUp);
I_ShutdownGraphics ();
RestoreConView ();
DisplayCrashLog ();
exit (-1);
}
//==========================================================================
//
// CatchAllExceptions
//
//==========================================================================
namespace
{
CONTEXT MainThreadContext;
}
LONG WINAPI CatchAllExceptions (LPEXCEPTION_POINTERS info)
{
#ifdef _DEBUG
if (info->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT)
{
return EXCEPTION_CONTINUE_SEARCH;
}
#endif
static bool caughtsomething = false;
if (caughtsomething) return EXCEPTION_EXECUTE_HANDLER;
caughtsomething = true;
char *custominfo = (char *)HeapAlloc (GetProcessHeap(), 0, 16384);
CrashPointers = *info;
DoomSpecificInfo (custominfo, 16384);
CreateCrashLog (custominfo, (DWORD)strlen(custominfo), ConWindow);
// If the main thread crashed, then make it clean up after itself.
// Otherwise, put the crashing thread to sleep and signal the main thread to clean up.
if (GetCurrentThreadId() == MainThreadID)
{
*info->ContextRecord = MainThreadContext;
}
else
{
#ifndef _M_X64
info->ContextRecord->Eip = (DWORD_PTR)SleepForever;
#else
info->ContextRecord->Rip = (DWORD_PTR)SleepForever;
#endif
QueueUserAPC (ExitFatally, MainThread, 0);
}
return EXCEPTION_CONTINUE_EXECUTION;
}
//==========================================================================
//
// infiniterecursion
//
// Debugging routine for testing the crash logger.
//
//==========================================================================
#ifdef _DEBUG
static void infiniterecursion(int foo)
{
if (foo)
{
infiniterecursion(foo);
}
}
#endif
//==========================================================================
//
// WinMain
//
//==========================================================================
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE nothing, LPSTR cmdline, int nCmdShow)
{
g_hInst = hInstance;
InitCommonControls (); // Load some needed controls and be pretty under XP
// We need to load riched20.dll so that we can create the control.
if (NULL == LoadLibrary ("riched20.dll"))
{
// This should only happen on basic Windows 95 installations, but since we
// don't support Windows 95, we have no obligation to provide assistance in
// getting it installed.
MessageBoxA(NULL, "Could not load riched20.dll", GAMENAME " Error", MB_OK | MB_ICONSTOP);
exit(0);
}
#if !defined(__GNUC__) && defined(_DEBUG)
if (__argc == 2 && strcmp (__argv[1], "TestCrash") == 0)
{
__try
{
*(int *)0 = 0;
}
__except(CrashPointers = *GetExceptionInformation(),
CreateCrashLog (__argv[1], 9, NULL), EXCEPTION_EXECUTE_HANDLER)
{
}
DisplayCrashLog ();
exit (0);
}
if (__argc == 2 && strcmp (__argv[1], "TestStackCrash") == 0)
{
__try
{
infiniterecursion(1);
}
__except(CrashPointers = *GetExceptionInformation(),
CreateCrashLog (__argv[1], 14, NULL), EXCEPTION_EXECUTE_HANDLER)
{
}
DisplayCrashLog ();
exit (0);
}
#endif
MainThread = INVALID_HANDLE_VALUE;
DuplicateHandle (GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &MainThread,
0, FALSE, DUPLICATE_SAME_ACCESS);
MainThreadID = GetCurrentThreadId();
#ifndef _DEBUG
if (MainThread != INVALID_HANDLE_VALUE)
{
SetUnhandledExceptionFilter (CatchAllExceptions);
static bool setJumpResult = false;
RtlCaptureContext(&MainThreadContext);
if (setJumpResult)
{
ExitFatally(0);
return 0;
}
setJumpResult = true;
}
#endif
#if defined(_DEBUG) && defined(_MSC_VER)
// Uncomment this line to make the Visual C++ CRT check the heap before
// every allocation and deallocation. This will be slow, but it can be a
// great help in finding problem areas.
//_CrtSetDbgFlag (_CRTDBG_ALLOC_MEM_DF | _CRTDBG_CHECK_ALWAYS_DF);
// Enable leak checking at exit.
_CrtSetDbgFlag (_CrtSetDbgFlag(0) | _CRTDBG_LEAK_CHECK_DF);
// Use this to break at a specific allocation number.
//_crtBreakAlloc = 53039;
#endif
DoMain (hInstance);
CloseHandle (MainThread);
MainThread = INVALID_HANDLE_VALUE;
return 0;
}