From b82d61193669657c2bc24bc280bc5735c15aeacc Mon Sep 17 00:00:00 2001 From: Christopher Bruns Date: Fri, 30 Oct 2015 21:29:36 -0400 Subject: [PATCH] Call EndDrawScene for each eye. Actually use stereoscopic projection matrix. --- src/gl/scene/gl_drawinfo.cpp | 10 +- src/gl/scene/gl_portal.cpp | 61 +- src/gl/scene/gl_scene.cpp | 4 +- src/win32/i_system.cpp | 3404 +++++++++++++++++----------------- 4 files changed, 1740 insertions(+), 1739 deletions(-) diff --git a/src/gl/scene/gl_drawinfo.cpp b/src/gl/scene/gl_drawinfo.cpp index 4dba1a2a0..5c0c53047 100644 --- a/src/gl/scene/gl_drawinfo.cpp +++ b/src/gl/scene/gl_drawinfo.cpp @@ -1014,7 +1014,8 @@ void FDrawInfo::SetupFloodStencil(wallseg * ws) glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); // increment stencil of valid pixels { // Use revertible color mask, to avoid stomping on anaglyph 3D state - ScopedColorMask colorMask(0, 0, 0, 0); // glColorMask(0, 0, 0, 0); // don't write to the graphics buffer + // ScopedColorMask colorMask(0, 0, 0, 0); + glColorMask(0, 0, 0, 0); // don't write to the graphics buffer gl_RenderState.EnableTexture(false); gl_RenderState.ResetColor(); glEnable(GL_DEPTH_TEST); @@ -1036,7 +1037,7 @@ void FDrawInfo::SetupFloodStencil(wallseg * ws) glStencilFunc(GL_EQUAL, recursion + 1, ~0); // draw sky into stencil glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // this stage doesn't modify the stencil - } // glColorMask(1, 1, 1, 1); // don't write to the graphics buffer + } glColorMask(1, 1, 1, 1); // don't write to the graphics buffer gl_RenderState.EnableTexture(true); glDisable(GL_DEPTH_TEST); glDepthMask(false); @@ -1050,7 +1051,8 @@ void FDrawInfo::ClearFloodStencil(wallseg * ws) gl_RenderState.EnableTexture(false); { // Use revertible color mask, to avoid stomping on anaglyph 3D state - ScopedColorMask colorMask(0, 0, 0, 0); // glColorMask(0,0,0,0); // don't write to the graphics buffer + // ScopedColorMask colorMask(0, 0, 0, 0); + glColorMask(0,0,0,0); // don't write to the graphics buffer gl_RenderState.ResetColor(); gl_RenderState.Apply(); @@ -1069,7 +1071,7 @@ void FDrawInfo::ClearFloodStencil(wallseg * ws) glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); glStencilFunc(GL_EQUAL, recursion, ~0); gl_RenderState.EnableTexture(true); - } // glColorMask(1, 1, 1, 1); + } glColorMask(1, 1, 1, 1); glEnable(GL_DEPTH_TEST); glDepthMask(true); } diff --git a/src/gl/scene/gl_portal.cpp b/src/gl/scene/gl_portal.cpp index 7ff82db4c..8af0619a1 100644 --- a/src/gl/scene/gl_portal.cpp +++ b/src/gl/scene/gl_portal.cpp @@ -198,7 +198,7 @@ bool GLPortal::Start(bool usestencil, bool doquery) // Create stencil glStencilFunc(GL_EQUAL,recursion,~0); // create stencil glStencilOp(GL_KEEP,GL_KEEP,GL_INCR); // increment stencil of valid pixels - // glColorMask(0,0,0,0); // don't write to the graphics buffer + glColorMask(0,0,0,0); // don't write to the graphics buffer gl_RenderState.SetEffect(EFF_STENCIL); gl_RenderState.EnableTexture(false); gl_RenderState.ResetColor(); @@ -207,36 +207,34 @@ bool GLPortal::Start(bool usestencil, bool doquery) if (NeedDepthBuffer()) { + glDepthMask(false); // don't write to Z-buffer! + if (!NeedDepthBuffer()) doquery = false; // too much overhead and nothing to gain. + else if (gl_noquery) doquery = false; + + // If occlusion query is supported let's use it to avoid rendering portals that aren't visible + if (!QueryObject) glGenQueries(1, &QueryObject); + if (QueryObject) { - ScopedColorMask colorMask(0, 0, 0, 0); - glDepthMask(false); // don't write to Z-buffer! - if (!NeedDepthBuffer()) doquery = false; // too much overhead and nothing to gain. - else if (gl_noquery) doquery = false; + glBeginQuery(GL_SAMPLES_PASSED, QueryObject); + } + else doquery = false; // some kind of error happened - // If occlusion query is supported let's use it to avoid rendering portals that aren't visible - if (!QueryObject) glGenQueries(1, &QueryObject); - if (QueryObject) - { - glBeginQuery(GL_SAMPLES_PASSED, QueryObject); - } - else doquery = false; // some kind of error happened + DrawPortalStencil(); - DrawPortalStencil(); + glEndQuery(GL_SAMPLES_PASSED); - glEndQuery(GL_SAMPLES_PASSED); + // Clear Z-buffer + glStencilFunc(GL_EQUAL, recursion + 1, ~0); // draw sky into stencil + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // this stage doesn't modify the stencil + glDepthMask(true); // enable z-buffer again + glDepthRange(1, 1); + glDepthFunc(GL_ALWAYS); + DrawPortalStencil(); - // Clear Z-buffer - glStencilFunc(GL_EQUAL, recursion + 1, ~0); // draw sky into stencil - glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // this stage doesn't modify the stencil - glDepthMask(true); // enable z-buffer again - glDepthRange(1, 1); - glDepthFunc(GL_ALWAYS); - DrawPortalStencil(); - - // set normal drawing mode - gl_RenderState.EnableTexture(true); - glDepthFunc(GL_LESS); - } // glColorMask(1, 1, 1, 1); + // set normal drawing mode + gl_RenderState.EnableTexture(true); + glDepthFunc(GL_LESS); + glColorMask(1, 1, 1, 1); gl_RenderState.SetEffect(EFF_NONE); glDepthRange(0, 1); @@ -261,13 +259,12 @@ bool GLPortal::Start(bool usestencil, bool doquery) // than the benefit. // Note: We must draw the stencil with z-write enabled here because there is no second pass! - ScopedColorMask colorMask(0, 0, 0, 0); glDepthMask(true); DrawPortalStencil(); glStencilFunc(GL_EQUAL,recursion+1,~0); // draw sky into stencil glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP); // this stage doesn't modify the stencil gl_RenderState.EnableTexture(true); - // glColorMask(1,1,1,1); + glColorMask(1,1,1,1); gl_RenderState.SetEffect(EFF_NONE); glDisable(GL_DEPTH_TEST); glDepthMask(false); // don't write to Z-buffer! @@ -376,7 +373,8 @@ void GLPortal::End(bool usestencil) GLRenderer->SetupView(viewx, viewy, viewz, viewangle, !!(MirrorFlag & 1), !!(PlaneMirrorFlag & 1)); { - ScopedColorMask colorMask(0, 0, 0, 0); // glColorMask(0, 0, 0, 0); // no graphics + // ScopedColorMask colorMask(0, 0, 0, 0); + glColorMask(0, 0, 0, 0); // no graphics gl_RenderState.SetEffect(EFF_NONE); gl_RenderState.ResetColor(); gl_RenderState.EnableTexture(false); @@ -405,7 +403,7 @@ void GLPortal::End(bool usestencil) gl_RenderState.EnableTexture(true); gl_RenderState.SetEffect(EFF_NONE); - } // glColorMask(1, 1, 1, 1); + } glColorMask(1, 1, 1, 1); recursion--; // restore old stencil op. @@ -440,7 +438,8 @@ void GLPortal::End(bool usestencil) glDepthFunc(GL_LEQUAL); glDepthRange(0, 1); { - ScopedColorMask colorMask(0, 0, 0, 0); // glColorMask(0,0,0,0); // no graphics + ScopedColorMask colorMask(0, 0, 0, 0); + // glColorMask(0,0,0,0); // no graphics gl_RenderState.SetEffect(EFF_STENCIL); gl_RenderState.EnableTexture(false); DrawPortalStencil(); diff --git a/src/gl/scene/gl_scene.cpp b/src/gl/scene/gl_scene.cpp index 618c285c0..bde423078 100644 --- a/src/gl/scene/gl_scene.cpp +++ b/src/gl/scene/gl_scene.cpp @@ -826,7 +826,7 @@ sector_t * FGLRenderer::RenderViewpoint (AActor * camera, GL_IRECT * bounds, flo // Stereo mode specific perspective projection (*eye)->GetProjection(fov, ratio, fovratio, projectionMatrix); SetProjection(projectionMatrix); - SetProjection(fov, ratio, fovratio); // switch to perspective mode and set up clipper + // SetProjection(fov, ratio, fovratio); // switch to perspective mode and set up clipper SetViewAngle(viewangle); // Stereo mode specific viewpoint adjustment - temporarily shifts global viewx, viewy, viewz (*eye)->GetViewShift(GLRenderer->mAngles.Yaw, viewShift); @@ -839,6 +839,7 @@ sector_t * FGLRenderer::RenderViewpoint (AActor * camera, GL_IRECT * bounds, flo clipper.SafeAddClipRangeRealAngles(viewangle + a1, viewangle - a1); ProcessScene(toscreen); + EndDrawScene(viewsector); } stereo3dMode.TearDown(); @@ -910,7 +911,6 @@ void FGLRenderer::RenderView (player_t* player) GLRenderer->mLightCount = ((it.Next()) != NULL); sector_t * viewsector = RenderViewpoint(player->camera, NULL, FieldOfView * 360.0f / FINEANGLES, ratio, fovratio, true, true); - EndDrawScene(viewsector); All.Unclock(); } diff --git a/src/win32/i_system.cpp b/src/win32/i_system.cpp index 372da8685..65f8c1ef5 100644 --- a/src/win32/i_system.cpp +++ b/src/win32/i_system.cpp @@ -1,1702 +1,1702 @@ -/* -** i_system.cpp -** Timers, pre-console output, IWAD selection, and misc system routines. -** -**--------------------------------------------------------------------------- -** 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 ------------------------------------------------------------ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#include - -#define USE_WINDOWS_DWORD -#include "hardware.h" -#include "doomerrors.h" -#include - -#include "doomtype.h" -#include "version.h" -#include "doomdef.h" -#include "cmdlib.h" -#include "m_argv.h" -#include "m_misc.h" -#include "i_video.h" -#include "i_sound.h" -#include "i_music.h" -#include "resource.h" -#include "x86.h" -#include "stats.h" - -#include "d_main.h" -#include "d_net.h" -#include "g_game.h" -#include "i_input.h" -#include "i_system.h" -#include "c_dispatch.h" -#include "templates.h" -#include "gameconfigfile.h" -#include "v_font.h" -#include "g_level.h" -#include "doomstat.h" -#include "v_palette.h" -#include "stats.h" -#include "textures/bitmap.h" -#include "textures/textures.h" - -// MACROS ------------------------------------------------------------------ - -#ifdef _MSC_VER -// Turn off "conversion from 'LONG_PTR' to 'LONG', possible loss of data" -// generated by SetClassLongPtr(). -#pragma warning(disable:4244) -#endif - -// TYPES ------------------------------------------------------------------- - -// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- - -extern void CheckCPUID(CPUInfo *cpu); -extern void LayoutMainWindow(HWND hWnd, HWND pane); - -// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- - -// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- - -static void CalculateCPUSpeed(); -static void I_SelectTimer(); - -static int I_GetTimePolled(bool saveMS); -static int I_WaitForTicPolled(int prevtic); -static void I_FreezeTimePolled(bool frozen); -static int I_GetTimeEventDriven(bool saveMS); -static int I_WaitForTicEvent(int prevtic); -static void I_FreezeTimeEventDriven(bool frozen); -static void CALLBACK TimerTicked(UINT id, UINT msg, DWORD_PTR user, DWORD_PTR dw1, DWORD_PTR dw2); - -static HCURSOR CreateCompatibleCursor(FTexture *cursorpic); -static HCURSOR CreateAlphaCursor(FTexture *cursorpic); -static HCURSOR CreateBitmapCursor(int xhot, int yhot, HBITMAP and_mask, HBITMAP color_mask); -static void DestroyCustomCursor(); - -// EXTERNAL DATA DECLARATIONS ---------------------------------------------- - -EXTERN_CVAR(String, language); -EXTERN_CVAR (Bool, queryiwad); - -extern HWND Window, ConWindow, GameTitleWindow; -extern HANDLE StdOut; -extern bool FancyStdOut; -extern HINSTANCE g_hInst; -extern FILE *Logfile; -extern bool NativeMouse; -extern bool ConWindowHidden; - -// PUBLIC DATA DEFINITIONS ------------------------------------------------- - -CVAR (String, queryiwad_key, "shift", CVAR_GLOBALCONFIG|CVAR_ARCHIVE); - -double PerfToSec, PerfToMillisec; -UINT TimerPeriod; -UINT TimerEventID; -UINT MillisecondsPerTic; -HANDLE NewTicArrived; -uint32 LanguageIDs[4]; - -int (*I_GetTime) (bool saveMS); -int (*I_WaitForTic) (int); -void (*I_FreezeTime) (bool frozen); - -os_t OSPlatform; -bool gameisdead; - -// PRIVATE DATA DEFINITIONS ------------------------------------------------ - -static ticcmd_t emptycmd; -static bool HasExited; - -static DWORD basetime = 0; -// These are for the polled timer. -static DWORD TicStart; -static DWORD TicNext; -static int TicFrozen; - -// These are for the event-driven timer. -static int tics; -static DWORD ted_start, ted_next; - -static WadStuff *WadList; -static int NumWads; -static int DefaultWad; - -static HCURSOR CustomCursor; - -// CODE -------------------------------------------------------------------- - -//========================================================================== -// -// I_Tactile -// -// Doom calls it when you take damage, so presumably it could be converted -// to something compatible with force feedback. -// -//========================================================================== - -void I_Tactile(int on, int off, int total) -{ - // UNUSED. - on = off = total = 0; -} - -//========================================================================== -// -// I_BaseTiccmd -// -// Returns an empty ticcmd. I have no idea why this should be system- -// specific. -// -//========================================================================== - -ticcmd_t *I_BaseTiccmd() -{ - return &emptycmd; -} - -// Stubs that select the timer to use and then call into it ---------------- - -//========================================================================== -// -// I_GetTimeSelect -// -//========================================================================== - -static int I_GetTimeSelect(bool saveMS) -{ - I_SelectTimer(); - return I_GetTime(saveMS); -} - -//========================================================================== -// -// I_WaitForTicSelect -// -//========================================================================== - -static int I_WaitForTicSelect(int prevtic) -{ - I_SelectTimer(); - return I_WaitForTic(prevtic); -} - -//========================================================================== -// -// I_SelectTimer -// -// Tries to create a timer event for efficent CPU use when the FPS is -// capped. Failing that, it sets things up for a polling timer instead. -// -//========================================================================== - -static void I_SelectTimer() -{ - assert(basetime == 0); - - // Use a timer event if possible. - NewTicArrived = CreateEvent(NULL, FALSE, FALSE, NULL); - if (NewTicArrived) - { - UINT delay; - const char *cmdDelay; - - cmdDelay = Args->CheckValue("-timerdelay"); - delay = 0; - if (cmdDelay != 0) - { - delay = atoi(cmdDelay); - } - if (delay == 0) - { - delay = 1000/TICRATE; - } - MillisecondsPerTic = delay; - TimerEventID = timeSetEvent(delay, 0, TimerTicked, 0, TIME_PERIODIC); - } - // Get the current time as the basetime. - basetime = timeGetTime(); - // Set timer functions. - if (TimerEventID != 0) - { - I_GetTime = I_GetTimeEventDriven; - I_WaitForTic = I_WaitForTicEvent; - I_FreezeTime = I_FreezeTimeEventDriven; - } - else - { - I_GetTime = I_GetTimePolled; - I_WaitForTic = I_WaitForTicPolled; - I_FreezeTime = I_FreezeTimePolled; - } -} - -//========================================================================== -// -// I_MSTime -// -// Returns the current time in milliseconds, where 0 is the first call -// to I_GetTime or I_WaitForTic. -// -//========================================================================== - -unsigned int I_MSTime() -{ - assert(basetime != 0); - return timeGetTime() - basetime; -} - -//========================================================================== -// -// I_FPSTime -// -// Returns the current system time in milliseconds. This is used by the FPS -// meter of DFrameBuffer::DrawRateStuff(). Since the screen can display -// before the play simulation is ready to begin, this needs to be -// separate from I_MSTime(). -// -//========================================================================== - -unsigned int I_FPSTime() -{ - return timeGetTime(); -} - -//========================================================================== -// -// I_GetTimePolled -// -// Returns the current time in tics. If saveMS is true, then calls to -// I_GetTimeFrac() will use this tic as 0 and the next tic as 1. -// -//========================================================================== - -static int I_GetTimePolled(bool saveMS) -{ - DWORD tm; - - if (TicFrozen != 0) - { - return TicFrozen; - } - - tm = timeGetTime(); - if (basetime == 0) - { - basetime = tm; - } - if (saveMS) - { - TicStart = tm; - TicNext = (tm * TICRATE / 1000 + 1) * 1000 / TICRATE; - } - - return ((tm-basetime)*TICRATE)/1000; -} - -//========================================================================== -// -// I_WaitForTicPolled -// -// Busy waits until the current tic is greater than prevtic. Time must not -// be frozen. -// -//========================================================================== - -static int I_WaitForTicPolled(int prevtic) -{ - int time; - - assert(TicFrozen == 0); - while ((time = I_GetTimePolled(false)) <= prevtic) - { } - - return time; -} - -//========================================================================== -// -// I_FreezeTimePolled -// -// Freeze/unfreeze the timer. -// -//========================================================================== - -static void I_FreezeTimePolled(bool frozen) -{ - if (frozen) - { - assert(TicFrozen == 0); - TicFrozen = I_GetTimePolled(false); - } - else - { - assert(TicFrozen != 0); - int froze = TicFrozen; - TicFrozen = 0; - int now = I_GetTimePolled(false); - basetime += (now - froze) * 1000 / TICRATE; - } -} - -//========================================================================== -// -// I_GetTimeEventDriven -// -// Returns the current tick counter. This is incremented asynchronously as -// the timer event fires. -// -//========================================================================== - -static int I_GetTimeEventDriven(bool saveMS) -{ - if (saveMS) - { - TicStart = ted_start; - TicNext = ted_next; - } - return tics; -} - -//========================================================================== -// -// I_WaitForTicEvent -// -// Waits on the timer event as long as the current tic is not later than -// prevtic. -// -//========================================================================== - -static int I_WaitForTicEvent(int prevtic) -{ - assert(!TicFrozen); - while (prevtic >= tics) - { - WaitForSingleObject(NewTicArrived, 1000/TICRATE); - } - return tics; -} - -//========================================================================== -// -// I_FreezeTimeEventDriven -// -// Freeze/unfreeze the ticker. -// -//========================================================================== - -static void I_FreezeTimeEventDriven(bool frozen) -{ - TicFrozen = frozen; -} - -//========================================================================== -// -// TimerTicked -// -// Advance the tick count and signal the NewTicArrived event. -// -//========================================================================== - -static void CALLBACK TimerTicked(UINT id, UINT msg, DWORD_PTR user, DWORD_PTR dw1, DWORD_PTR dw2) -{ - if (!TicFrozen) - { - tics++; - } - ted_start = timeGetTime (); - ted_next = ted_start + MillisecondsPerTic; - SetEvent(NewTicArrived); -} - -//========================================================================== -// -// I_GetTimeFrac -// -// Returns the fractional amount of a tic passed since the most recently -// saved tic. -// -//========================================================================== - -fixed_t I_GetTimeFrac(uint32 *ms) -{ - DWORD now = timeGetTime(); - if (ms != NULL) - { - *ms = TicNext; - } - DWORD step = TicNext - TicStart; - if (step == 0) - { - return FRACUNIT; - } - else - { - fixed_t frac = clamp ((now - TicStart)*FRACUNIT/step, 0, FRACUNIT); - return frac; - } -} - -//========================================================================== -// -// I_WaitVBL -// -// I_WaitVBL is never used to actually synchronize to the vertical blank. -// Instead, it's used for delay purposes. Doom used a 70 Hz display mode, -// so that's what we use to determine how long to wait for. -// -//========================================================================== - -void I_WaitVBL(int count) -{ - Sleep(1000 * count / 70); -} - -//========================================================================== -// -// I_DetectOS -// -// Determine which version of Windows the game is running on. -// -//========================================================================== - -void I_DetectOS(void) -{ - OSVERSIONINFOEX info; - const char *osname; - - info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); - if (!GetVersionEx((OSVERSIONINFO *)&info)) - { - // Retry with the older OSVERSIONINFO structure. - info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - GetVersionEx((OSVERSIONINFO *)&info); - } - - switch (info.dwPlatformId) - { - case VER_PLATFORM_WIN32_WINDOWS: - OSPlatform = os_Win95; - if (info.dwMinorVersion < 10) - { - osname = "95"; - } - else if (info.dwMinorVersion < 90) - { - osname = "98"; - } - else - { - osname = "Me"; - } - break; - - case VER_PLATFORM_WIN32_NT: - OSPlatform = info.dwMajorVersion < 5 ? os_WinNT4 : os_Win2k; - osname = "NT"; - if (info.dwMajorVersion == 5) - { - if (info.dwMinorVersion == 0) - { - osname = "2000"; - } - if (info.dwMinorVersion == 1) - { - osname = "XP"; - } - else if (info.dwMinorVersion == 2) - { - osname = "Server 2003"; - } - } - else if (info.dwMajorVersion == 6) - { - if (info.dwMinorVersion == 0) - { - osname = (info.wProductType == VER_NT_WORKSTATION) ? "Vista" : "Server 2008"; - } - else if (info.dwMinorVersion == 1) - { - osname = (info.wProductType == VER_NT_WORKSTATION) ? "7" : "Server 2008 R2"; - } - else if (info.dwMinorVersion == 2) - { - // Starting with Windows 8.1, you need to specify in your manifest - // the highest version of Windows you support, which will also be the - // highest version of Windows this function returns. - osname = (info.wProductType == VER_NT_WORKSTATION) ? "8" : "Server 2012"; - } - else if (info.dwMinorVersion == 3) - { - osname = (info.wProductType == VER_NT_WORKSTATION) ? "8.1" : "Server 2012 R2"; - } - else if (info.dwMinorVersion == 4) - { - osname = (info.wProductType == VER_NT_WORKSTATION) ? "10 (or higher)" : "Server 10 (or higher)"; - } - } - break; - - default: - OSPlatform = os_unknown; - osname = "Unknown OS"; - break; - } - - if (OSPlatform == os_Win95) - { - Printf ("OS: Windows %s %lu.%lu.%lu %s\n", - osname, - info.dwMajorVersion, info.dwMinorVersion, - info.dwBuildNumber & 0xffff, info.szCSDVersion); - } - else - { - Printf ("OS: Windows %s (NT %lu.%lu) Build %lu\n %s\n", - osname, - info.dwMajorVersion, info.dwMinorVersion, - info.dwBuildNumber, info.szCSDVersion); - } - - if (OSPlatform == os_unknown) - { - Printf ("(Assuming Windows 2000)\n"); - OSPlatform = os_Win2k; - } -} - -//========================================================================== -// -// SubsetLanguageIDs -// -// Helper function for SetLanguageIDs. -// -//========================================================================== - -static void SubsetLanguageIDs(LCID id, LCTYPE type, int idx) -{ - char buf[8]; - LCID langid; - char *idp; - - if (!GetLocaleInfo(id, type, buf, 8)) - return; - langid = MAKELCID(strtoul(buf, NULL, 16), SORT_DEFAULT); - if (!GetLocaleInfo(langid, LOCALE_SABBREVLANGNAME, buf, 8)) - return; - idp = (char *)(&LanguageIDs[idx]); - memset (idp, 0, 4); - idp[0] = tolower(buf[0]); - idp[1] = tolower(buf[1]); - idp[2] = tolower(buf[2]); - idp[3] = 0; -} - -//========================================================================== -// -// SetLanguageIDs -// -//========================================================================== - -void SetLanguageIDs() -{ - size_t langlen = strlen(language); - - if (langlen < 2 || langlen > 3) - { - memset(LanguageIDs, 0, sizeof(LanguageIDs)); - SubsetLanguageIDs(LOCALE_USER_DEFAULT, LOCALE_ILANGUAGE, 0); - SubsetLanguageIDs(LOCALE_USER_DEFAULT, LOCALE_IDEFAULTLANGUAGE, 1); - SubsetLanguageIDs(LOCALE_SYSTEM_DEFAULT, LOCALE_ILANGUAGE, 2); - SubsetLanguageIDs(LOCALE_SYSTEM_DEFAULT, LOCALE_IDEFAULTLANGUAGE, 3); - } - else - { - DWORD lang = 0; - - ((BYTE *)&lang)[0] = (language)[0]; - ((BYTE *)&lang)[1] = (language)[1]; - ((BYTE *)&lang)[2] = (language)[2]; - LanguageIDs[0] = lang; - LanguageIDs[1] = lang; - LanguageIDs[2] = lang; - LanguageIDs[3] = lang; - } -} - -//========================================================================== -// -// CalculateCPUSpeed -// -// Make a decent guess at how much time elapses between TSC steps. This can -// vary over runtime depending on power management settings, so should not -// be used anywhere that truely accurate timing actually matters. -// -//========================================================================== - -void CalculateCPUSpeed() -{ - LARGE_INTEGER freq; - - QueryPerformanceFrequency (&freq); - - if (freq.QuadPart != 0 && CPU.bRDTSC) - { - LARGE_INTEGER count1, count2; - cycle_t ClockCalibration; - DWORD min_diff; - - ClockCalibration.Reset(); - - // Count cycles for at least 55 milliseconds. - // The performance counter may be very low resolution compared to CPU - // speeds today, so the longer we count, the more accurate our estimate. - // On the other hand, we don't want to count too long, because we don't - // want the user to notice us spend time here, since most users will - // probably never use the performance statistics. - min_diff = freq.LowPart * 11 / 200; - - // Minimize the chance of task switching during the testing by going very - // high priority. This is another reason to avoid timing for too long. - SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS); - SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); - - // Make sure we start timing on a counter boundary. - QueryPerformanceCounter(&count1); - do { QueryPerformanceCounter(&count2); } while (count1.QuadPart == count2.QuadPart); - - // Do the timing loop. - ClockCalibration.Clock(); - do { QueryPerformanceCounter(&count1); } while ((count1.QuadPart - count2.QuadPart) < min_diff); - ClockCalibration.Unclock(); - - SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); - SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); - - PerfToSec = double(count1.QuadPart - count2.QuadPart) / (double(ClockCalibration.GetRawCounter()) * freq.QuadPart); - PerfToMillisec = PerfToSec * 1000.0; - } - - Printf ("CPU Speed: %.0f MHz\n", 0.001 / PerfToMillisec); -} - -//========================================================================== -// -// I_Init -// -//========================================================================== - -void I_Init() -{ - CheckCPUID(&CPU); - CalculateCPUSpeed(); - DumpCPUInfo(&CPU); - - I_GetTime = I_GetTimeSelect; - I_WaitForTic = I_WaitForTicSelect; - - atterm (I_ShutdownSound); - I_InitSound (); -} - -//========================================================================== -// -// I_Quit -// -//========================================================================== - -void I_Quit() -{ - HasExited = true; /* Prevent infinitely recursive exits -- killough */ - - if (TimerEventID != 0) - { - timeKillEvent(TimerEventID); - } - if (NewTicArrived != NULL) - { - CloseHandle(NewTicArrived); - } - timeEndPeriod(TimerPeriod); - if (demorecording) - { - G_CheckDemoStatus(); - } - - C_DeinitConsole(); -} - - -//========================================================================== -// -// I_FatalError -// -// Throw an error that will end the game. -// -//========================================================================== - -void STACK_ARGS I_FatalError(const char *error, ...) -{ - static BOOL alreadyThrown = false; - gameisdead = true; - - if (!alreadyThrown) // ignore all but the first message -- killough - { - alreadyThrown = true; - char errortext[MAX_ERRORTEXT]; - va_list argptr; - va_start(argptr, error); - myvsnprintf(errortext, MAX_ERRORTEXT, error, argptr); - va_end(argptr); - - // Record error to log (if logging) - if (Logfile) - { - fprintf(Logfile, "\n**** DIED WITH FATAL ERROR:\n%s\n", errortext); - fflush(Logfile); - } - - throw CFatalError(errortext); - } - - if (!HasExited) // If it hasn't exited yet, exit now -- killough - { - HasExited = 1; // Prevent infinitely recursive exits -- killough - exit(-1); - } -} - -//========================================================================== -// -// I_Error -// -// Throw an error that will send us to the console if we are far enough -// along in the startup process. -// -//========================================================================== - -void STACK_ARGS I_Error(const char *error, ...) -{ - va_list argptr; - char errortext[MAX_ERRORTEXT]; - - va_start(argptr, error); - myvsnprintf(errortext, MAX_ERRORTEXT, error, argptr); - va_end(argptr); - - throw CRecoverableError(errortext); -} - -//========================================================================== -// -// ToEditControl -// -// Converts string to Unicode and inserts it into the control. -// -//========================================================================== - -void ToEditControl(HWND edit, const char *buf, wchar_t *wbuf, int bpos) -{ - // Let's just do this ourself. It's not hard, and we can compensate for - // special console characters at the same time. -#if 0 - MultiByteToWideChar(1252 /* Western */, 0, buf, bpos, wbuf, countof(wbuf)); - wbuf[bpos] = 0; -#else - static wchar_t notlatin1[32] = // code points 0x80-0x9F - { - 0x20AC, // Euro sign - 0x0081, // Undefined - 0x201A, // Single low-9 quotation mark - 0x0192, // Latin small letter f with hook - 0x201E, // Double low-9 quotation mark - 0x2026, // Horizontal ellipsis - 0x2020, // Dagger - 0x2021, // Double dagger - 0x02C6, // Modifier letter circumflex accent - 0x2030, // Per mille sign - 0x0160, // Latin capital letter S with caron - 0x2039, // Single left-pointing angle quotation mark - 0x0152, // Latin capital ligature OE - 0x008D, // Undefined - 0x017D, // Latin capital letter Z with caron - 0x008F, // Undefined - 0x0090, // Undefined - 0x2018, // Left single quotation mark - 0x2019, // Right single quotation mark - 0x201C, // Left double quotation mark - 0x201D, // Right double quotation mark - 0x2022, // Bullet - 0x2013, // En dash - 0x2014, // Em dash - 0x02DC, // Small tilde - 0x2122, // Trade mark sign - 0x0161, // Latin small letter s with caron - 0x203A, // Single right-pointing angle quotation mark - 0x0153, // Latin small ligature oe - 0x009D, // Undefined - 0x017E, // Latin small letter z with caron - 0x0178 // Latin capital letter Y with diaeresis - }; - for (int i = 0; i <= bpos; ++i) - { - wchar_t code = (BYTE)buf[i]; - if (code >= 0x1D && code <= 0x1F) - { // The bar characters, most commonly used to indicate map changes - code = 0x2550; // Box Drawings Double Horizontal - } - else if (code >= 0x80 && code <= 0x9F) - { - code = notlatin1[code - 0x80]; - } - wbuf[i] = code; - } -#endif - SendMessageW(edit, EM_REPLACESEL, FALSE, (LPARAM)wbuf); -} - -//========================================================================== -// -// I_PrintStr -// -// Send output to the list box shown during startup (and hidden during -// gameplay). -// -//========================================================================== - -static void DoPrintStr(const char *cp, HWND edit, HANDLE StdOut) -{ - if (edit == NULL && StdOut == NULL) - return; - - char buf[256]; - wchar_t wbuf[countof(buf)]; - int bpos = 0; - CHARRANGE selection; - CHARRANGE endselection; - LONG lines_before = 0, lines_after; - CHARFORMAT format; - - if (edit != NULL) - { - // Store the current selection and set it to the end so we can append text. - SendMessage(edit, EM_EXGETSEL, 0, (LPARAM)&selection); - endselection.cpMax = endselection.cpMin = GetWindowTextLength(edit); - SendMessage(edit, EM_EXSETSEL, 0, (LPARAM)&endselection); - - // GetWindowTextLength and EM_EXSETSEL can disagree on where the end of - // the text is. Find out what EM_EXSETSEL thought it was and use that later. - SendMessage(edit, EM_EXGETSEL, 0, (LPARAM)&endselection); - - // Remember how many lines there were before we added text. - lines_before = (LONG)SendMessage(edit, EM_GETLINECOUNT, 0, 0); - } - - while (*cp != 0) - { - // 28 is the escape code for a color change. - if ((*cp == 28 && bpos != 0) || bpos == 255) - { - buf[bpos] = 0; - if (edit != NULL) - { - ToEditControl(edit, buf, wbuf, bpos); - } - if (StdOut != NULL) - { - DWORD bytes_written; - WriteFile(StdOut, buf, bpos, &bytes_written, NULL); - } - bpos = 0; - } - if (*cp != 28) - { - buf[bpos++] = *cp++; - } - else - { - const BYTE *color_id = (const BYTE *)cp + 1; - EColorRange range = V_ParseFontColor(color_id, CR_UNTRANSLATED, CR_YELLOW); - cp = (const char *)color_id; - - if (range != CR_UNDEFINED) - { - // Change the color of future text added to the control. - PalEntry color = V_LogColorFromColorRange(range); - if (StdOut != NULL && FancyStdOut) - { - // Unfortunately, we are pretty limited here: There are only - // eight basic colors, and each comes in a dark and a bright - // variety. - float h, s, v, r, g, b; - WORD attrib = 0; - - RGBtoHSV(color.r / 255.f, color.g / 255.f, color.b / 255.f, &h, &s, &v); - if (s != 0) - { // color - HSVtoRGB(&r, &g, &b, h, 1, 1); - if (r == 1) attrib = FOREGROUND_RED; - if (g == 1) attrib |= FOREGROUND_GREEN; - if (b == 1) attrib |= FOREGROUND_BLUE; - if (v > 0.6) attrib |= FOREGROUND_INTENSITY; - } - else - { // gray - if (v < 0.33) attrib = FOREGROUND_INTENSITY; - else if (v < 0.90) attrib = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; - else attrib = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY; - } - SetConsoleTextAttribute(StdOut, attrib); - } - if (edit != NULL) - { - // GDI uses BGR colors, but color is RGB, so swap the R and the B. - swapvalues(color.r, color.b); - // Change the color. - format.cbSize = sizeof(format); - format.dwMask = CFM_COLOR; - format.dwEffects = 0; - format.crTextColor = color; - SendMessage(edit, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&format); - } - } - } - } - if (bpos != 0) - { - buf[bpos] = 0; - if (edit != NULL) - { - ToEditControl(edit, buf, wbuf, bpos); - } - if (StdOut != NULL) - { - DWORD bytes_written; - WriteFile(StdOut, buf, bpos, &bytes_written, NULL); - } - } - - if (edit != NULL) - { - // If the old selection was at the end of the text, keep it at the end and - // scroll. Don't scroll if the selection is anywhere else. - if (selection.cpMin == endselection.cpMin && selection.cpMax == endselection.cpMax) - { - selection.cpMax = selection.cpMin = GetWindowTextLength (edit); - lines_after = (LONG)SendMessage(edit, EM_GETLINECOUNT, 0, 0); - if (lines_after > lines_before) - { - SendMessage(edit, EM_LINESCROLL, 0, lines_after - lines_before); - } - } - // Restore the previous selection. - SendMessage(edit, EM_EXSETSEL, 0, (LPARAM)&selection); - // Give the edit control a chance to redraw itself. - I_GetEvent(); - } - if (StdOut != NULL && FancyStdOut) - { // Set text back to gray, in case it was changed. - SetConsoleTextAttribute(StdOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); - } -} - -static TArray bufferedConsoleStuff; - -void I_PrintStr(const char *cp) -{ - if (ConWindowHidden) - { - bufferedConsoleStuff.Push(cp); - DoPrintStr(cp, NULL, StdOut); - } - else - { - DoPrintStr(cp, ConWindow, StdOut); - } -} - -void I_FlushBufferedConsoleStuff() -{ - for (unsigned i = 0; i < bufferedConsoleStuff.Size(); i++) - { - DoPrintStr(bufferedConsoleStuff[i], ConWindow, NULL); - } - bufferedConsoleStuff.Clear(); -} - -//========================================================================== -// -// SetQueryIWAD -// -// The user had the "Don't ask again" box checked when they closed the -// IWAD selection dialog. -// -//========================================================================== - -static void SetQueryIWad(HWND dialog) -{ - HWND checkbox = GetDlgItem(dialog, IDC_DONTASKIWAD); - int state = (int)SendMessage(checkbox, BM_GETCHECK, 0, 0); - bool query = (state != BST_CHECKED); - - if (!query && queryiwad) - { - MessageBox(dialog, - "You have chosen not to show this dialog box in the future.\n" - "If you wish to see it again, hold down SHIFT while starting " GAMENAME ".", - "Don't ask me this again", - MB_OK | MB_ICONINFORMATION); - } - - queryiwad = query; -} - -//========================================================================== -// -// IWADBoxCallback -// -// Dialog proc for the IWAD selector. -// -//========================================================================== - -BOOL CALLBACK IWADBoxCallback(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) -{ - HWND ctrl; - int i; - - switch (message) - { - case WM_INITDIALOG: - // Add our program name to the window title - { - TCHAR label[256]; - FString newlabel; - - GetWindowText(hDlg, label, countof(label)); - newlabel.Format(GAMESIG " %s: %s", GetVersionString(), label); - SetWindowText(hDlg, newlabel.GetChars()); - } - // Populate the list with all the IWADs found - ctrl = GetDlgItem(hDlg, IDC_IWADLIST); - for (i = 0; i < NumWads; i++) - { - FString work; - const char *filepart = strrchr(WadList[i].Path, '/'); - if (filepart == NULL) - filepart = WadList[i].Path; - else - filepart++; - work.Format("%s (%s)", WadList[i].Name.GetChars(), filepart); - SendMessage(ctrl, LB_ADDSTRING, 0, (LPARAM)work.GetChars()); - SendMessage(ctrl, LB_SETITEMDATA, i, (LPARAM)i); - } - SendMessage(ctrl, LB_SETCURSEL, DefaultWad, 0); - SetFocus(ctrl); - // Set the state of the "Don't ask me again" checkbox - ctrl = GetDlgItem(hDlg, IDC_DONTASKIWAD); - SendMessage(ctrl, BM_SETCHECK, queryiwad ? BST_UNCHECKED : BST_CHECKED, 0); - // Make sure the dialog is in front. If SHIFT was pressed to force it visible, - // then the other window will normally be on top. - SetForegroundWindow(hDlg); - break; - - case WM_COMMAND: - if (LOWORD(wParam) == IDCANCEL) - { - EndDialog (hDlg, -1); - } - else if (LOWORD(wParam) == IDOK || - (LOWORD(wParam) == IDC_IWADLIST && HIWORD(wParam) == LBN_DBLCLK)) - { - SetQueryIWad(hDlg); - ctrl = GetDlgItem (hDlg, IDC_IWADLIST); - EndDialog(hDlg, SendMessage (ctrl, LB_GETCURSEL, 0, 0)); - } - break; - } - return FALSE; -} - -//========================================================================== -// -// I_PickIWad -// -// Open a dialog to pick the IWAD, if there is more than one found. -// -//========================================================================== - -int I_PickIWad(WadStuff *wads, int numwads, bool showwin, int defaultiwad) -{ - int vkey; - - if (stricmp(queryiwad_key, "shift") == 0) - { - vkey = VK_SHIFT; - } - else if (stricmp(queryiwad_key, "control") == 0 || stricmp (queryiwad_key, "ctrl") == 0) - { - vkey = VK_CONTROL; - } - else - { - vkey = 0; - } - if (showwin || (vkey != 0 && GetAsyncKeyState(vkey))) - { - WadList = wads; - NumWads = numwads; - DefaultWad = defaultiwad; - - return (int)DialogBox(g_hInst, MAKEINTRESOURCE(IDD_IWADDIALOG), - (HWND)Window, (DLGPROC)IWADBoxCallback); - } - return defaultiwad; -} - -//========================================================================== -// -// I_SetCursor -// -// Returns true if the cursor was successfully changed. -// -//========================================================================== - -bool I_SetCursor(FTexture *cursorpic) -{ - HCURSOR cursor; - - if (cursorpic != NULL && cursorpic->UseType != FTexture::TEX_Null && - (screen == NULL || !screen->Is8BitMode())) - { - // Must be no larger than 32x32. - if (cursorpic->GetWidth() > 32 || cursorpic->GetHeight() > 32) - { - return false; - } - - cursor = CreateAlphaCursor(cursorpic); - if (cursor == NULL) - { - cursor = CreateCompatibleCursor(cursorpic); - } - if (cursor == NULL) - { - return false; - } - // Replace the existing cursor with the new one. - DestroyCustomCursor(); - CustomCursor = cursor; - atterm(DestroyCustomCursor); - } - else - { - DestroyCustomCursor(); - cursor = LoadCursor(NULL, IDC_ARROW); - } - SetClassLongPtr(Window, GCLP_HCURSOR, (LONG_PTR)cursor); - if (NativeMouse) - { - POINT pt; - RECT client; - - // If the mouse pointer is within the window's client rect, set it now. - if (GetCursorPos(&pt) && GetClientRect(Window, &client) && - ClientToScreen(Window, (LPPOINT)&client.left) && - ClientToScreen(Window, (LPPOINT)&client.right)) - { - if (pt.x >= client.left && pt.x < client.right && - pt.y >= client.top && pt.y < client.bottom) - { - SetCursor(cursor); - } - } - } - return true; -} - -//========================================================================== -// -// CreateCompatibleCursor -// -// Creates a cursor with a 1-bit alpha channel. -// -//========================================================================== - -static HCURSOR CreateCompatibleCursor(FTexture *cursorpic) -{ - int picwidth = cursorpic->GetWidth(); - int picheight = cursorpic->GetHeight(); - - // Create bitmap masks for the cursor from the texture. - HDC dc = GetDC(NULL); - if (dc == NULL) - { - return false; - } - HDC and_mask_dc = CreateCompatibleDC(dc); - HDC xor_mask_dc = CreateCompatibleDC(dc); - HBITMAP and_mask = CreateCompatibleBitmap(dc, 32, 32); - HBITMAP xor_mask = CreateCompatibleBitmap(dc, 32, 32); - ReleaseDC(NULL, dc); - - SelectObject(and_mask_dc, and_mask); - SelectObject(xor_mask_dc, xor_mask); - - // Initialize with an invisible cursor. - SelectObject(and_mask_dc, GetStockObject(WHITE_PEN)); - SelectObject(and_mask_dc, GetStockObject(WHITE_BRUSH)); - Rectangle(and_mask_dc, 0, 0, 32, 32); - SelectObject(xor_mask_dc, GetStockObject(BLACK_PEN)); - SelectObject(xor_mask_dc, GetStockObject(BLACK_BRUSH)); - Rectangle(xor_mask_dc, 0, 0, 32, 32); - - FBitmap bmp; - const BYTE *pixels; - - bmp.Create(picwidth, picheight); - cursorpic->CopyTrueColorPixels(&bmp, 0, 0); - pixels = bmp.GetPixels(); - - // Copy color data from the source texture to the cursor bitmaps. - for (int y = 0; y < picheight; ++y) - { - for (int x = 0; x < picwidth; ++x) - { - const BYTE *bgra = &pixels[x*4 + y*bmp.GetPitch()]; - if (bgra[3] != 0) - { - SetPixelV(and_mask_dc, x, y, RGB(0,0,0)); - SetPixelV(xor_mask_dc, x, y, RGB(bgra[2], bgra[1], bgra[0])); - } - } - } - DeleteDC(and_mask_dc); - DeleteDC(xor_mask_dc); - - // Create the cursor from the bitmaps. - return CreateBitmapCursor(cursorpic->LeftOffset, cursorpic->TopOffset, and_mask, xor_mask); -} - -//========================================================================== -// -// CreateAlphaCursor -// -// Creates a cursor with a full alpha channel. -// -//========================================================================== - -static HCURSOR CreateAlphaCursor(FTexture *cursorpic) -{ - HDC dc; - BITMAPV5HEADER bi; - HBITMAP color, mono; - void *bits; - - memset(&bi, 0, sizeof(bi)); - bi.bV5Size = sizeof(bi); - bi.bV5Width = 32; - bi.bV5Height = 32; - bi.bV5Planes = 1; - bi.bV5BitCount = 32; - bi.bV5Compression = BI_BITFIELDS; - bi.bV5RedMask = 0x00FF0000; - bi.bV5GreenMask = 0x0000FF00; - bi.bV5BlueMask = 0x000000FF; - bi.bV5AlphaMask = 0xFF000000; - - dc = GetDC(NULL); - if (dc == NULL) - { - return NULL; - } - - // Create the DIB section with an alpha channel. - color = CreateDIBSection(dc, (BITMAPINFO *)&bi, DIB_RGB_COLORS, &bits, NULL, 0); - ReleaseDC(NULL, dc); - - if (color == NULL) - { - return NULL; - } - - // Create an empty mask bitmap, since CreateIconIndirect requires this. - mono = CreateBitmap(32, 32, 1, 1, NULL); - if (mono == NULL) - { - DeleteObject(color); - return NULL; - } - - // Copy cursor to the color bitmap. Note that GDI bitmaps are upside down compared - // to normal conventions, so we create the FBitmap pointing at the last row and use - // a negative pitch so that CopyTrueColorPixels will use GDI's orientation. - FBitmap bmp((BYTE *)bits + 31*32*4, -32*4, 32, 32); - cursorpic->CopyTrueColorPixels(&bmp, 0, 0); - - return CreateBitmapCursor(cursorpic->LeftOffset, cursorpic->TopOffset, mono, color); -} - -//========================================================================== -// -// CreateBitmapCursor -// -// Create the cursor from the bitmaps. Deletes the bitmaps before returning. -// -//========================================================================== - -static HCURSOR CreateBitmapCursor(int xhot, int yhot, HBITMAP and_mask, HBITMAP color_mask) -{ - ICONINFO iconinfo = - { - FALSE, // fIcon - xhot, // xHotspot - yhot, // yHotspot - and_mask, // hbmMask - color_mask // hbmColor - }; - HCURSOR cursor = CreateIconIndirect(&iconinfo); - - // Delete the bitmaps. - DeleteObject(and_mask); - DeleteObject(color_mask); - - return cursor; -} - -//========================================================================== -// -// DestroyCustomCursor -// -//========================================================================== - -static void DestroyCustomCursor() -{ - if (CustomCursor != NULL) - { - DestroyCursor(CustomCursor); - CustomCursor = NULL; - } -} - -//========================================================================== -// -// I_WriteIniFailed -// -// Display a message when the config failed to save. -// -//========================================================================== - -bool I_WriteIniFailed() -{ - char *lpMsgBuf; - FString errortext; - - FormatMessageA (FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - GetLastError(), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language - (LPSTR)&lpMsgBuf, - 0, - NULL - ); - errortext.Format ("The config file %s could not be written:\n%s", GameConfig->GetPathName(), lpMsgBuf); - LocalFree (lpMsgBuf); - return MessageBox(Window, errortext.GetChars(), GAMENAME " configuration not saved", MB_ICONEXCLAMATION | MB_RETRYCANCEL) == IDRETRY; -} - -//========================================================================== -// -// I_FindFirst -// -// Start a pattern matching sequence. -// -//========================================================================== - -void *I_FindFirst(const char *filespec, findstate_t *fileinfo) -{ - return FindFirstFileA(filespec, (LPWIN32_FIND_DATAA)fileinfo); -} - -//========================================================================== -// -// I_FindNext -// -// Return the next file in a pattern matching sequence. -// -//========================================================================== - -int I_FindNext(void *handle, findstate_t *fileinfo) -{ - return !FindNextFileA((HANDLE)handle, (LPWIN32_FIND_DATAA)fileinfo); -} - -//========================================================================== -// -// I_FindClose -// -// Finish a pattern matching sequence. -// -//========================================================================== - -int I_FindClose(void *handle) -{ - return FindClose((HANDLE)handle); -} - -//========================================================================== -// -// QueryPathKey -// -// Returns the value of a registry key into the output variable value. -// -//========================================================================== - -static bool QueryPathKey(HKEY key, const char *keypath, const char *valname, FString &value) -{ - HKEY pathkey; - DWORD pathtype; - DWORD pathlen; - LONG res; - - if(ERROR_SUCCESS == RegOpenKeyEx(key, keypath, 0, KEY_QUERY_VALUE, &pathkey)) - { - if (ERROR_SUCCESS == RegQueryValueEx(pathkey, valname, 0, &pathtype, NULL, &pathlen) && - pathtype == REG_SZ && pathlen != 0) - { - // Don't include terminating null in count - char *chars = value.LockNewBuffer(pathlen - 1); - res = RegQueryValueEx(pathkey, valname, 0, NULL, (LPBYTE)chars, &pathlen); - value.UnlockBuffer(); - if (res != ERROR_SUCCESS) - { - value = ""; - } - } - RegCloseKey(pathkey); - } - return value.IsNotEmpty(); -} - -//========================================================================== -// -// I_GetGogPaths -// -// Check the registry for GOG installation paths, so we can search for IWADs -// that were bought from GOG.com. This is a bit different from the Steam -// version because each game has its own independent installation path, no -// such thing as /SteamApps/common/. -// -//========================================================================== - -TArray I_GetGogPaths() -{ - TArray result; - FString path; - FString gamepath; - -#ifdef _WIN64 - FString gogregistrypath = "Software\\Wow6432Node\\GOG.com\\Games"; -#else - // If a 32-bit ZDoom runs on a 64-bit Windows, this will be transparently and - // automatically redirected to the Wow6432Node address instead, so this address - // should be safe to use in all cases. - FString gogregistrypath = "Software\\GOG.com\\Games"; -#endif - - // Look for Ultimate Doom - gamepath = gogregistrypath + "\\1435827232"; - if (QueryPathKey(HKEY_LOCAL_MACHINE, gamepath.GetChars(), "Path", path)) - { - result.Push(path); // directly in install folder - } - - // Look for Doom II - gamepath = gogregistrypath + "\\1435848814"; - if (QueryPathKey(HKEY_LOCAL_MACHINE, gamepath.GetChars(), "Path", path)) - { - result.Push(path + "/doom2"); // in a subdirectory - // If direct support for the Master Levels is ever added, they are in path + /master/wads - } - - // Look for Final Doom - gamepath = gogregistrypath + "\\1435848742"; - if (QueryPathKey(HKEY_LOCAL_MACHINE, gamepath.GetChars(), "Path", path)) - { - // in subdirectories - result.Push(path + "/TNT"); - result.Push(path + "/Plutonia"); - } - - return result; -} - -//========================================================================== -// -// I_GetSteamPath -// -// Check the registry for the path to Steam, so that we can search for -// IWADs that were bought with Steam. -// -//========================================================================== - -TArray I_GetSteamPath() -{ - TArray result; - static const char *const steam_dirs[] = - { - "doom 2/base", - "final doom/base", - "heretic shadow of the serpent riders/base", - "hexen/base", - "hexen deathkings of the dark citadel/base", - "ultimate doom/base", - "DOOM 3 BFG Edition/base/wads", - "Strife" - }; - - FString path; - - if (!QueryPathKey(HKEY_CURRENT_USER, "Software\\Valve\\Steam", "SteamPath", path)) - { - if (!QueryPathKey(HKEY_LOCAL_MACHINE, "Software\\Valve\\Steam", "InstallPath", path)) - return result; - } - path += "/SteamApps/common/"; - - for(unsigned int i = 0; i < countof(steam_dirs); ++i) - { - result.Push(path + steam_dirs[i]); - } - - return result; -} - -//========================================================================== -// -// I_MakeRNGSeed -// -// Returns a 32-bit random seed, preferably one with lots of entropy. -// -//========================================================================== - -unsigned int I_MakeRNGSeed() -{ - unsigned int seed; - - // If RtlGenRandom is available, use that to avoid increasing the - // working set by pulling in all of the crytographic API. - HMODULE advapi = GetModuleHandle("advapi32.dll"); - if (advapi != NULL) - { - BOOLEAN (APIENTRY *RtlGenRandom)(void *, ULONG) = - (BOOLEAN (APIENTRY *)(void *, ULONG))GetProcAddress(advapi, "SystemFunction036"); - if (RtlGenRandom != NULL) - { - if (RtlGenRandom(&seed, sizeof(seed))) - { - return seed; - } - } - } - - // Use the full crytographic API to produce a seed. If that fails, - // time() is used as a fallback. - HCRYPTPROV prov; - - if (!CryptAcquireContext(&prov, NULL, MS_DEF_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) - { - return (unsigned int)time(NULL); - } - if (!CryptGenRandom(prov, sizeof(seed), (BYTE *)&seed)) - { - seed = (unsigned int)time(NULL); - } - CryptReleaseContext(prov, 0); - return seed; -} - -//========================================================================== -// -// I_GetLongPathName -// -// Returns the long version of the path, or the original if there isn't -// anything worth changing. -// -//========================================================================== - -FString I_GetLongPathName(FString shortpath) -{ - static TOptWin32Proc - GetLongPathNameA("kernel32.dll", "GetLongPathNameA"); - - // Doesn't exist on NT4 - if (GetLongPathName == NULL) - return shortpath; - - DWORD buffsize = GetLongPathNameA.Call(shortpath.GetChars(), NULL, 0); - if (buffsize == 0) - { // nothing to change (it doesn't exist, maybe?) - return shortpath; - } - TCHAR *buff = new TCHAR[buffsize]; - DWORD buffsize2 = GetLongPathNameA.Call(shortpath.GetChars(), buff, buffsize); - if (buffsize2 >= buffsize) - { // Failure! Just return the short path - delete[] buff; - return shortpath; - } - FString longpath(buff, buffsize2); - delete[] buff; - return longpath; -} +/* +** i_system.cpp +** Timers, pre-console output, IWAD selection, and misc system routines. +** +**--------------------------------------------------------------------------- +** 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 ------------------------------------------------------------ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include + +#define USE_WINDOWS_DWORD +#include "hardware.h" +#include "doomerrors.h" +#include + +#include "doomtype.h" +#include "version.h" +#include "doomdef.h" +#include "cmdlib.h" +#include "m_argv.h" +#include "m_misc.h" +#include "i_video.h" +#include "i_sound.h" +#include "i_music.h" +#include "resource.h" +#include "x86.h" +#include "stats.h" + +#include "d_main.h" +#include "d_net.h" +#include "g_game.h" +#include "i_input.h" +#include "i_system.h" +#include "c_dispatch.h" +#include "templates.h" +#include "gameconfigfile.h" +#include "v_font.h" +#include "g_level.h" +#include "doomstat.h" +#include "v_palette.h" +#include "stats.h" +#include "textures/bitmap.h" +#include "textures/textures.h" + +// MACROS ------------------------------------------------------------------ + +#ifdef _MSC_VER +// Turn off "conversion from 'LONG_PTR' to 'LONG', possible loss of data" +// generated by SetClassLongPtr(). +#pragma warning(disable:4244) +#endif + +// TYPES ------------------------------------------------------------------- + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +extern void CheckCPUID(CPUInfo *cpu); +extern void LayoutMainWindow(HWND hWnd, HWND pane); + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +static void CalculateCPUSpeed(); +static void I_SelectTimer(); + +static int I_GetTimePolled(bool saveMS); +static int I_WaitForTicPolled(int prevtic); +static void I_FreezeTimePolled(bool frozen); +static int I_GetTimeEventDriven(bool saveMS); +static int I_WaitForTicEvent(int prevtic); +static void I_FreezeTimeEventDriven(bool frozen); +static void CALLBACK TimerTicked(UINT id, UINT msg, DWORD_PTR user, DWORD_PTR dw1, DWORD_PTR dw2); + +static HCURSOR CreateCompatibleCursor(FTexture *cursorpic); +static HCURSOR CreateAlphaCursor(FTexture *cursorpic); +static HCURSOR CreateBitmapCursor(int xhot, int yhot, HBITMAP and_mask, HBITMAP color_mask); +static void DestroyCustomCursor(); + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +EXTERN_CVAR(String, language); +EXTERN_CVAR (Bool, queryiwad); + +extern HWND Window, ConWindow, GameTitleWindow; +extern HANDLE StdOut; +extern bool FancyStdOut; +extern HINSTANCE g_hInst; +extern FILE *Logfile; +extern bool NativeMouse; +extern bool ConWindowHidden; + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +CVAR (String, queryiwad_key, "shift", CVAR_GLOBALCONFIG|CVAR_ARCHIVE); + +double PerfToSec, PerfToMillisec; +UINT TimerPeriod; +UINT TimerEventID; +UINT MillisecondsPerTic; +HANDLE NewTicArrived; +uint32 LanguageIDs[4]; + +int (*I_GetTime) (bool saveMS); +int (*I_WaitForTic) (int); +void (*I_FreezeTime) (bool frozen); + +os_t OSPlatform; +bool gameisdead; + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +static ticcmd_t emptycmd; +static bool HasExited; + +static DWORD basetime = 0; +// These are for the polled timer. +static DWORD TicStart; +static DWORD TicNext; +static int TicFrozen; + +// These are for the event-driven timer. +static int tics; +static DWORD ted_start, ted_next; + +static WadStuff *WadList; +static int NumWads; +static int DefaultWad; + +static HCURSOR CustomCursor; + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// I_Tactile +// +// Doom calls it when you take damage, so presumably it could be converted +// to something compatible with force feedback. +// +//========================================================================== + +void I_Tactile(int on, int off, int total) +{ + // UNUSED. + on = off = total = 0; +} + +//========================================================================== +// +// I_BaseTiccmd +// +// Returns an empty ticcmd. I have no idea why this should be system- +// specific. +// +//========================================================================== + +ticcmd_t *I_BaseTiccmd() +{ + return &emptycmd; +} + +// Stubs that select the timer to use and then call into it ---------------- + +//========================================================================== +// +// I_GetTimeSelect +// +//========================================================================== + +static int I_GetTimeSelect(bool saveMS) +{ + I_SelectTimer(); + return I_GetTime(saveMS); +} + +//========================================================================== +// +// I_WaitForTicSelect +// +//========================================================================== + +static int I_WaitForTicSelect(int prevtic) +{ + I_SelectTimer(); + return I_WaitForTic(prevtic); +} + +//========================================================================== +// +// I_SelectTimer +// +// Tries to create a timer event for efficent CPU use when the FPS is +// capped. Failing that, it sets things up for a polling timer instead. +// +//========================================================================== + +static void I_SelectTimer() +{ + assert(basetime == 0); + + // Use a timer event if possible. + NewTicArrived = CreateEvent(NULL, FALSE, FALSE, NULL); + if (NewTicArrived) + { + UINT delay; + const char *cmdDelay; + + cmdDelay = Args->CheckValue("-timerdelay"); + delay = 0; + if (cmdDelay != 0) + { + delay = atoi(cmdDelay); + } + if (delay == 0) + { + delay = 1000/TICRATE; + } + MillisecondsPerTic = delay; + TimerEventID = timeSetEvent(delay, 0, TimerTicked, 0, TIME_PERIODIC); + } + // Get the current time as the basetime. + basetime = timeGetTime(); + // Set timer functions. + if (TimerEventID != 0) + { + I_GetTime = I_GetTimeEventDriven; + I_WaitForTic = I_WaitForTicEvent; + I_FreezeTime = I_FreezeTimeEventDriven; + } + else + { + I_GetTime = I_GetTimePolled; + I_WaitForTic = I_WaitForTicPolled; + I_FreezeTime = I_FreezeTimePolled; + } +} + +//========================================================================== +// +// I_MSTime +// +// Returns the current time in milliseconds, where 0 is the first call +// to I_GetTime or I_WaitForTic. +// +//========================================================================== + +unsigned int I_MSTime() +{ + // assert(basetime != 0); + return timeGetTime() - basetime; +} + +//========================================================================== +// +// I_FPSTime +// +// Returns the current system time in milliseconds. This is used by the FPS +// meter of DFrameBuffer::DrawRateStuff(). Since the screen can display +// before the play simulation is ready to begin, this needs to be +// separate from I_MSTime(). +// +//========================================================================== + +unsigned int I_FPSTime() +{ + return timeGetTime(); +} + +//========================================================================== +// +// I_GetTimePolled +// +// Returns the current time in tics. If saveMS is true, then calls to +// I_GetTimeFrac() will use this tic as 0 and the next tic as 1. +// +//========================================================================== + +static int I_GetTimePolled(bool saveMS) +{ + DWORD tm; + + if (TicFrozen != 0) + { + return TicFrozen; + } + + tm = timeGetTime(); + if (basetime == 0) + { + basetime = tm; + } + if (saveMS) + { + TicStart = tm; + TicNext = (tm * TICRATE / 1000 + 1) * 1000 / TICRATE; + } + + return ((tm-basetime)*TICRATE)/1000; +} + +//========================================================================== +// +// I_WaitForTicPolled +// +// Busy waits until the current tic is greater than prevtic. Time must not +// be frozen. +// +//========================================================================== + +static int I_WaitForTicPolled(int prevtic) +{ + int time; + + assert(TicFrozen == 0); + while ((time = I_GetTimePolled(false)) <= prevtic) + { } + + return time; +} + +//========================================================================== +// +// I_FreezeTimePolled +// +// Freeze/unfreeze the timer. +// +//========================================================================== + +static void I_FreezeTimePolled(bool frozen) +{ + if (frozen) + { + assert(TicFrozen == 0); + TicFrozen = I_GetTimePolled(false); + } + else + { + assert(TicFrozen != 0); + int froze = TicFrozen; + TicFrozen = 0; + int now = I_GetTimePolled(false); + basetime += (now - froze) * 1000 / TICRATE; + } +} + +//========================================================================== +// +// I_GetTimeEventDriven +// +// Returns the current tick counter. This is incremented asynchronously as +// the timer event fires. +// +//========================================================================== + +static int I_GetTimeEventDriven(bool saveMS) +{ + if (saveMS) + { + TicStart = ted_start; + TicNext = ted_next; + } + return tics; +} + +//========================================================================== +// +// I_WaitForTicEvent +// +// Waits on the timer event as long as the current tic is not later than +// prevtic. +// +//========================================================================== + +static int I_WaitForTicEvent(int prevtic) +{ + assert(!TicFrozen); + while (prevtic >= tics) + { + WaitForSingleObject(NewTicArrived, 1000/TICRATE); + } + return tics; +} + +//========================================================================== +// +// I_FreezeTimeEventDriven +// +// Freeze/unfreeze the ticker. +// +//========================================================================== + +static void I_FreezeTimeEventDriven(bool frozen) +{ + TicFrozen = frozen; +} + +//========================================================================== +// +// TimerTicked +// +// Advance the tick count and signal the NewTicArrived event. +// +//========================================================================== + +static void CALLBACK TimerTicked(UINT id, UINT msg, DWORD_PTR user, DWORD_PTR dw1, DWORD_PTR dw2) +{ + if (!TicFrozen) + { + tics++; + } + ted_start = timeGetTime (); + ted_next = ted_start + MillisecondsPerTic; + SetEvent(NewTicArrived); +} + +//========================================================================== +// +// I_GetTimeFrac +// +// Returns the fractional amount of a tic passed since the most recently +// saved tic. +// +//========================================================================== + +fixed_t I_GetTimeFrac(uint32 *ms) +{ + DWORD now = timeGetTime(); + if (ms != NULL) + { + *ms = TicNext; + } + DWORD step = TicNext - TicStart; + if (step == 0) + { + return FRACUNIT; + } + else + { + fixed_t frac = clamp ((now - TicStart)*FRACUNIT/step, 0, FRACUNIT); + return frac; + } +} + +//========================================================================== +// +// I_WaitVBL +// +// I_WaitVBL is never used to actually synchronize to the vertical blank. +// Instead, it's used for delay purposes. Doom used a 70 Hz display mode, +// so that's what we use to determine how long to wait for. +// +//========================================================================== + +void I_WaitVBL(int count) +{ + Sleep(1000 * count / 70); +} + +//========================================================================== +// +// I_DetectOS +// +// Determine which version of Windows the game is running on. +// +//========================================================================== + +void I_DetectOS(void) +{ + OSVERSIONINFOEX info; + const char *osname; + + info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + if (!GetVersionEx((OSVERSIONINFO *)&info)) + { + // Retry with the older OSVERSIONINFO structure. + info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx((OSVERSIONINFO *)&info); + } + + switch (info.dwPlatformId) + { + case VER_PLATFORM_WIN32_WINDOWS: + OSPlatform = os_Win95; + if (info.dwMinorVersion < 10) + { + osname = "95"; + } + else if (info.dwMinorVersion < 90) + { + osname = "98"; + } + else + { + osname = "Me"; + } + break; + + case VER_PLATFORM_WIN32_NT: + OSPlatform = info.dwMajorVersion < 5 ? os_WinNT4 : os_Win2k; + osname = "NT"; + if (info.dwMajorVersion == 5) + { + if (info.dwMinorVersion == 0) + { + osname = "2000"; + } + if (info.dwMinorVersion == 1) + { + osname = "XP"; + } + else if (info.dwMinorVersion == 2) + { + osname = "Server 2003"; + } + } + else if (info.dwMajorVersion == 6) + { + if (info.dwMinorVersion == 0) + { + osname = (info.wProductType == VER_NT_WORKSTATION) ? "Vista" : "Server 2008"; + } + else if (info.dwMinorVersion == 1) + { + osname = (info.wProductType == VER_NT_WORKSTATION) ? "7" : "Server 2008 R2"; + } + else if (info.dwMinorVersion == 2) + { + // Starting with Windows 8.1, you need to specify in your manifest + // the highest version of Windows you support, which will also be the + // highest version of Windows this function returns. + osname = (info.wProductType == VER_NT_WORKSTATION) ? "8" : "Server 2012"; + } + else if (info.dwMinorVersion == 3) + { + osname = (info.wProductType == VER_NT_WORKSTATION) ? "8.1" : "Server 2012 R2"; + } + else if (info.dwMinorVersion == 4) + { + osname = (info.wProductType == VER_NT_WORKSTATION) ? "10 (or higher)" : "Server 10 (or higher)"; + } + } + break; + + default: + OSPlatform = os_unknown; + osname = "Unknown OS"; + break; + } + + if (OSPlatform == os_Win95) + { + Printf ("OS: Windows %s %lu.%lu.%lu %s\n", + osname, + info.dwMajorVersion, info.dwMinorVersion, + info.dwBuildNumber & 0xffff, info.szCSDVersion); + } + else + { + Printf ("OS: Windows %s (NT %lu.%lu) Build %lu\n %s\n", + osname, + info.dwMajorVersion, info.dwMinorVersion, + info.dwBuildNumber, info.szCSDVersion); + } + + if (OSPlatform == os_unknown) + { + Printf ("(Assuming Windows 2000)\n"); + OSPlatform = os_Win2k; + } +} + +//========================================================================== +// +// SubsetLanguageIDs +// +// Helper function for SetLanguageIDs. +// +//========================================================================== + +static void SubsetLanguageIDs(LCID id, LCTYPE type, int idx) +{ + char buf[8]; + LCID langid; + char *idp; + + if (!GetLocaleInfo(id, type, buf, 8)) + return; + langid = MAKELCID(strtoul(buf, NULL, 16), SORT_DEFAULT); + if (!GetLocaleInfo(langid, LOCALE_SABBREVLANGNAME, buf, 8)) + return; + idp = (char *)(&LanguageIDs[idx]); + memset (idp, 0, 4); + idp[0] = tolower(buf[0]); + idp[1] = tolower(buf[1]); + idp[2] = tolower(buf[2]); + idp[3] = 0; +} + +//========================================================================== +// +// SetLanguageIDs +// +//========================================================================== + +void SetLanguageIDs() +{ + size_t langlen = strlen(language); + + if (langlen < 2 || langlen > 3) + { + memset(LanguageIDs, 0, sizeof(LanguageIDs)); + SubsetLanguageIDs(LOCALE_USER_DEFAULT, LOCALE_ILANGUAGE, 0); + SubsetLanguageIDs(LOCALE_USER_DEFAULT, LOCALE_IDEFAULTLANGUAGE, 1); + SubsetLanguageIDs(LOCALE_SYSTEM_DEFAULT, LOCALE_ILANGUAGE, 2); + SubsetLanguageIDs(LOCALE_SYSTEM_DEFAULT, LOCALE_IDEFAULTLANGUAGE, 3); + } + else + { + DWORD lang = 0; + + ((BYTE *)&lang)[0] = (language)[0]; + ((BYTE *)&lang)[1] = (language)[1]; + ((BYTE *)&lang)[2] = (language)[2]; + LanguageIDs[0] = lang; + LanguageIDs[1] = lang; + LanguageIDs[2] = lang; + LanguageIDs[3] = lang; + } +} + +//========================================================================== +// +// CalculateCPUSpeed +// +// Make a decent guess at how much time elapses between TSC steps. This can +// vary over runtime depending on power management settings, so should not +// be used anywhere that truely accurate timing actually matters. +// +//========================================================================== + +void CalculateCPUSpeed() +{ + LARGE_INTEGER freq; + + QueryPerformanceFrequency (&freq); + + if (freq.QuadPart != 0 && CPU.bRDTSC) + { + LARGE_INTEGER count1, count2; + cycle_t ClockCalibration; + DWORD min_diff; + + ClockCalibration.Reset(); + + // Count cycles for at least 55 milliseconds. + // The performance counter may be very low resolution compared to CPU + // speeds today, so the longer we count, the more accurate our estimate. + // On the other hand, we don't want to count too long, because we don't + // want the user to notice us spend time here, since most users will + // probably never use the performance statistics. + min_diff = freq.LowPart * 11 / 200; + + // Minimize the chance of task switching during the testing by going very + // high priority. This is another reason to avoid timing for too long. + SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS); + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); + + // Make sure we start timing on a counter boundary. + QueryPerformanceCounter(&count1); + do { QueryPerformanceCounter(&count2); } while (count1.QuadPart == count2.QuadPart); + + // Do the timing loop. + ClockCalibration.Clock(); + do { QueryPerformanceCounter(&count1); } while ((count1.QuadPart - count2.QuadPart) < min_diff); + ClockCalibration.Unclock(); + + SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); + + PerfToSec = double(count1.QuadPart - count2.QuadPart) / (double(ClockCalibration.GetRawCounter()) * freq.QuadPart); + PerfToMillisec = PerfToSec * 1000.0; + } + + Printf ("CPU Speed: %.0f MHz\n", 0.001 / PerfToMillisec); +} + +//========================================================================== +// +// I_Init +// +//========================================================================== + +void I_Init() +{ + CheckCPUID(&CPU); + CalculateCPUSpeed(); + DumpCPUInfo(&CPU); + + I_GetTime = I_GetTimeSelect; + I_WaitForTic = I_WaitForTicSelect; + + atterm (I_ShutdownSound); + I_InitSound (); +} + +//========================================================================== +// +// I_Quit +// +//========================================================================== + +void I_Quit() +{ + HasExited = true; /* Prevent infinitely recursive exits -- killough */ + + if (TimerEventID != 0) + { + timeKillEvent(TimerEventID); + } + if (NewTicArrived != NULL) + { + CloseHandle(NewTicArrived); + } + timeEndPeriod(TimerPeriod); + if (demorecording) + { + G_CheckDemoStatus(); + } + + C_DeinitConsole(); +} + + +//========================================================================== +// +// I_FatalError +// +// Throw an error that will end the game. +// +//========================================================================== + +void STACK_ARGS I_FatalError(const char *error, ...) +{ + static BOOL alreadyThrown = false; + gameisdead = true; + + if (!alreadyThrown) // ignore all but the first message -- killough + { + alreadyThrown = true; + char errortext[MAX_ERRORTEXT]; + va_list argptr; + va_start(argptr, error); + myvsnprintf(errortext, MAX_ERRORTEXT, error, argptr); + va_end(argptr); + + // Record error to log (if logging) + if (Logfile) + { + fprintf(Logfile, "\n**** DIED WITH FATAL ERROR:\n%s\n", errortext); + fflush(Logfile); + } + + throw CFatalError(errortext); + } + + if (!HasExited) // If it hasn't exited yet, exit now -- killough + { + HasExited = 1; // Prevent infinitely recursive exits -- killough + exit(-1); + } +} + +//========================================================================== +// +// I_Error +// +// Throw an error that will send us to the console if we are far enough +// along in the startup process. +// +//========================================================================== + +void STACK_ARGS I_Error(const char *error, ...) +{ + va_list argptr; + char errortext[MAX_ERRORTEXT]; + + va_start(argptr, error); + myvsnprintf(errortext, MAX_ERRORTEXT, error, argptr); + va_end(argptr); + + throw CRecoverableError(errortext); +} + +//========================================================================== +// +// ToEditControl +// +// Converts string to Unicode and inserts it into the control. +// +//========================================================================== + +void ToEditControl(HWND edit, const char *buf, wchar_t *wbuf, int bpos) +{ + // Let's just do this ourself. It's not hard, and we can compensate for + // special console characters at the same time. +#if 0 + MultiByteToWideChar(1252 /* Western */, 0, buf, bpos, wbuf, countof(wbuf)); + wbuf[bpos] = 0; +#else + static wchar_t notlatin1[32] = // code points 0x80-0x9F + { + 0x20AC, // Euro sign + 0x0081, // Undefined + 0x201A, // Single low-9 quotation mark + 0x0192, // Latin small letter f with hook + 0x201E, // Double low-9 quotation mark + 0x2026, // Horizontal ellipsis + 0x2020, // Dagger + 0x2021, // Double dagger + 0x02C6, // Modifier letter circumflex accent + 0x2030, // Per mille sign + 0x0160, // Latin capital letter S with caron + 0x2039, // Single left-pointing angle quotation mark + 0x0152, // Latin capital ligature OE + 0x008D, // Undefined + 0x017D, // Latin capital letter Z with caron + 0x008F, // Undefined + 0x0090, // Undefined + 0x2018, // Left single quotation mark + 0x2019, // Right single quotation mark + 0x201C, // Left double quotation mark + 0x201D, // Right double quotation mark + 0x2022, // Bullet + 0x2013, // En dash + 0x2014, // Em dash + 0x02DC, // Small tilde + 0x2122, // Trade mark sign + 0x0161, // Latin small letter s with caron + 0x203A, // Single right-pointing angle quotation mark + 0x0153, // Latin small ligature oe + 0x009D, // Undefined + 0x017E, // Latin small letter z with caron + 0x0178 // Latin capital letter Y with diaeresis + }; + for (int i = 0; i <= bpos; ++i) + { + wchar_t code = (BYTE)buf[i]; + if (code >= 0x1D && code <= 0x1F) + { // The bar characters, most commonly used to indicate map changes + code = 0x2550; // Box Drawings Double Horizontal + } + else if (code >= 0x80 && code <= 0x9F) + { + code = notlatin1[code - 0x80]; + } + wbuf[i] = code; + } +#endif + SendMessageW(edit, EM_REPLACESEL, FALSE, (LPARAM)wbuf); +} + +//========================================================================== +// +// I_PrintStr +// +// Send output to the list box shown during startup (and hidden during +// gameplay). +// +//========================================================================== + +static void DoPrintStr(const char *cp, HWND edit, HANDLE StdOut) +{ + if (edit == NULL && StdOut == NULL) + return; + + char buf[256]; + wchar_t wbuf[countof(buf)]; + int bpos = 0; + CHARRANGE selection; + CHARRANGE endselection; + LONG lines_before = 0, lines_after; + CHARFORMAT format; + + if (edit != NULL) + { + // Store the current selection and set it to the end so we can append text. + SendMessage(edit, EM_EXGETSEL, 0, (LPARAM)&selection); + endselection.cpMax = endselection.cpMin = GetWindowTextLength(edit); + SendMessage(edit, EM_EXSETSEL, 0, (LPARAM)&endselection); + + // GetWindowTextLength and EM_EXSETSEL can disagree on where the end of + // the text is. Find out what EM_EXSETSEL thought it was and use that later. + SendMessage(edit, EM_EXGETSEL, 0, (LPARAM)&endselection); + + // Remember how many lines there were before we added text. + lines_before = (LONG)SendMessage(edit, EM_GETLINECOUNT, 0, 0); + } + + while (*cp != 0) + { + // 28 is the escape code for a color change. + if ((*cp == 28 && bpos != 0) || bpos == 255) + { + buf[bpos] = 0; + if (edit != NULL) + { + ToEditControl(edit, buf, wbuf, bpos); + } + if (StdOut != NULL) + { + DWORD bytes_written; + WriteFile(StdOut, buf, bpos, &bytes_written, NULL); + } + bpos = 0; + } + if (*cp != 28) + { + buf[bpos++] = *cp++; + } + else + { + const BYTE *color_id = (const BYTE *)cp + 1; + EColorRange range = V_ParseFontColor(color_id, CR_UNTRANSLATED, CR_YELLOW); + cp = (const char *)color_id; + + if (range != CR_UNDEFINED) + { + // Change the color of future text added to the control. + PalEntry color = V_LogColorFromColorRange(range); + if (StdOut != NULL && FancyStdOut) + { + // Unfortunately, we are pretty limited here: There are only + // eight basic colors, and each comes in a dark and a bright + // variety. + float h, s, v, r, g, b; + WORD attrib = 0; + + RGBtoHSV(color.r / 255.f, color.g / 255.f, color.b / 255.f, &h, &s, &v); + if (s != 0) + { // color + HSVtoRGB(&r, &g, &b, h, 1, 1); + if (r == 1) attrib = FOREGROUND_RED; + if (g == 1) attrib |= FOREGROUND_GREEN; + if (b == 1) attrib |= FOREGROUND_BLUE; + if (v > 0.6) attrib |= FOREGROUND_INTENSITY; + } + else + { // gray + if (v < 0.33) attrib = FOREGROUND_INTENSITY; + else if (v < 0.90) attrib = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + else attrib = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY; + } + SetConsoleTextAttribute(StdOut, attrib); + } + if (edit != NULL) + { + // GDI uses BGR colors, but color is RGB, so swap the R and the B. + swapvalues(color.r, color.b); + // Change the color. + format.cbSize = sizeof(format); + format.dwMask = CFM_COLOR; + format.dwEffects = 0; + format.crTextColor = color; + SendMessage(edit, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&format); + } + } + } + } + if (bpos != 0) + { + buf[bpos] = 0; + if (edit != NULL) + { + ToEditControl(edit, buf, wbuf, bpos); + } + if (StdOut != NULL) + { + DWORD bytes_written; + WriteFile(StdOut, buf, bpos, &bytes_written, NULL); + } + } + + if (edit != NULL) + { + // If the old selection was at the end of the text, keep it at the end and + // scroll. Don't scroll if the selection is anywhere else. + if (selection.cpMin == endselection.cpMin && selection.cpMax == endselection.cpMax) + { + selection.cpMax = selection.cpMin = GetWindowTextLength (edit); + lines_after = (LONG)SendMessage(edit, EM_GETLINECOUNT, 0, 0); + if (lines_after > lines_before) + { + SendMessage(edit, EM_LINESCROLL, 0, lines_after - lines_before); + } + } + // Restore the previous selection. + SendMessage(edit, EM_EXSETSEL, 0, (LPARAM)&selection); + // Give the edit control a chance to redraw itself. + I_GetEvent(); + } + if (StdOut != NULL && FancyStdOut) + { // Set text back to gray, in case it was changed. + SetConsoleTextAttribute(StdOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + } +} + +static TArray bufferedConsoleStuff; + +void I_PrintStr(const char *cp) +{ + if (ConWindowHidden) + { + bufferedConsoleStuff.Push(cp); + DoPrintStr(cp, NULL, StdOut); + } + else + { + DoPrintStr(cp, ConWindow, StdOut); + } +} + +void I_FlushBufferedConsoleStuff() +{ + for (unsigned i = 0; i < bufferedConsoleStuff.Size(); i++) + { + DoPrintStr(bufferedConsoleStuff[i], ConWindow, NULL); + } + bufferedConsoleStuff.Clear(); +} + +//========================================================================== +// +// SetQueryIWAD +// +// The user had the "Don't ask again" box checked when they closed the +// IWAD selection dialog. +// +//========================================================================== + +static void SetQueryIWad(HWND dialog) +{ + HWND checkbox = GetDlgItem(dialog, IDC_DONTASKIWAD); + int state = (int)SendMessage(checkbox, BM_GETCHECK, 0, 0); + bool query = (state != BST_CHECKED); + + if (!query && queryiwad) + { + MessageBox(dialog, + "You have chosen not to show this dialog box in the future.\n" + "If you wish to see it again, hold down SHIFT while starting " GAMENAME ".", + "Don't ask me this again", + MB_OK | MB_ICONINFORMATION); + } + + queryiwad = query; +} + +//========================================================================== +// +// IWADBoxCallback +// +// Dialog proc for the IWAD selector. +// +//========================================================================== + +BOOL CALLBACK IWADBoxCallback(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + HWND ctrl; + int i; + + switch (message) + { + case WM_INITDIALOG: + // Add our program name to the window title + { + TCHAR label[256]; + FString newlabel; + + GetWindowText(hDlg, label, countof(label)); + newlabel.Format(GAMESIG " %s: %s", GetVersionString(), label); + SetWindowText(hDlg, newlabel.GetChars()); + } + // Populate the list with all the IWADs found + ctrl = GetDlgItem(hDlg, IDC_IWADLIST); + for (i = 0; i < NumWads; i++) + { + FString work; + const char *filepart = strrchr(WadList[i].Path, '/'); + if (filepart == NULL) + filepart = WadList[i].Path; + else + filepart++; + work.Format("%s (%s)", WadList[i].Name.GetChars(), filepart); + SendMessage(ctrl, LB_ADDSTRING, 0, (LPARAM)work.GetChars()); + SendMessage(ctrl, LB_SETITEMDATA, i, (LPARAM)i); + } + SendMessage(ctrl, LB_SETCURSEL, DefaultWad, 0); + SetFocus(ctrl); + // Set the state of the "Don't ask me again" checkbox + ctrl = GetDlgItem(hDlg, IDC_DONTASKIWAD); + SendMessage(ctrl, BM_SETCHECK, queryiwad ? BST_UNCHECKED : BST_CHECKED, 0); + // Make sure the dialog is in front. If SHIFT was pressed to force it visible, + // then the other window will normally be on top. + SetForegroundWindow(hDlg); + break; + + case WM_COMMAND: + if (LOWORD(wParam) == IDCANCEL) + { + EndDialog (hDlg, -1); + } + else if (LOWORD(wParam) == IDOK || + (LOWORD(wParam) == IDC_IWADLIST && HIWORD(wParam) == LBN_DBLCLK)) + { + SetQueryIWad(hDlg); + ctrl = GetDlgItem (hDlg, IDC_IWADLIST); + EndDialog(hDlg, SendMessage (ctrl, LB_GETCURSEL, 0, 0)); + } + break; + } + return FALSE; +} + +//========================================================================== +// +// I_PickIWad +// +// Open a dialog to pick the IWAD, if there is more than one found. +// +//========================================================================== + +int I_PickIWad(WadStuff *wads, int numwads, bool showwin, int defaultiwad) +{ + int vkey; + + if (stricmp(queryiwad_key, "shift") == 0) + { + vkey = VK_SHIFT; + } + else if (stricmp(queryiwad_key, "control") == 0 || stricmp (queryiwad_key, "ctrl") == 0) + { + vkey = VK_CONTROL; + } + else + { + vkey = 0; + } + if (showwin || (vkey != 0 && GetAsyncKeyState(vkey))) + { + WadList = wads; + NumWads = numwads; + DefaultWad = defaultiwad; + + return (int)DialogBox(g_hInst, MAKEINTRESOURCE(IDD_IWADDIALOG), + (HWND)Window, (DLGPROC)IWADBoxCallback); + } + return defaultiwad; +} + +//========================================================================== +// +// I_SetCursor +// +// Returns true if the cursor was successfully changed. +// +//========================================================================== + +bool I_SetCursor(FTexture *cursorpic) +{ + HCURSOR cursor; + + if (cursorpic != NULL && cursorpic->UseType != FTexture::TEX_Null && + (screen == NULL || !screen->Is8BitMode())) + { + // Must be no larger than 32x32. + if (cursorpic->GetWidth() > 32 || cursorpic->GetHeight() > 32) + { + return false; + } + + cursor = CreateAlphaCursor(cursorpic); + if (cursor == NULL) + { + cursor = CreateCompatibleCursor(cursorpic); + } + if (cursor == NULL) + { + return false; + } + // Replace the existing cursor with the new one. + DestroyCustomCursor(); + CustomCursor = cursor; + atterm(DestroyCustomCursor); + } + else + { + DestroyCustomCursor(); + cursor = LoadCursor(NULL, IDC_ARROW); + } + SetClassLongPtr(Window, GCLP_HCURSOR, (LONG_PTR)cursor); + if (NativeMouse) + { + POINT pt; + RECT client; + + // If the mouse pointer is within the window's client rect, set it now. + if (GetCursorPos(&pt) && GetClientRect(Window, &client) && + ClientToScreen(Window, (LPPOINT)&client.left) && + ClientToScreen(Window, (LPPOINT)&client.right)) + { + if (pt.x >= client.left && pt.x < client.right && + pt.y >= client.top && pt.y < client.bottom) + { + SetCursor(cursor); + } + } + } + return true; +} + +//========================================================================== +// +// CreateCompatibleCursor +// +// Creates a cursor with a 1-bit alpha channel. +// +//========================================================================== + +static HCURSOR CreateCompatibleCursor(FTexture *cursorpic) +{ + int picwidth = cursorpic->GetWidth(); + int picheight = cursorpic->GetHeight(); + + // Create bitmap masks for the cursor from the texture. + HDC dc = GetDC(NULL); + if (dc == NULL) + { + return false; + } + HDC and_mask_dc = CreateCompatibleDC(dc); + HDC xor_mask_dc = CreateCompatibleDC(dc); + HBITMAP and_mask = CreateCompatibleBitmap(dc, 32, 32); + HBITMAP xor_mask = CreateCompatibleBitmap(dc, 32, 32); + ReleaseDC(NULL, dc); + + SelectObject(and_mask_dc, and_mask); + SelectObject(xor_mask_dc, xor_mask); + + // Initialize with an invisible cursor. + SelectObject(and_mask_dc, GetStockObject(WHITE_PEN)); + SelectObject(and_mask_dc, GetStockObject(WHITE_BRUSH)); + Rectangle(and_mask_dc, 0, 0, 32, 32); + SelectObject(xor_mask_dc, GetStockObject(BLACK_PEN)); + SelectObject(xor_mask_dc, GetStockObject(BLACK_BRUSH)); + Rectangle(xor_mask_dc, 0, 0, 32, 32); + + FBitmap bmp; + const BYTE *pixels; + + bmp.Create(picwidth, picheight); + cursorpic->CopyTrueColorPixels(&bmp, 0, 0); + pixels = bmp.GetPixels(); + + // Copy color data from the source texture to the cursor bitmaps. + for (int y = 0; y < picheight; ++y) + { + for (int x = 0; x < picwidth; ++x) + { + const BYTE *bgra = &pixels[x*4 + y*bmp.GetPitch()]; + if (bgra[3] != 0) + { + SetPixelV(and_mask_dc, x, y, RGB(0,0,0)); + SetPixelV(xor_mask_dc, x, y, RGB(bgra[2], bgra[1], bgra[0])); + } + } + } + DeleteDC(and_mask_dc); + DeleteDC(xor_mask_dc); + + // Create the cursor from the bitmaps. + return CreateBitmapCursor(cursorpic->LeftOffset, cursorpic->TopOffset, and_mask, xor_mask); +} + +//========================================================================== +// +// CreateAlphaCursor +// +// Creates a cursor with a full alpha channel. +// +//========================================================================== + +static HCURSOR CreateAlphaCursor(FTexture *cursorpic) +{ + HDC dc; + BITMAPV5HEADER bi; + HBITMAP color, mono; + void *bits; + + memset(&bi, 0, sizeof(bi)); + bi.bV5Size = sizeof(bi); + bi.bV5Width = 32; + bi.bV5Height = 32; + bi.bV5Planes = 1; + bi.bV5BitCount = 32; + bi.bV5Compression = BI_BITFIELDS; + bi.bV5RedMask = 0x00FF0000; + bi.bV5GreenMask = 0x0000FF00; + bi.bV5BlueMask = 0x000000FF; + bi.bV5AlphaMask = 0xFF000000; + + dc = GetDC(NULL); + if (dc == NULL) + { + return NULL; + } + + // Create the DIB section with an alpha channel. + color = CreateDIBSection(dc, (BITMAPINFO *)&bi, DIB_RGB_COLORS, &bits, NULL, 0); + ReleaseDC(NULL, dc); + + if (color == NULL) + { + return NULL; + } + + // Create an empty mask bitmap, since CreateIconIndirect requires this. + mono = CreateBitmap(32, 32, 1, 1, NULL); + if (mono == NULL) + { + DeleteObject(color); + return NULL; + } + + // Copy cursor to the color bitmap. Note that GDI bitmaps are upside down compared + // to normal conventions, so we create the FBitmap pointing at the last row and use + // a negative pitch so that CopyTrueColorPixels will use GDI's orientation. + FBitmap bmp((BYTE *)bits + 31*32*4, -32*4, 32, 32); + cursorpic->CopyTrueColorPixels(&bmp, 0, 0); + + return CreateBitmapCursor(cursorpic->LeftOffset, cursorpic->TopOffset, mono, color); +} + +//========================================================================== +// +// CreateBitmapCursor +// +// Create the cursor from the bitmaps. Deletes the bitmaps before returning. +// +//========================================================================== + +static HCURSOR CreateBitmapCursor(int xhot, int yhot, HBITMAP and_mask, HBITMAP color_mask) +{ + ICONINFO iconinfo = + { + FALSE, // fIcon + xhot, // xHotspot + yhot, // yHotspot + and_mask, // hbmMask + color_mask // hbmColor + }; + HCURSOR cursor = CreateIconIndirect(&iconinfo); + + // Delete the bitmaps. + DeleteObject(and_mask); + DeleteObject(color_mask); + + return cursor; +} + +//========================================================================== +// +// DestroyCustomCursor +// +//========================================================================== + +static void DestroyCustomCursor() +{ + if (CustomCursor != NULL) + { + DestroyCursor(CustomCursor); + CustomCursor = NULL; + } +} + +//========================================================================== +// +// I_WriteIniFailed +// +// Display a message when the config failed to save. +// +//========================================================================== + +bool I_WriteIniFailed() +{ + char *lpMsgBuf; + FString errortext; + + FormatMessageA (FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPSTR)&lpMsgBuf, + 0, + NULL + ); + errortext.Format ("The config file %s could not be written:\n%s", GameConfig->GetPathName(), lpMsgBuf); + LocalFree (lpMsgBuf); + return MessageBox(Window, errortext.GetChars(), GAMENAME " configuration not saved", MB_ICONEXCLAMATION | MB_RETRYCANCEL) == IDRETRY; +} + +//========================================================================== +// +// I_FindFirst +// +// Start a pattern matching sequence. +// +//========================================================================== + +void *I_FindFirst(const char *filespec, findstate_t *fileinfo) +{ + return FindFirstFileA(filespec, (LPWIN32_FIND_DATAA)fileinfo); +} + +//========================================================================== +// +// I_FindNext +// +// Return the next file in a pattern matching sequence. +// +//========================================================================== + +int I_FindNext(void *handle, findstate_t *fileinfo) +{ + return !FindNextFileA((HANDLE)handle, (LPWIN32_FIND_DATAA)fileinfo); +} + +//========================================================================== +// +// I_FindClose +// +// Finish a pattern matching sequence. +// +//========================================================================== + +int I_FindClose(void *handle) +{ + return FindClose((HANDLE)handle); +} + +//========================================================================== +// +// QueryPathKey +// +// Returns the value of a registry key into the output variable value. +// +//========================================================================== + +static bool QueryPathKey(HKEY key, const char *keypath, const char *valname, FString &value) +{ + HKEY pathkey; + DWORD pathtype; + DWORD pathlen; + LONG res; + + if(ERROR_SUCCESS == RegOpenKeyEx(key, keypath, 0, KEY_QUERY_VALUE, &pathkey)) + { + if (ERROR_SUCCESS == RegQueryValueEx(pathkey, valname, 0, &pathtype, NULL, &pathlen) && + pathtype == REG_SZ && pathlen != 0) + { + // Don't include terminating null in count + char *chars = value.LockNewBuffer(pathlen - 1); + res = RegQueryValueEx(pathkey, valname, 0, NULL, (LPBYTE)chars, &pathlen); + value.UnlockBuffer(); + if (res != ERROR_SUCCESS) + { + value = ""; + } + } + RegCloseKey(pathkey); + } + return value.IsNotEmpty(); +} + +//========================================================================== +// +// I_GetGogPaths +// +// Check the registry for GOG installation paths, so we can search for IWADs +// that were bought from GOG.com. This is a bit different from the Steam +// version because each game has its own independent installation path, no +// such thing as /SteamApps/common/. +// +//========================================================================== + +TArray I_GetGogPaths() +{ + TArray result; + FString path; + FString gamepath; + +#ifdef _WIN64 + FString gogregistrypath = "Software\\Wow6432Node\\GOG.com\\Games"; +#else + // If a 32-bit ZDoom runs on a 64-bit Windows, this will be transparently and + // automatically redirected to the Wow6432Node address instead, so this address + // should be safe to use in all cases. + FString gogregistrypath = "Software\\GOG.com\\Games"; +#endif + + // Look for Ultimate Doom + gamepath = gogregistrypath + "\\1435827232"; + if (QueryPathKey(HKEY_LOCAL_MACHINE, gamepath.GetChars(), "Path", path)) + { + result.Push(path); // directly in install folder + } + + // Look for Doom II + gamepath = gogregistrypath + "\\1435848814"; + if (QueryPathKey(HKEY_LOCAL_MACHINE, gamepath.GetChars(), "Path", path)) + { + result.Push(path + "/doom2"); // in a subdirectory + // If direct support for the Master Levels is ever added, they are in path + /master/wads + } + + // Look for Final Doom + gamepath = gogregistrypath + "\\1435848742"; + if (QueryPathKey(HKEY_LOCAL_MACHINE, gamepath.GetChars(), "Path", path)) + { + // in subdirectories + result.Push(path + "/TNT"); + result.Push(path + "/Plutonia"); + } + + return result; +} + +//========================================================================== +// +// I_GetSteamPath +// +// Check the registry for the path to Steam, so that we can search for +// IWADs that were bought with Steam. +// +//========================================================================== + +TArray I_GetSteamPath() +{ + TArray result; + static const char *const steam_dirs[] = + { + "doom 2/base", + "final doom/base", + "heretic shadow of the serpent riders/base", + "hexen/base", + "hexen deathkings of the dark citadel/base", + "ultimate doom/base", + "DOOM 3 BFG Edition/base/wads", + "Strife" + }; + + FString path; + + if (!QueryPathKey(HKEY_CURRENT_USER, "Software\\Valve\\Steam", "SteamPath", path)) + { + if (!QueryPathKey(HKEY_LOCAL_MACHINE, "Software\\Valve\\Steam", "InstallPath", path)) + return result; + } + path += "/SteamApps/common/"; + + for(unsigned int i = 0; i < countof(steam_dirs); ++i) + { + result.Push(path + steam_dirs[i]); + } + + return result; +} + +//========================================================================== +// +// I_MakeRNGSeed +// +// Returns a 32-bit random seed, preferably one with lots of entropy. +// +//========================================================================== + +unsigned int I_MakeRNGSeed() +{ + unsigned int seed; + + // If RtlGenRandom is available, use that to avoid increasing the + // working set by pulling in all of the crytographic API. + HMODULE advapi = GetModuleHandle("advapi32.dll"); + if (advapi != NULL) + { + BOOLEAN (APIENTRY *RtlGenRandom)(void *, ULONG) = + (BOOLEAN (APIENTRY *)(void *, ULONG))GetProcAddress(advapi, "SystemFunction036"); + if (RtlGenRandom != NULL) + { + if (RtlGenRandom(&seed, sizeof(seed))) + { + return seed; + } + } + } + + // Use the full crytographic API to produce a seed. If that fails, + // time() is used as a fallback. + HCRYPTPROV prov; + + if (!CryptAcquireContext(&prov, NULL, MS_DEF_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) + { + return (unsigned int)time(NULL); + } + if (!CryptGenRandom(prov, sizeof(seed), (BYTE *)&seed)) + { + seed = (unsigned int)time(NULL); + } + CryptReleaseContext(prov, 0); + return seed; +} + +//========================================================================== +// +// I_GetLongPathName +// +// Returns the long version of the path, or the original if there isn't +// anything worth changing. +// +//========================================================================== + +FString I_GetLongPathName(FString shortpath) +{ + static TOptWin32Proc + GetLongPathNameA("kernel32.dll", "GetLongPathNameA"); + + // Doesn't exist on NT4 + if (GetLongPathName == NULL) + return shortpath; + + DWORD buffsize = GetLongPathNameA.Call(shortpath.GetChars(), NULL, 0); + if (buffsize == 0) + { // nothing to change (it doesn't exist, maybe?) + return shortpath; + } + TCHAR *buff = new TCHAR[buffsize]; + DWORD buffsize2 = GetLongPathNameA.Call(shortpath.GetChars(), buff, buffsize); + if (buffsize2 >= buffsize) + { // Failure! Just return the short path + delete[] buff; + return shortpath; + } + FString longpath(buff, buffsize2); + delete[] buff; + return longpath; +}